From 79952bf73532c35ed276b0ea1e4addbdbcb2e5a2 Mon Sep 17 00:00:00 2001 From: DokoGen Date: Mon, 3 Aug 2026 12:09:13 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B9=D0=BA=D0=B8=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=D0=B0?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D1=8B=20=D0=B2=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BA=D1=81=D1=82=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84?= =?UTF-8?q?=D0=B0=D0=B9=D0=BB-=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=D1=80=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - import_settings.json рядом с программой (создаётся автоматически): что импортировать (var), откуда (cell: 'Общие сведения'!B2 или keywords: регэксп поиска по колонке A) - importer.py читает словарь: сначала конкретная ячейка, потом ключевые слова - листы общих сведений/сотрудников/ИС тоже настраиваются (ключевые слова) - UI: меню «Генерация → Файл настроек импорта…» — выбор пути к словарю - при сборке .exe файл остаётся редактируемым без пересборки --- dokogen/import_settings.json | 209 +++++++++++++++++++++++++++++++++++ dokogen/import_settings.py | 174 +++++++++++++++++++++++++++++ dokogen/importer.py | 140 +++++++++++++---------- dokogen/ui.py | 21 +++- 4 files changed, 484 insertions(+), 60 deletions(-) create mode 100644 dokogen/import_settings.json create mode 100644 dokogen/import_settings.py diff --git a/dokogen/import_settings.json b/dokogen/import_settings.json new file mode 100644 index 0000000..a98c7cb --- /dev/null +++ b/dokogen/import_settings.json @@ -0,0 +1,209 @@ +{ + "common_sheet_keyword": "общие", + "employees_sheet_keyword": "сотруд", + "is_sheet_exclude_keywords": [ + "общие сведения", + "сотрудники" + ], + "value_column": 1, + "fields": [ + { + "name": "Полное наименование организации", + "var": "fullName", + "cell": "", + "keywords": "полное наименование организации" + }, + { + "name": "Сокращённое наименование организации", + "var": "shortName", + "cell": "", + "keywords": "сокращенное наименование организации" + }, + { + "name": "Юридический адрес", + "var": "addressLegal", + "cell": "", + "keywords": "адрес организации \\(юридический\\)" + }, + { + "name": "Фактический адрес", + "var": "addressActual", + "cell": "", + "keywords": "адрес организации \\(фактический\\)" + }, + { + "name": "ИНН", + "var": "inn", + "cell": "", + "keywords": "инн" + }, + { + "name": "Дата ОГРН", + "var": "ogrnDate", + "cell": "", + "keywords": "дата.*огрн" + }, + { + "name": "ОГРН", + "var": "ogrn", + "cell": "", + "keywords": "огрн" + }, + { + "name": "КПП", + "var": "kpp", + "cell": "", + "keywords": "кпп" + }, + { + "name": "Основной ОКВЭД", + "var": "okved", + "cell": "", + "keywords": "основной оквэд" + }, + { + "name": "Должность руководителя", + "var": "chiefPosition", + "cell": "", + "keywords": "должность руководителя" + }, + { + "name": "ФИО руководителя", + "var": "chiefFio", + "cell": "", + "keywords": "фио руководителя" + }, + { + "name": "Должность ответственного за ПДн", + "var": "ispdnPosition", + "cell": "", + "keywords": "должность.*защит.*информ|должность.*администрат" + }, + { + "name": "ФИО ответственного за ПДн", + "var": "ispdnFio", + "cell": "", + "keywords": "фио.*защит.*информ|фио.*администрат" + }, + { + "name": "Email ответственного за ПДн", + "var": "ispdnEmail", + "cell": "", + "keywords": "электронная почта.*защит|электронная почта.*ответств|электронная почта.*специалист" + }, + { + "name": "Телефон ответственного за ПДн", + "var": "ispdnPhone", + "cell": "", + "keywords": "телефон.*защит|телефон.*ответств|телефон.*специалист" + }, + { + "name": "Структурное подразделение по безопасности", + "var": "ispdnDepartment", + "cell": "", + "keywords": "структур.*подраздел" + }, + { + "name": "Номер договора", + "var": "contractNumber", + "cell": "", + "keywords": "номер.*договора|номер.*контракта" + }, + { + "name": "Дата договора", + "var": "contractDate", + "cell": "", + "keywords": "дата.*договора|дата.*контракта" + }, + { + "name": "Перечень бумажных документов", + "var": "paperDocuments", + "cell": "", + "keywords": "названи.*документ" + }, + { + "name": "Место хранения документов", + "var": "paperStorage", + "cell": "", + "keywords": "место.*хранен" + }, + { + "name": "Должность ответственного за обработку ПДн", + "var": "responsiblePosition", + "cell": "", + "keywords": "должность.*ответственн" + }, + { + "name": "ФИО ответственного за обработку ПДн", + "var": "responsibleFio", + "cell": "", + "keywords": "фио.*ответственн" + }, + { + "name": "Должность администратора безопасности", + "var": "administratorPosition", + "cell": "", + "keywords": "должность.*администрат.*(?:безопас|защит|пд)" + }, + { + "name": "ФИО администратора безопасности", + "var": "administratorFio", + "cell": "", + "keywords": "фио.*администрат.*(?:безопас|защит|пд)" + }, + { + "name": "Сайт организации", + "var": "site_name", + "cell": "", + "keywords": "сайт организации" + }, + { + "name": "Должность ответственного за сайт", + "var": "site_responsible_position", + "cell": "", + "keywords": "должность ответственного за сайт|должность.*отв.*сайт" + }, + { + "name": "ФИО ответственного за сайт", + "var": "site_responsible_fio", + "cell": "", + "keywords": "фио.*отв.*сайт|фио.*сайт" + }, + { + "name": "Разработчик сайта", + "var": "site_service_provider", + "cell": "", + "keywords": "наименование организации.*разрабат.*сайт|наименование.*размещала.*сайт|разрабат.*сайт.*организац" + }, + { + "name": "ИНН разработчика сайта", + "var": "site_service_provider_inn", + "cell": "", + "keywords": "инн.*разрабат.*сайт|инн.*размещала.*сайт" + }, + { + "name": "Размещение сайта (хостинг)", + "var": "site_hosting", + "cell": "", + "keywords": "размещение сайта|адрес расположения серверов|адрес.*сервер.*сайт" + }, + { + "name": "Адрес хостинга", + "var": "site_hosting_address", + "cell": "", + "keywords": "адрес.*хостинг|адрес.*располож.*сервер" + }, + { + "name": "Электронная почта", + "var": "email", + "cell": "", + "keywords": "электронная почта" + }, + { + "name": "Телефон", + "var": "phone", + "cell": "", + "keywords": "телефон" + } + ] +} \ No newline at end of file diff --git a/dokogen/import_settings.py b/dokogen/import_settings.py new file mode 100644 index 0000000..ca33475 --- /dev/null +++ b/dokogen/import_settings.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +"""DokoGen — настройки импорта из опросника (редактируемый словарь). + +Идея: «что импортировать и откуда» хранится НЕ в коде, а в текстовом +JSON-файле рядом с программой (import_settings.json). При сборке .exe +файл остаётся редактируемым — можно править пути к ячейкам и ключевые +слова без пересборки. + +Формат файла: +{ + "common_sheet_keyword": "общие", // ключевое слово листа «Общие сведения» + "employees_sheet_keyword": "сотруд", // ключевое слово листа «Сотрудники» + "is_sheet_exclude_keywords": ["общие сведения", "сотрудники"], // листы, которые НЕ ИС + "value_column": 1, // колонка значения (0=A, 1=B, ...) + "fields": [ + { + "name": "Полное наименование организации", // человекочитаемое название + "var": "fullName", // ключ в данных программы + "cell": "'Общие сведения'!B2", // (необязательно) конкретная ячейка + "keywords": "полное наименование организации" // (необязательно) регэксп поиска по колонке A + } + ] +} + +Порядок применения для каждого поля: +1) если задана cell и ячейка заполнена — берём значение оттуда; +2) иначе ищем строку, где колонка A совпадает с keywords (регэксп), + и берём значение из колонки value_column. +""" + +import os +import re +import sys +import json + +# ============================================================ +# ПУТЬ К ФАЙЛУ НАСТРОЕК ПО УМОЛЧАНИЮ +# ============================================================ +def default_import_settings_path(): + """Путь к файлу настроек: рядом с exe (frozen) или рядом с модулем.""" + if getattr(sys, 'frozen', False): + base = os.path.dirname(sys.executable) + else: + base = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(base, 'import_settings.json') + + +# ============================================================ +# ДЕФОЛТНЫЙ СЛОВАРЬ (используется, если файл не найден) +# ============================================================ +def _f(name, var, cell='', keywords=''): + return {'name': name, 'var': var, 'cell': cell, 'keywords': keywords} + + +DEFAULT_SETTINGS = { + 'common_sheet_keyword': 'общие', + 'employees_sheet_keyword': 'сотруд', + 'is_sheet_exclude_keywords': ['общие сведения', 'сотрудники'], + 'value_column': 1, + 'fields': [ + _f('Полное наименование организации', 'fullName', keywords=r'полное наименование организации'), + _f('Сокращённое наименование организации', 'shortName', keywords=r'сокращенное наименование организации'), + _f('Юридический адрес', 'addressLegal', keywords=r'адрес организации \(юридический\)'), + _f('Фактический адрес', 'addressActual', keywords=r'адрес организации \(фактический\)'), + _f('ИНН', 'inn', keywords=r'инн'), + _f('Дата ОГРН', 'ogrnDate', keywords=r'дата.*огрн'), + _f('ОГРН', 'ogrn', keywords=r'огрн'), + _f('КПП', 'kpp', keywords=r'кпп'), + _f('Основной ОКВЭД', 'okved', keywords=r'основной оквэд'), + _f('Должность руководителя', 'chiefPosition', keywords=r'должность руководителя'), + _f('ФИО руководителя', 'chiefFio', keywords=r'фио руководителя'), + _f('Должность ответственного за ПДн', 'ispdnPosition', keywords=r'должность.*защит.*информ|должность.*администрат'), + _f('ФИО ответственного за ПДн', 'ispdnFio', keywords=r'фио.*защит.*информ|фио.*администрат'), + _f('Email ответственного за ПДн', 'ispdnEmail', keywords=r'электронная почта.*защит|электронная почта.*ответств|электронная почта.*специалист'), + _f('Телефон ответственного за ПДн', 'ispdnPhone', keywords=r'телефон.*защит|телефон.*ответств|телефон.*специалист'), + _f('Структурное подразделение по безопасности', 'ispdnDepartment', keywords=r'структур.*подраздел'), + _f('Номер договора', 'contractNumber', keywords=r'номер.*договора|номер.*контракта'), + _f('Дата договора', 'contractDate', keywords=r'дата.*договора|дата.*контракта'), + _f('Перечень бумажных документов', 'paperDocuments', keywords=r'названи.*документ'), + _f('Место хранения документов', 'paperStorage', keywords=r'место.*хранен'), + _f('Должность ответственного за обработку ПДн', 'responsiblePosition', keywords=r'должность.*ответственн'), + _f('ФИО ответственного за обработку ПДн', 'responsibleFio', keywords=r'фио.*ответственн'), + _f('Должность администратора безопасности', 'administratorPosition', keywords=r'должность.*администрат.*(?:безопас|защит|пд)'), + _f('ФИО администратора безопасности', 'administratorFio', keywords=r'фио.*администрат.*(?:безопас|защит|пд)'), + _f('Сайт организации', 'site_name', keywords=r'сайт организации'), + _f('Должность ответственного за сайт', 'site_responsible_position', keywords=r'должность ответственного за сайт|должность.*отв.*сайт'), + _f('ФИО ответственного за сайт', 'site_responsible_fio', keywords=r'фио.*отв.*сайт|фио.*сайт'), + _f('Разработчик сайта', 'site_service_provider', keywords=r'наименование организации.*разрабат.*сайт|наименование.*размещала.*сайт|разрабат.*сайт.*организац'), + _f('ИНН разработчика сайта', 'site_service_provider_inn', keywords=r'инн.*разрабат.*сайт|инн.*размещала.*сайт'), + _f('Размещение сайта (хостинг)', 'site_hosting', keywords=r'размещение сайта|адрес расположения серверов|адрес.*сервер.*сайт'), + _f('Адрес хостинга', 'site_hosting_address', keywords=r'адрес.*хостинг|адрес.*располож.*сервер'), + _f('Электронная почта', 'email', keywords=r'электронная почта'), + _f('Телефон', 'phone', keywords=r'телефон'), + ], +} + + +# ============================================================ +# ЗАГРУЗКА НАСТРОЕК +# ============================================================ +def load_settings(path=None): + """Загрузка словаря импорта из JSON-файла. + + path=None -> файл по умолчанию рядом с программой. + Если файла нет или он битый — возвращается встроенный дефолт. + """ + if path is None: + path = default_import_settings_path() + + if not path or not os.path.exists(path): + return DEFAULT_SETTINGS + + try: + with open(path, encoding='utf-8-sig') as f: + loaded = json.load(f) + # Дополняем недостающие ключи из дефолта + merged = dict(DEFAULT_SETTINGS) + for k, v in loaded.items(): + if k == 'fields' and isinstance(v, list): + merged['fields'] = v + else: + merged[k] = v + return merged + except Exception: + return DEFAULT_SETTINGS + + +def ensure_default_file(path=None): + """Создаёт файл настроек import_settings.json, если его ещё нет. + + Возвращает путь к файлу. Нужно, чтобы пользователь видел словарь + импорта и мог править его в блокноте без пересборки программы. + """ + if path is None: + path = default_import_settings_path() + if os.path.exists(path): + return path + try: + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, 'w', encoding='utf-8') as f: + json.dump(DEFAULT_SETTINGS, f, ensure_ascii=False, indent=2) + except Exception: + pass + return path + + +def parse_cell_ref(cell_ref): + """Разбор ссылки на ячейку: 'Общие сведения'!B2 / Общие сведения!B2 / B2. + + Возвращает (sheet_name или None, координаты 'B2'). + """ + if not cell_ref: + return None, '' + ref = cell_ref.strip() + if ref.startswith('='): + ref = ref[1:].strip() + if '!' in ref: + sheet_part, coord = ref.split('!', 1) + sheet_part = sheet_part.strip() + if sheet_part.startswith("'") and sheet_part.endswith("'"): + sheet_part = sheet_part[1:-1] + return sheet_part or None, coord.strip().upper() + return None, ref.upper() + + +def cell_value(ws, coord): + """Значение ячейки по координате 'B2' (с нормализацией).""" + if not coord: + return '' + try: + val = ws[coord] + return val.value if val is not None else '' + except Exception: + return '' diff --git a/dokogen/importer.py b/dokogen/importer.py index cb79c85..a13c3f4 100644 --- a/dokogen/importer.py +++ b/dokogen/importer.py @@ -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] diff --git a/dokogen/ui.py b/dokogen/ui.py index e4a0919..b0a4c2d 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -38,6 +38,10 @@ class DokoGenApp: '.autosave', 'autosave_152.json' ) + # Путь к файлу настроек импорта (словарь «что и откуда брать») + # None = import_settings.json рядом с программой (создаётся автоматически) + self.import_settings_path: Optional[str] = None + # Переменные self.doc_number_var = tk.StringVar(value="") self.doc_date_var = tk.StringVar(value="") @@ -83,6 +87,7 @@ class DokoGenApp: gen_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="Генерация", menu=gen_menu) gen_menu.add_command(label="Импорт из опросника", command=self.import_from_survey) + gen_menu.add_command(label="Файл настроек импорта…", command=self.select_import_settings) gen_menu.add_separator() gen_menu.add_command(label="Сгенерировать всё", command=self.generate_all) @@ -1267,6 +1272,20 @@ class DokoGenApp: self.log("✅ Склонения краткого названия обновлены") # ==================== ИМПОРТ ==================== + def select_import_settings(self): + """Выбор файла настроек импорта (JSON-словарь).""" + from .import_settings import default_import_settings_path, ensure_default_file + path = filedialog.askopenfilename( + title="Файл настроек импорта (что и откуда брать)", + filetypes=[("Настройки импорта", "*.json")] + ) + if not path: + self.log(f"Настройки импорта: по умолчанию ({default_import_settings_path()})") + self.import_settings_path = None + return + self.import_settings_path = path + self.log(f"Настройки импорта: {path}") + def import_from_survey(self): path = filedialog.askopenfilename( title="Выберите опросный лист (152-ФЗ)", @@ -1280,7 +1299,7 @@ class DokoGenApp: def _run(): try: from .importer import import_152fz - data = import_152fz(path, self.log) + data = import_152fz(path, self.log, settings_path=self.import_settings_path) if data: self._apply_imported_data(data) except ImportError: