Исходный код DokoGen (генератор документов 152-ФЗ)
This commit is contained in:
@@ -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 ''
|
||||
Reference in New Issue
Block a user