From 2037925986cbbb772ca5584cea77de6ab553a2ba Mon Sep 17 00:00:00 2001 From: prog1764 Date: Tue, 4 Aug 2026 14:42:33 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=9E=D0=9A=D0=98=D0=98=20=E2=80=94=20?= =?UTF-8?q?=D0=9E=D0=9A=D0=92=D0=AD=D0=94=20(=D0=B8=D0=BC=D0=BF=D0=BE?= =?UTF-8?q?=D1=80=D1=82+DaData),=20=D0=B4=D0=BE=D0=BC=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=B8=D0=BC=D1=8F,=20=D0=BF=D0=BE=D0=BB=D1=8F?= =?UTF-8?q?=20236=20=D0=BF=D1=80=D0=B8=D0=BA=D0=B0=D0=B7=D0=B0=20=D0=A4?= =?UTF-8?q?=D0=A1=D0=A2=D0=AD=D0=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ОКВЭД: починен импорт (split по запятым/точкам с запятой), добавлен okvedExtra и domainName в DEFAULT_SETTINGS + merge полей по var в load_settings (старый import_settings.json теперь дополняется) - DaData: проверка и подстановка основного/доп. ОКВЭД - Вкладка «Организация»: поле «Доменное имя и внешний IP» + импорт из опросника - Вкладка «Объекты КИИ»: поля 236 приказа — сведения (назначение, сфера, тип, архитектура, типовой отраслевой объект), субъект (12 полей), сети и ПО (категория сети, оператор, цель/способ взаимодействия, ПО/аппаратные средства, ОС, прикладные), безопасность (нарушитель, угрозы, инциденты, показатели критериев, обоснование, орг./тех. меры) - importer: распознавание полей 236 из опросника, variables: новые переменные шаблонов --- dokogen/help_docs.py | 6 +- dokogen/import_settings.py | 10 +- dokogen/importer.py | 146 ++++++++++++++++++++- dokogen/models.py | 43 ++++++- dokogen/ui.py | 256 ++++++++++++++++++++++++++++++++++++- dokogen/variables.py | 43 +++++++ dokogen/verify.py | 12 ++ 7 files changed, 502 insertions(+), 14 deletions(-) diff --git a/dokogen/help_docs.py b/dokogen/help_docs.py index 4fb930f..6fd98ff 100644 --- a/dokogen/help_docs.py +++ b/dokogen/help_docs.py @@ -97,7 +97,7 @@ def _generate_variables_reference(path): _bullet(doc, f'{var} — {desc}') _h(doc, 'Циклы', 12) - _bullet(doc, 'information_systems — объекты КИИ ({{item.name}}, {{item.description}}, {{item.category}}, {{item.defense_tools_list}}, {{item.users}}, {{item.software}} и др.).') + _bullet(doc, 'information_systems — объекты КИИ ({{item.name}}, {{item.description}}, {{item.category}}, {{item.purpose}}, {{item.sphere}}, {{item.object_type}}, {{item.architecture}}, {{item.net_category}}, {{item.hw_sw}}, {{item.os_sw}}, {{item.app_sw}}, {{item.defense_tools_list}}, {{item.users}} и др.).') _bullet(doc, 'commission — комиссия ({{item.role}}, {{item.position}}, {{item.fio}}, {{item.fio_initials}}, {{item.fio_short}}, {{item.signature}}).') _bullet(doc, 'employees_access — сотрудники с доступом ({{item.position}}, {{item.fio}}).') @@ -140,7 +140,7 @@ def _generate_user_manual(path): 'сотрудника или объекта КИИ — используются маркеры цикла:') _p(doc, ' ... {{item.name}} ... ', bold=True) _p(doc, 'Доступные циклы:') - _bullet(doc, 'information_systems — объекты КИИ ({{item.name}}, {{item.description}}, {{item.category}}, {{item.defense_tools_list}}, {{item.users}}, {{item.software}} и др.).') + _bullet(doc, 'information_systems — объекты КИИ ({{item.name}}, {{item.description}}, {{item.category}}, {{item.purpose}}, {{item.sphere}}, {{item.object_type}}, {{item.architecture}}, {{item.net_category}}, {{item.hw_sw}}, {{item.os_sw}}, {{item.app_sw}}, {{item.defense_tools_list}}, {{item.users}} и др.).') _bullet(doc, 'commission — комиссия ({{item.role}}, {{item.position}}, {{item.fio}}, {{item.fio_initials}}, {{item.fio_short}}, {{item.signature}}).') _bullet(doc, 'employees_access — сотрудники с доступом ({{item.position}}, {{item.fio}}).') @@ -179,7 +179,7 @@ def _generate_admin_manual(path): _h(doc, '1. Файлы настроек рядом с программой', 12) _bullet(doc, 'api_settings.json — API-ключи (DaData, ИИ).') _bullet(doc, 'import_settings.json — словарь импорта: что и откуда брать из опросника.') - _bullet(doc, 'dictionaries/ — справочники: defense_tools.json, okved.json.') + _bullet(doc, 'dictionaries/ — справочники: defense_tools.json, okved.json (словарь ОКВЭД).') _bullet(doc, 'dokogen.log — журнал работы (пишется автоматически).') _bullet(doc, 'docs/ — инструкции и пустой опросник (создаются автоматически).') diff --git a/dokogen/import_settings.py b/dokogen/import_settings.py index ca33475..781ce44 100644 --- a/dokogen/import_settings.py +++ b/dokogen/import_settings.py @@ -67,6 +67,8 @@ DEFAULT_SETTINGS = { _f('ОГРН', 'ogrn', keywords=r'огрн'), _f('КПП', 'kpp', keywords=r'кпп'), _f('Основной ОКВЭД', 'okved', keywords=r'основной оквэд'), + _f('Дополнительные ОКВЭД', 'okvedExtra', keywords=r'дополнительные оквэд'), + _f('Доменное имя и внешний сетевой адрес', 'domainName', keywords=r'доменное имя и внешний сетевой адрес'), _f('Должность руководителя', 'chiefPosition', keywords=r'должность руководителя'), _f('ФИО руководителя', 'chiefFio', keywords=r'фио руководителя'), _f('Должность ответственного за ПДн', 'ispdnPosition', keywords=r'должность.*защит.*информ|должность.*администрат'), @@ -117,7 +119,13 @@ def load_settings(path=None): merged = dict(DEFAULT_SETTINGS) for k, v in loaded.items(): if k == 'fields' and isinstance(v, list): - merged['fields'] = v + # Мержим поля по var: поля из файла + недостающие из дефолта + by_var = {f.get('var'): f for f in v} + for df in DEFAULT_SETTINGS.get('fields', []): + dv = df.get('var') + if dv and dv not in by_var: + by_var[dv] = df + merged['fields'] = list(by_var.values()) else: merged[k] = v return merged diff --git a/dokogen/importer.py b/dokogen/importer.py index 89fa9d7..67078e6 100644 --- a/dokogen/importer.py +++ b/dokogen/importer.py @@ -248,7 +248,11 @@ def _import_187_common(wb, data, settings, log_fn): def _import_187_is_list(wb, data, settings, log_fn): - """Импорт объектов КИИ из остальных листов (ОБЪЕКТ №1, №2, ...).""" + """Импорт объектов КИИ из остальных листов (ОБЪЕКТ №1, №2, ...). + + Заполняет поля 236 приказа ФСТЭК: сфера, назначение, тип, архитектура, + сети электросвязи, ПО, СЗИ, сведения о субъекте. + """ exclude_kws = settings.get('is_sheet_exclude_keywords', ['общие сведения', 'сотрудники']) is_sheets = [] for sn in wb.sheetnames: @@ -277,6 +281,20 @@ def _import_187_is_list(wb, data, settings, log_fn): is_obj = { 'name': '', 'description': '', 'usersList': [], 'isInternet': False, 'defense_tools': [], 'software': '', + # 236 приказ + 'sphere': '', 'purpose': '', 'object_type': '', 'architecture': '', + 'typical_object': '', + 'subject_name': '', 'subject_address': '', + 'subject_chief_position': '', 'subject_chief_fio': '', + 'subject_security_position': '', 'subject_security_fio': '', + 'subject_department': '', 'subject_department_chief': '', + 'subject_phone': '', 'subject_email': '', + 'subject_inn': '', 'subject_kpp': '', + 'net_category': '', 'net_operator': '', 'net_purpose': '', 'net_method': '', + 'hw_sw': '', 'os_sw': '', 'app_sw': '', + 'attacker_category': '', 'threats': '', 'incidents': '', + 'criteria_values': '', 'criteria_justification': '', + 'org_measures': '', 'tech_measures': '', } defense_keywords = { @@ -290,23 +308,137 @@ def _import_187_is_list(wb, data, settings, log_fn): 'гарантирован': 'Доверенная загрузка', } + def _set(field_lower, key, val): + """Записать значение, если ключевое слово найдено в вопросе.""" + if val and 'выбрать' not in field_lower: + is_obj[key] = val + return True + return False + for vals in rows: field = (vals[0] or '').strip() field_lower = field.lower() val_b = clean_value(vals[1]) if len(vals) > 1 else '' is_checked = val_b.upper() in ('ДА', 'YES', '✓', '✔', '1', 'TRUE', 'ЕСТЬ') + # --- Наименование объекта --- if 'наименование объекта' in field_lower or 'название объекта' in field_lower: is_obj['name'] = val_b log_fn(f" Объект КИИ: {val_b}") continue - if 'описание объекта' in field_lower or 'описание окки' in field_lower: + # --- Описание --- + if 'описание окки' in field_lower or 'описание объекта' in field_lower: if val_b: is_obj['description'] = val_b continue - if 'операционная' in field_lower or 'прикладные программы' in field_lower or 'наименования программно' in field_lower: - if val_b: - is_obj['software'] = (is_obj['software'] + '; ' + val_b) if is_obj['software'] else val_b + # --- Сфера деятельности --- + if 'сфера' in field_lower and 'деятельност' in field_lower: + if _set(field_lower, 'sphere', val_b): + log_fn(f" Сфера: {val_b[:60]}") + continue + # --- Назначение объекта --- + if 'назначение объекта' in field_lower: + if _set(field_lower, 'purpose', val_b): + log_fn(f" Назначение: {val_b[:60]}") + continue + # --- Тип объекта --- + if 'тип объекта' in field_lower: + if _set(field_lower, 'object_type', val_b): + log_fn(f" Тип объекта: {val_b[:60]}") + continue + # --- Архитектура --- + if 'архитектура' in field_lower: + if _set(field_lower, 'architecture', val_b): + log_fn(f" Архитектура: {val_b[:60]}") + continue + # --- Типовой отраслевой объект --- + if 'типового отраслевого' in field_lower or 'типовой отраслевой' in field_lower: + if _set(field_lower, 'typical_object', val_b): + log_fn(f" Типовой объект: {val_b[:60]}") + continue + # --- Субъект --- + if 'наименование организации эксплуатирующ' in field_lower or ('наименование субъекта' in field_lower): + if _set(field_lower, 'subject_name', val_b): + log_fn(f" Субъект: {val_b[:60]}") + continue + if 'адрес местонахождения' in field_lower: + if _set(field_lower, 'subject_address', val_b): + log_fn(f" Адрес субъекта: {val_b[:60]}") + continue + if 'инн лица, эксплуатирующего' in field_lower or 'инн субъекта' in field_lower: + if _set(field_lower, 'subject_inn', val_b): + log_fn(f" ИНН субъекта: {val_b[:60]}") + continue + if 'кпп его обособленных' in field_lower or 'кпп субъекта' in field_lower: + if _set(field_lower, 'subject_kpp', val_b): + log_fn(f" КПП субъекта: {val_b[:60]}") + continue + if 'элемент (компонент) объекта, который эксплуатируется' in field_lower: + if _set(field_lower, 'subject_department', val_b): + log_fn(f" Элемент объекта: {val_b[:60]}") + continue + # --- Сети электросвязи --- + if 'категория сети электросвязи' in field_lower: + if _set(field_lower, 'net_category', val_b): + log_fn(f" Категория сети: {val_b[:60]}") + continue + if 'наименование провайдера' in field_lower or 'оператор связи' in field_lower: + if _set(field_lower, 'net_operator', val_b): + log_fn(f" Оператор: {val_b[:60]}") + continue + if 'цель взаимодействия' in field_lower: + if _set(field_lower, 'net_purpose', val_b): + log_fn(f" Цель взаимодействия: {val_b[:60]}") + continue + if 'способ взаимодействия' in field_lower: + if _set(field_lower, 'net_method', val_b): + log_fn(f" Способ взаимодействия: {val_b[:60]}") + continue + # --- ПО и аппаратные средства --- + if 'наименования программно-аппаратных средств' in field_lower: + if _set(field_lower, 'hw_sw', val_b): + log_fn(f" ПО/аппаратные средства: {val_b[:60]}") + continue + if 'операционная' in field_lower and 'система' in field_lower: + if _set(field_lower, 'os_sw', val_b): + log_fn(f" ОС: {val_b[:60]}") + continue + if 'прикладные программы' in field_lower: + if _set(field_lower, 'app_sw', val_b): + log_fn(f" Прикладные программы: {val_b[:60]}") + continue + if 'общесистемн' in field_lower: + if _set(field_lower, 'os_sw', val_b): + log_fn(f" Общесистемное ПО: {val_b[:60]}") + continue + # --- Безопасность / 236 приказ --- + if 'категория нарушителя' in field_lower: + if _set(field_lower, 'attacker_category', val_b): + log_fn(f" Категория нарушителя: {val_b[:60]}") + continue + if 'основные угрозы' in field_lower or 'угрозы безопасности' in field_lower: + if _set(field_lower, 'threats', val_b): + log_fn(f" Угрозы: {val_b[:60]}") + continue + if 'компьютерных инцидентов' in field_lower or 'инцидент' in field_lower: + if _set(field_lower, 'incidents', val_b): + log_fn(f" Инциденты: {val_b[:60]}") + continue + if 'показателей критериев' in field_lower or 'показатели критериев' in field_lower: + if _set(field_lower, 'criteria_values', val_b): + log_fn(f" Показатели: {val_b[:60]}") + continue + if 'обоснование полученных' in field_lower or 'обоснование значений' in field_lower: + if _set(field_lower, 'criteria_justification', val_b): + log_fn(f" Обоснование: {val_b[:60]}") + continue + if 'организационные меры' in field_lower: + if _set(field_lower, 'org_measures', val_b): + log_fn(f" Орг. меры: {val_b[:60]}") + continue + if 'технические меры' in field_lower: + if _set(field_lower, 'tech_measures', val_b): + log_fn(f" Тех. меры: {val_b[:60]}") continue if 'интернет' in field_lower: is_obj['isInternet'] = is_checked @@ -321,7 +453,7 @@ def _import_187_is_list(wb, data, settings, log_fn): if cleaned and cleaned not in is_obj['usersList']: is_obj['usersList'].append(cleaned) continue - # Средства защиты + # --- Средства защиты --- matched = False for kw, label in defense_keywords.items(): if re.search(kw, field_lower): @@ -332,7 +464,7 @@ def _import_187_is_list(wb, data, settings, log_fn): if matched: continue if val_b and 'выбрать' not in field_lower and field_lower not in ('значение', 'примечание'): - # Остальные заполненные поля — в описание (адрес, сфера, тип и т.д.) + # Остальные заполненные поля — в описание (адрес, балансовая стоимость и т.д.) if not is_obj['description']: is_obj['description'] = f"{field}: {val_b}" else: diff --git a/dokogen/models.py b/dokogen/models.py index 36ebfcb..d046520 100644 --- a/dokogen/models.py +++ b/dokogen/models.py @@ -23,10 +23,50 @@ class InformationSystem: category: str = "" # Категория значимости объекта КИИ: 1 / 2 / 3 / без категории users_list: List[str] = field(default_factory=list) # Пользователи ОКИИ defense_tools_list: List[str] = field(default_factory=list) # Средства защиты - purpose: str = "" + purpose: str = "" # Назначение объекта (п. 236) structure: str = "" # Структура объекта КИИ software: str = "" # Программное обеспечение + # ---- 236 приказ ФСТЭК: сведения об объекте ---- + sphere: str = "" # Сфера (область) деятельности + object_type: str = "" # Тип объекта (ИС, АСУ, ИТКС) + architecture: str = "" # Архитектура объекта + typical_object: str = "" # Наименование типового отраслевого объекта КИИ + + # Субъект (владелец объекта) + subject_name: str = "" # Наименование субъекта + subject_address: str = "" # Адрес местонахождения субъекта + subject_chief_position: str = "" # Должность руководителя субъекта + subject_chief_fio: str = "" # ФИО руководителя субъекта + subject_security_position: str = "" # Должность ответственного за безопасность ЗОКИИ + subject_security_fio: str = "" # ФИО ответственного за безопасность ЗОКИИ + subject_department: str = "" # Структурное подразделение по безопасности + subject_department_chief: str = "" # Руководитель структурного подразделения + subject_phone: str = "" # Телефон + subject_email: str = "" # Адрес электронной почты + subject_inn: str = "" # ИНН субъекта + subject_kpp: str = "" # КПП обособленных подразделений + + # Сети электросвязи + net_category: str = "" # Категория сети электросвязи + net_operator: str = "" # Оператор связи и (или) провайдер хостинга + net_purpose: str = "" # Цель взаимодействия с сетью электросвязи + net_method: str = "" # Способ взаимодействия с указанием типа доступа + + # Программно-аппаратные средства + hw_sw: str = "" # Наименования программно-аппаратных средств и их количество + os_sw: str = "" # Общесистемное ПО (ОС, средства виртуализации) + app_sw: str = "" # Прикладные программы + + # Безопасность (236 приказ) + attacker_category: str = "" # Категория нарушителя (внешний/внутренний) + threats: str = "" # Основные угрозы безопасности информации + incidents: str = "" # Типы компьютерных инцидентов + criteria_values: str = "" # Полученные значения по показателям критериев значимости + criteria_justification: str = "" # Обоснование полученных значений + org_measures: str = "" # Организационные меры + tech_measures: str = "" # Технические меры + @dataclass class Company: @@ -58,6 +98,7 @@ class Company: kpp: str = "" okved: str = "" # Основной ОКВЭД okved_extra: List[str] = field(default_factory=list) # Дополнительные ОКВЭД + domain_name: str = "" # Доменное имя и внешний сетевой адрес # Должностные лица chief_position: str = "" diff --git a/dokogen/ui.py b/dokogen/ui.py index 56881b7..0a5283a 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -255,6 +255,7 @@ class DokoGenApp: ("КПП:", "kpp"), ("ОГРН:", "ogrn"), ("Дата ОГРН:", "ogrn_date"), + ("Доменное имя и внешний IP:", "domain_name"), ] for i, (label, key) in enumerate(addr_fields): ttk.Label(f2, text=label).grid(row=i, column=0, sticky=tk.W, pady=2) @@ -589,9 +590,12 @@ class DokoGenApp: for key, label in [('full_name', 'Наименование'), ('short_name', 'Краткое'), ('address', 'Адрес'), ('inn', 'ИНН'), ('kpp', 'КПП'), ('ogrn', 'ОГРН'), ('ogrn_date', 'Дата ОГРН'), + ('okved', 'Основной ОКВЭД'), ('chief_position', 'Руководитель'), ('chief_fio', 'ФИО')]: if d.get(key): lines.append(f"{label}: {d[key]}") + if d.get('okved_extra'): + lines.append(f"Доп. ОКВЭД: {', '.join(d['okved_extra'])}") else: # ИИ-рецензия: показываем снимок полей (что ИИ увидел), замечания и предложения lines.append(f"Оценка: {d.get('rating', '')}") @@ -700,6 +704,15 @@ class DokoGenApp: if comp.get(data_key) and ui_key in self._company_entries: self._company_entries[ui_key].delete(0, tk.END) self._company_entries[ui_key].insert(0, comp[data_key]) + # ОКВЭД из DaData + if comp.get('okved') and 'okved' in self._company_entries: + self._company_entries['okved'].delete(0, tk.END) + self._company_entries['okved'].insert(0, comp['okved']) + if comp.get('okved_extra') and hasattr(self, 'okved_extra_listbox'): + self.okved_extra_listbox.delete(0, tk.END) + for code in comp['okved_extra']: + if str(code).strip(): + self.okved_extra_listbox.insert(tk.END, str(code).strip()) if want('chief'): if comp.get('chief_position') and 'chief_position' in self._official_entries: self._official_entries['chief_position'].delete(0, tk.END) @@ -772,6 +785,93 @@ class DokoGenApp: nb2 = ttk.Notebook(self._is_edit_panel) nb2.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + # ===== Вкладка «Сведения (236 приказ)» ===== + info_tab = ttk.Frame(nb2) + nb2.add(info_tab, text="Сведения (236)") + info_frame = ttk.Frame(info_tab, padding=8) + info_frame.pack(fill=tk.BOTH, expand=True) + info_fields = [ + ("Назначение объекта:", "is_purpose_entry"), + ("Сфера деятельности:", "is_sphere_entry"), + ("Тип объекта:", "is_object_type_entry"), + ("Архитектура объекта:", "is_architecture_entry"), + ("Типовой отраслевой объект КИИ:", "is_typical_object_entry"), + ] + for i, (label, attr) in enumerate(info_fields): + ttk.Label(info_frame, text=label).grid(row=i, column=0, sticky=tk.W, pady=2) + entry = ttk.Entry(info_frame, width=70) + entry.grid(row=i, column=1, padx=8, sticky=tk.EW, pady=1) + setattr(self, attr, entry) + info_frame.columnconfigure(1, weight=1) + + # ===== Вкладка «Субъект» ===== + subj_tab = ttk.Frame(nb2) + nb2.add(subj_tab, text="Субъект") + subj_frame = ttk.Frame(subj_tab, padding=8) + subj_frame.pack(fill=tk.BOTH, expand=True) + subj_fields = [ + ("Наименование субъекта:", "is_subject_name_entry"), + ("Адрес местонахождения:", "is_subject_address_entry"), + ("Должность руководителя:", "is_subject_chief_position_entry"), + ("ФИО руководителя:", "is_subject_chief_fio_entry"), + ("Должность отв. за безопасность:", "is_subject_security_position_entry"), + ("ФИО отв. за безопасность:", "is_subject_security_fio_entry"), + ("Структурное подразделение:", "is_subject_department_entry"), + ("Руководитель подразделения:", "is_subject_department_chief_entry"), + ("Телефон:", "is_subject_phone_entry"), + ("Эл. почта:", "is_subject_email_entry"), + ("ИНН субъекта:", "is_subject_inn_entry"), + ("КПП обособленных подразделений:", "is_subject_kpp_entry"), + ] + for i, (label, attr) in enumerate(subj_fields): + ttk.Label(subj_frame, text=label).grid(row=i, column=0, sticky=tk.W, pady=2) + entry = ttk.Entry(subj_frame, width=70) + entry.grid(row=i, column=1, padx=8, sticky=tk.EW, pady=1) + setattr(self, attr, entry) + subj_frame.columnconfigure(1, weight=1) + + # ===== Вкладка «Сети и ПО» ===== + net_tab = ttk.Frame(nb2) + nb2.add(net_tab, text="Сети и ПО") + net_frame = ttk.Frame(net_tab, padding=8) + net_frame.pack(fill=tk.BOTH, expand=True) + net_fields = [ + ("Категория сети электросвязи:", "is_net_category_entry"), + ("Оператор связи/провайдер хостинга:", "is_net_operator_entry"), + ("Цель взаимодействия с сетью:", "is_net_purpose_entry"), + ("Способ взаимодействия/тип доступа:", "is_net_method_entry"), + ("ПО и аппаратные средства:", "is_hw_sw_entry"), + ("Общесистемное ПО (ОС, виртуализация):", "is_os_sw_entry"), + ("Прикладные программы:", "is_app_sw_entry"), + ] + for i, (label, attr) in enumerate(net_fields): + ttk.Label(net_frame, text=label).grid(row=i, column=0, sticky=tk.W, pady=2) + entry = ttk.Entry(net_frame, width=70) + entry.grid(row=i, column=1, padx=8, sticky=tk.EW, pady=1) + setattr(self, attr, entry) + net_frame.columnconfigure(1, weight=1) + + # ===== Вкладка «Безопасность (236)» ===== + sec_tab = ttk.Frame(nb2) + nb2.add(sec_tab, text="Безопасность (236)") + sec_frame = ttk.Frame(sec_tab, padding=8) + sec_frame.pack(fill=tk.BOTH, expand=True) + sec_fields = [ + ("Категория нарушителя:", "is_attacker_category_entry"), + ("Основные угрозы БИ:", "is_threats_entry"), + ("Типы компьютерных инцидентов:", "is_incidents_entry"), + ("Показатели критериев значимости:", "is_criteria_values_entry"), + ("Обоснование значений показателей:", "is_criteria_justification_entry"), + ("Организационные меры:", "is_org_measures_entry"), + ("Технические меры:", "is_tech_measures_entry"), + ] + for i, (label, attr) in enumerate(sec_fields): + ttk.Label(sec_frame, text=label).grid(row=i, column=0, sticky=tk.W, pady=2) + entry = ttk.Entry(sec_frame, width=70) + entry.grid(row=i, column=1, padx=8, sticky=tk.EW, pady=1) + setattr(self, attr, entry) + sec_frame.columnconfigure(1, weight=1) + # СЗИ def_tab = ttk.Frame(nb2) nb2.add(def_tab, text="Средства защиты") @@ -1021,6 +1121,43 @@ class DokoGenApp: isys.is_local_network = self.is_lan_var.get() isys.is_internet = self.is_internet_var.get() isys.category = self.is_category_var.get() + # Поля 236 приказа + for attr, model_field in [ + ('is_purpose_entry', 'purpose'), + ('is_sphere_entry', 'sphere'), + ('is_object_type_entry', 'object_type'), + ('is_architecture_entry', 'architecture'), + ('is_typical_object_entry', 'typical_object'), + ('is_subject_name_entry', 'subject_name'), + ('is_subject_address_entry', 'subject_address'), + ('is_subject_chief_position_entry', 'subject_chief_position'), + ('is_subject_chief_fio_entry', 'subject_chief_fio'), + ('is_subject_security_position_entry', 'subject_security_position'), + ('is_subject_security_fio_entry', 'subject_security_fio'), + ('is_subject_department_entry', 'subject_department'), + ('is_subject_department_chief_entry', 'subject_department_chief'), + ('is_subject_phone_entry', 'subject_phone'), + ('is_subject_email_entry', 'subject_email'), + ('is_subject_inn_entry', 'subject_inn'), + ('is_subject_kpp_entry', 'subject_kpp'), + ('is_net_category_entry', 'net_category'), + ('is_net_operator_entry', 'net_operator'), + ('is_net_purpose_entry', 'net_purpose'), + ('is_net_method_entry', 'net_method'), + ('is_hw_sw_entry', 'hw_sw'), + ('is_os_sw_entry', 'os_sw'), + ('is_app_sw_entry', 'app_sw'), + ('is_attacker_category_entry', 'attacker_category'), + ('is_threats_entry', 'threats'), + ('is_incidents_entry', 'incidents'), + ('is_criteria_values_entry', 'criteria_values'), + ('is_criteria_justification_entry', 'criteria_justification'), + ('is_org_measures_entry', 'org_measures'), + ('is_tech_measures_entry', 'tech_measures'), + ]: + w = getattr(self, attr, None) + if w is not None: + setattr(isys, model_field, w.get().strip()) # Списки isys.defense_tools_list = list(self.defense_tools_listbox.get(0, tk.END)) isys.users_list = list(self.users_listbox.get(0, tk.END)) @@ -1224,6 +1361,45 @@ class DokoGenApp: self.is_internet_var.set(isys.is_internet) self.is_category_var.set(isys.category or "без категории") + # Поля 236 приказа + for attr, model_field in [ + ('is_purpose_entry', 'purpose'), + ('is_sphere_entry', 'sphere'), + ('is_object_type_entry', 'object_type'), + ('is_architecture_entry', 'architecture'), + ('is_typical_object_entry', 'typical_object'), + ('is_subject_name_entry', 'subject_name'), + ('is_subject_address_entry', 'subject_address'), + ('is_subject_chief_position_entry', 'subject_chief_position'), + ('is_subject_chief_fio_entry', 'subject_chief_fio'), + ('is_subject_security_position_entry', 'subject_security_position'), + ('is_subject_security_fio_entry', 'subject_security_fio'), + ('is_subject_department_entry', 'subject_department'), + ('is_subject_department_chief_entry', 'subject_department_chief'), + ('is_subject_phone_entry', 'subject_phone'), + ('is_subject_email_entry', 'subject_email'), + ('is_subject_inn_entry', 'subject_inn'), + ('is_subject_kpp_entry', 'subject_kpp'), + ('is_net_category_entry', 'net_category'), + ('is_net_operator_entry', 'net_operator'), + ('is_net_purpose_entry', 'net_purpose'), + ('is_net_method_entry', 'net_method'), + ('is_hw_sw_entry', 'hw_sw'), + ('is_os_sw_entry', 'os_sw'), + ('is_app_sw_entry', 'app_sw'), + ('is_attacker_category_entry', 'attacker_category'), + ('is_threats_entry', 'threats'), + ('is_incidents_entry', 'incidents'), + ('is_criteria_values_entry', 'criteria_values'), + ('is_criteria_justification_entry', 'criteria_justification'), + ('is_org_measures_entry', 'org_measures'), + ('is_tech_measures_entry', 'tech_measures'), + ]: + w = getattr(self, attr, None) + if w is not None: + w.delete(0, tk.END) + w.insert(0, getattr(isys, model_field, '') or '') + # Списки for lb, items in [ (self.defense_tools_listbox, isys.defense_tools_list or []), @@ -1257,6 +1433,37 @@ class DokoGenApp: category=src.category, defense_tools_list=list(src.defense_tools_list), users_list=list(src.users_list), + purpose=src.purpose, + sphere=src.sphere, + object_type=src.object_type, + architecture=src.architecture, + typical_object=src.typical_object, + subject_name=src.subject_name, + subject_address=src.subject_address, + subject_chief_position=src.subject_chief_position, + subject_chief_fio=src.subject_chief_fio, + subject_security_position=src.subject_security_position, + subject_security_fio=src.subject_security_fio, + subject_department=src.subject_department, + subject_department_chief=src.subject_department_chief, + subject_phone=src.subject_phone, + subject_email=src.subject_email, + subject_inn=src.subject_inn, + subject_kpp=src.subject_kpp, + net_category=src.net_category, + net_operator=src.net_operator, + net_purpose=src.net_purpose, + net_method=src.net_method, + hw_sw=src.hw_sw, + os_sw=src.os_sw, + app_sw=src.app_sw, + attacker_category=src.attacker_category, + threats=src.threats, + incidents=src.incidents, + criteria_values=src.criteria_values, + criteria_justification=src.criteria_justification, + org_measures=src.org_measures, + tech_measures=src.tech_measures, ) self.model.information_systems.append(copy) self._refresh_is_listbox() @@ -1745,6 +1952,20 @@ class DokoGenApp: if data.get('ispdnDepartment'): self.model.security_department = data['ispdnDepartment'] + # Доменное имя и внешний сетевой адрес + if data.get('domainName'): + entry = self._company_entries.get('domain_name') + if entry: + entry.delete(0, tk.END) + entry.insert(0, data['domainName']) + + # Доменное имя и внешний сетевой адрес + if data.get('domainName'): + entry = self._company_entries.get('domain_name') + if entry: + entry.delete(0, tk.END) + entry.insert(0, data['domainName']) + # ОКВЭД (основной и дополнительные) if data.get('okved'): entry = self._company_entries.get('okved') @@ -1756,7 +1977,8 @@ class DokoGenApp: self.okved_extra_listbox.delete(0, tk.END) extra = data['okvedExtra'] if isinstance(extra, str): - parts = [p.strip() for p in extra.replace(';', '\n').split('\n') if p.strip()] + import re as _re + parts = [p.strip() for p in _re.split(r'[;,\n]+', extra) if p.strip()] else: parts = [str(p).strip() for p in extra if str(p).strip()] for p in parts: @@ -1774,9 +1996,39 @@ class DokoGenApp: category=is_data.get('category', ''), defense_tools_list=is_data.get('defense_tools', []), users_list=is_data.get('usersList', []), - purpose=is_data.get('purpose', is_data.get('description', '')), + purpose=is_data.get('purpose', ''), structure=is_data.get('structure', ''), software=is_data.get('software', ''), + sphere=is_data.get('sphere', ''), + object_type=is_data.get('object_type', ''), + architecture=is_data.get('architecture', ''), + typical_object=is_data.get('typical_object', ''), + subject_name=is_data.get('subject_name', ''), + subject_address=is_data.get('subject_address', ''), + subject_chief_position=is_data.get('subject_chief_position', ''), + subject_chief_fio=is_data.get('subject_chief_fio', ''), + subject_security_position=is_data.get('subject_security_position', ''), + subject_security_fio=is_data.get('subject_security_fio', ''), + subject_department=is_data.get('subject_department', ''), + subject_department_chief=is_data.get('subject_department_chief', ''), + subject_phone=is_data.get('subject_phone', ''), + subject_email=is_data.get('subject_email', ''), + subject_inn=is_data.get('subject_inn', ''), + subject_kpp=is_data.get('subject_kpp', ''), + net_category=is_data.get('net_category', ''), + net_operator=is_data.get('net_operator', ''), + net_purpose=is_data.get('net_purpose', ''), + net_method=is_data.get('net_method', ''), + hw_sw=is_data.get('hw_sw', ''), + os_sw=is_data.get('os_sw', ''), + app_sw=is_data.get('app_sw', ''), + attacker_category=is_data.get('attacker_category', ''), + threats=is_data.get('threats', ''), + incidents=is_data.get('incidents', ''), + criteria_values=is_data.get('criteria_values', ''), + criteria_justification=is_data.get('criteria_justification', ''), + org_measures=is_data.get('org_measures', ''), + tech_measures=is_data.get('tech_measures', ''), ) self.model.information_systems.append(isys) self._refresh_is_listbox() diff --git a/dokogen/variables.py b/dokogen/variables.py index d44914b..a4dc228 100644 --- a/dokogen/variables.py +++ b/dokogen/variables.py @@ -220,6 +220,17 @@ def _add_is_indexed_vars(replacements: Dict[str, str], isys, idx: int): 'is_purpose': isys.purpose or '—', 'is_structure': getattr(isys, 'structure', '') or '—', 'is_software': getattr(isys, 'software', '') or '—', + 'is_sphere': getattr(isys, 'sphere', '') or '—', + 'is_object_type': getattr(isys, 'object_type', '') or '—', + 'is_architecture': getattr(isys, 'architecture', '') or '—', + 'is_typical_object': getattr(isys, 'typical_object', '') or '—', + 'is_net_category': getattr(isys, 'net_category', '') or '—', + 'is_net_operator': getattr(isys, 'net_operator', '') or '—', + 'is_net_purpose': getattr(isys, 'net_purpose', '') or '—', + 'is_net_method': getattr(isys, 'net_method', '') or '—', + 'is_hw_sw': getattr(isys, 'hw_sw', '') or '—', + 'is_os_sw': getattr(isys, 'os_sw', '') or '—', + 'is_app_sw': getattr(isys, 'app_sw', '') or '—', } for f, v in fields.items(): replacements[f'{{{{{f}_{idx}}}}}'] = str(v) @@ -294,6 +305,38 @@ def _build_loop_data(company: Company, is_list: List) -> Dict[str, List[Dict]]: 'is_purpose': isys.purpose or '—', 'structure': getattr(isys, 'structure', '') or '—', 'software': getattr(isys, 'software', '') or '—', + # 236 приказ + 'purpose': isys.purpose or '—', + 'sphere': getattr(isys, 'sphere', '') or '—', + 'object_type': getattr(isys, 'object_type', '') or '—', + 'architecture': getattr(isys, 'architecture', '') or '—', + 'typical_object': getattr(isys, 'typical_object', '') or '—', + 'subject_name': getattr(isys, 'subject_name', '') or '—', + 'subject_address': getattr(isys, 'subject_address', '') or '—', + 'subject_chief_position': getattr(isys, 'subject_chief_position', '') or '—', + 'subject_chief_fio': getattr(isys, 'subject_chief_fio', '') or '—', + 'subject_security_position': getattr(isys, 'subject_security_position', '') or '—', + 'subject_security_fio': getattr(isys, 'subject_security_fio', '') or '—', + 'subject_department': getattr(isys, 'subject_department', '') or '—', + 'subject_department_chief': getattr(isys, 'subject_department_chief', '') or '—', + 'subject_phone': getattr(isys, 'subject_phone', '') or '—', + 'subject_email': getattr(isys, 'subject_email', '') or '—', + 'subject_inn': getattr(isys, 'subject_inn', '') or '—', + 'subject_kpp': getattr(isys, 'subject_kpp', '') or '—', + 'net_category': getattr(isys, 'net_category', '') or '—', + 'net_operator': getattr(isys, 'net_operator', '') or '—', + 'net_purpose': getattr(isys, 'net_purpose', '') or '—', + 'net_method': getattr(isys, 'net_method', '') or '—', + 'hw_sw': getattr(isys, 'hw_sw', '') or '—', + 'os_sw': getattr(isys, 'os_sw', '') or '—', + 'app_sw': getattr(isys, 'app_sw', '') or '—', + 'attacker_category': getattr(isys, 'attacker_category', '') or '—', + 'threats': getattr(isys, 'threats', '') or '—', + 'incidents': getattr(isys, 'incidents', '') or '—', + 'criteria_values': getattr(isys, 'criteria_values', '') or '—', + 'criteria_justification': getattr(isys, 'criteria_justification', '') or '—', + 'org_measures': getattr(isys, 'org_measures', '') or '—', + 'tech_measures': getattr(isys, 'tech_measures', '') or '—', }) loops['information_systems'] = items diff --git a/dokogen/verify.py b/dokogen/verify.py index 9f93445..b9e90bf 100644 --- a/dokogen/verify.py +++ b/dokogen/verify.py @@ -250,6 +250,14 @@ def check_company_dadata(inn, token): chief_fio = _normalize_text(management.get('name', '')) ogrn_date = _fmt_date(d.get('ogrn_date') or '') + # ОКВЭД: основной (okved) и дополнительные (okveds — список словарей с 'code') + okved = d.get('okved', '') or '' + okved_extra = [] + for ok in (d.get('okveds') or []): + code = (ok or {}).get('code') or '' + if code: + okved_extra.append(code) + return { 'source': 'DaData', 'source_url': 'https://dadata.ru', @@ -266,6 +274,8 @@ def check_company_dadata(inn, token): 'chief_position': chief_position, 'chief_fio': chief_fio, 'inn': d.get('inn', inn), + 'okved': okved, + 'okved_extra': okved_extra, }, 'apply': {'company': { 'full_name': full_name, @@ -276,6 +286,8 @@ def check_company_dadata(inn, token): 'ogrn_date': ogrn_date, 'chief_position': chief_position, 'chief_fio': chief_fio, + 'okved': okved, + 'okved_extra': okved_extra, }}, }, None