feat: настройки импорта вынесены в текстовый файл-словарь
- import_settings.json рядом с программой (создаётся автоматически): что импортировать (var), откуда (cell: 'Общие сведения'!B2 или keywords: регэксп поиска по колонке A) - importer.py читает словарь: сначала конкретная ячейка, потом ключевые слова - листы общих сведений/сотрудников/ИС тоже настраиваются (ключевые слова) - UI: меню «Генерация → Файл настроек импорта…» — выбор пути к словарю - при сборке .exe файл остаётся редактируемым без пересборки
This commit is contained in:
+81
-59
@@ -12,6 +12,9 @@ except ImportError:
|
||||
openpyxl = None
|
||||
|
||||
|
||||
from .import_settings import load_settings, parse_cell_ref, cell_value
|
||||
|
||||
|
||||
def clean_value(val):
|
||||
"""Очистка и нормализация значения ячейки Excel."""
|
||||
if val is None:
|
||||
@@ -42,14 +45,20 @@ def clean_value(val):
|
||||
return text
|
||||
|
||||
|
||||
def import_152fz(filepath: str, log_fn: Optional[Callable] = None) -> dict:
|
||||
"""Импорт данных из опросного листа Excel (152-ФЗ)."""
|
||||
def import_152fz(filepath: str, log_fn: Optional[Callable] = None, settings_path: Optional[str] = None) -> dict:
|
||||
"""Импорт данных из опросного листа Excel (152-ФЗ).
|
||||
|
||||
settings_path — путь к JSON-файлу настроек импорта (что и откуда брать).
|
||||
Если None — файл import_settings.json рядом с программой; если его нет —
|
||||
используется встроенный словарь по умолчанию.
|
||||
"""
|
||||
if openpyxl is None:
|
||||
raise ImportError("openpyxl не установлен. Установите: pip install openpyxl")
|
||||
|
||||
if log_fn is None:
|
||||
log_fn = lambda msg: None
|
||||
|
||||
settings = load_settings(settings_path)
|
||||
log_fn(f"Загрузка опросного листа: {filepath}")
|
||||
|
||||
data = {
|
||||
@@ -68,9 +77,9 @@ def import_152fz(filepath: str, log_fn: Optional[Callable] = None) -> dict:
|
||||
return data
|
||||
|
||||
try:
|
||||
_import_152_common(wb, data, log_fn)
|
||||
_import_152_is_list(wb, data, log_fn)
|
||||
_import_152_employees(wb, data, log_fn)
|
||||
_import_152_common(wb, data, settings, log_fn)
|
||||
_import_152_is_list(wb, data, settings, log_fn)
|
||||
_import_152_employees(wb, data, settings, log_fn)
|
||||
except Exception as e:
|
||||
log_fn(f"❌ Ошибка при импорте: {e}")
|
||||
log_fn(traceback.format_exc())
|
||||
@@ -88,71 +97,83 @@ def import_152fz(filepath: str, log_fn: Optional[Callable] = None) -> dict:
|
||||
return data
|
||||
|
||||
|
||||
def _import_152_common(wb, data, log_fn):
|
||||
"""Импорт общих сведений из листа 'Общие сведения'."""
|
||||
def _import_152_common(wb, data, settings, log_fn):
|
||||
"""Импорт общих сведений по словарю настроек.
|
||||
|
||||
Для каждого поля: сначала пробуем конкретную ячейку (cell),
|
||||
если она задана и заполнена; иначе ищем строку по ключевым
|
||||
словам (keywords) в колонке A листа «Общие сведения».
|
||||
"""
|
||||
sheet_name = None
|
||||
sheet_kw = settings.get('common_sheet_keyword', 'общие')
|
||||
for sn in wb.sheetnames:
|
||||
if 'общие' in sn.lower():
|
||||
if sheet_kw in sn.lower():
|
||||
sheet_name = sn
|
||||
break
|
||||
if not sheet_name:
|
||||
log_fn("⚠ Лист 'Общие сведения' не найден")
|
||||
log_fn(f"⚠ Лист по ключу '{sheet_kw}' не найден")
|
||||
return
|
||||
|
||||
ws = wb[sheet_name]
|
||||
field_map = [
|
||||
('полное наименование организации', 'fullName'),
|
||||
('сокращенное наименование организации', 'shortName'),
|
||||
('адрес организации \\(юридический\\)', 'addressLegal'),
|
||||
('адрес организации \\(фактический\\)', 'addressActual'),
|
||||
('инн', 'inn'),
|
||||
('дата.*огрн', 'ogrnDate'),
|
||||
('огрн', 'ogrn'),
|
||||
('кпп', 'kpp'),
|
||||
('основной оквэд', 'okved'),
|
||||
('должность руководителя', 'chiefPosition'),
|
||||
('фио руководителя', 'chiefFio'),
|
||||
('должность.*защит.*информ|должность.*администрат', 'ispdnPosition'),
|
||||
('фио.*защит.*информ|фио.*администрат', 'ispdnFio'),
|
||||
('электронная почта.*защит|электронная почта.*ответств|электронная почта.*специалист', 'ispdnEmail'),
|
||||
('телефон.*защит|телефон.*ответств|телефон.*специалист', 'ispdnPhone'),
|
||||
('структур.*подраздел', 'ispdnDepartment'),
|
||||
('номер.*договора|номер.*контракта', 'contractNumber'),
|
||||
('дата.*договора|дата.*контракта', 'contractDate'),
|
||||
('названи.*документ', 'paperDocuments'),
|
||||
('место.*хранен', 'paperStorage'),
|
||||
('должность.*ответственн', 'responsiblePosition'),
|
||||
('фио.*ответственн', 'responsibleFio'),
|
||||
('должность.*администрат.*(?:безопас|защит|пд)', 'administratorPosition'),
|
||||
('фио.*администрат.*(?:безопас|защит|пд)', 'administratorFio'),
|
||||
('сайт организации', 'site_name'),
|
||||
('должность ответственного за сайт|должность.*отв.*сайт', 'site_responsible_position'),
|
||||
('фио.*отв.*сайт|фио.*сайт', 'site_responsible_fio'),
|
||||
('наименование организации.*разрабат.*сайт|наименование.*размещала.*сайт|разрабат.*сайт.*организац', 'site_service_provider'),
|
||||
('инн.*разрабат.*сайт|инн.*размещала.*сайт', 'site_service_provider_inn'),
|
||||
('размещение сайта|адрес расположения серверов|адрес.*сервер.*сайт', 'site_hosting'),
|
||||
('адрес.*хостинг|адрес.*располож.*сервер', 'site_hosting_address'),
|
||||
('электронная почта', 'email'),
|
||||
('телефон', 'phone'),
|
||||
]
|
||||
value_col = int(settings.get('value_column', 1))
|
||||
|
||||
# Кэш значений: для каждого поля ищем по строке один раз
|
||||
row_cache = {}
|
||||
for row in ws.iter_rows(min_row=1, values_only=True):
|
||||
field_raw = clean_value(row[0]) if row[0] is not None else ''
|
||||
field = field_raw.lower()
|
||||
value = clean_value(row[1]) if len(row) > 1 else ''
|
||||
if not field:
|
||||
if not field_raw:
|
||||
continue
|
||||
for keyword, key in field_map:
|
||||
if re.search(keyword, field):
|
||||
if value:
|
||||
data[key] = value
|
||||
log_fn(f" {keyword}: {value}")
|
||||
break
|
||||
row_cache[field_raw.lower()] = (
|
||||
clean_value(row[value_col]) if len(row) > value_col else ''
|
||||
)
|
||||
|
||||
for rule in settings.get('fields', []):
|
||||
key = rule.get('var', '')
|
||||
if not key:
|
||||
continue
|
||||
name = rule.get('name', key)
|
||||
cell_ref = rule.get('cell', '')
|
||||
keywords = rule.get('keywords', '')
|
||||
|
||||
# 1) Конкретная ячейка
|
||||
if cell_ref:
|
||||
sheet_part, coord = parse_cell_ref(cell_ref)
|
||||
target_ws = ws
|
||||
if sheet_part:
|
||||
for sn in wb.sheetnames:
|
||||
if sn.lower() == sheet_part.lower():
|
||||
target_ws = wb[sn]
|
||||
break
|
||||
val = clean_value(cell_value(target_ws, coord))
|
||||
if val:
|
||||
data[key] = val
|
||||
log_fn(f" {name} [ячейка {coord}]: {val}")
|
||||
continue
|
||||
|
||||
# 2) Поиск по ключевым словам
|
||||
if keywords:
|
||||
try:
|
||||
rx = re.compile(keywords, re.IGNORECASE)
|
||||
except re.error:
|
||||
rx = None
|
||||
if rx:
|
||||
for field_lower, val in row_cache.items():
|
||||
if rx.search(field_lower):
|
||||
if val:
|
||||
data[key] = val
|
||||
log_fn(f" {name} [по ключу]: {val}")
|
||||
break
|
||||
|
||||
|
||||
def _import_152_is_list(wb, data, log_fn):
|
||||
def _import_152_is_list(wb, data, settings, log_fn):
|
||||
"""Импорт информационных систем из остальных листов."""
|
||||
is_sheets = [sn for sn in wb.sheetnames if sn not in ('Общие сведения', 'Сотрудники')]
|
||||
exclude_kws = settings.get('is_sheet_exclude_keywords', ['общие сведения', 'сотрудники'])
|
||||
is_sheets = []
|
||||
for sn in wb.sheetnames:
|
||||
sn_lower = sn.lower()
|
||||
if any(kw in sn_lower for kw in exclude_kws):
|
||||
continue
|
||||
is_sheets.append(sn)
|
||||
if not is_sheets:
|
||||
log_fn("⚠ Листы ИС не найдены")
|
||||
return
|
||||
@@ -326,15 +347,16 @@ def _import_152_is_list(wb, data, log_fn):
|
||||
log_fn(f"\n✅ Всего импортировано ИС: {len(is_list)}")
|
||||
|
||||
|
||||
def _import_152_employees(wb, data, log_fn):
|
||||
"""Импорт комиссии и сотрудников из листа 'Сотрудники'."""
|
||||
def _import_152_employees(wb, data, settings, log_fn):
|
||||
"""Импорт комиссии и сотрудников из листа «Сотрудники»."""
|
||||
sheet_name = None
|
||||
sheet_kw = settings.get('employees_sheet_keyword', 'сотруд')
|
||||
for sn in wb.sheetnames:
|
||||
if 'сотруд' in sn.lower():
|
||||
if sheet_kw in sn.lower():
|
||||
sheet_name = sn
|
||||
break
|
||||
if not sheet_name:
|
||||
log_fn("⚠ Лист «Сотрудники» не найден")
|
||||
log_fn(f"⚠ Лист по ключу '{sheet_kw}' не найден")
|
||||
return
|
||||
|
||||
ws = wb[sheet_name]
|
||||
|
||||
Reference in New Issue
Block a user