fix: ОКВЭД импорт/DaData/словарь+интернет, доменное имя, вкладка ОКИИ → вложенные вкладки, словарики, СЗИ разбивка, адрес отдельно

- ОКВЭД: resolve_okved (словарь okved.json + DaData suggest okved2), применяется при импорте и DaData; галочки «ОКВЭД осн./доп.» в «что применять»
- DaData: показ и подстановка доп. ОКВЭД
- доменное имя: поле во вкладке «Организация» + импорт
- вкладка «Объекты КИИ»: сверху Наименование/Описание/Категория, остальное вкладками: Основное (назначение, сфера, тип, архитектура, типовой отраслевой объект — со словариками и выпадающими списками), Субъект, Сети и ПО, Безопасность (236), СЗИ, Пользователи ОКИИ
- новые словари: spheres.json, object_types.json, architectures.json, typical_objects.json
- адрес объекта — в отдельное поле (не в описание)
- СЗИ: разбивка слипшихся названий (Kaspersky...)
This commit is contained in:
2026-08-04 15:33:36 +04:00
parent 2037925986
commit ed71343d92
7 changed files with 237 additions and 68 deletions
+122 -66
View File
@@ -359,6 +359,8 @@ class DokoGenApp:
'address': tk.BooleanVar(value=True),
'requisites': tk.BooleanVar(value=True),
'chief': tk.BooleanVar(value=True),
'okved': tk.BooleanVar(value=True),
'okved_extra': tk.BooleanVar(value=True),
}
apply_row = ttk.Frame(right)
apply_row.pack(fill=tk.X, pady=(4, 0))
@@ -368,6 +370,11 @@ class DokoGenApp:
ttk.Checkbutton(apply_row, text="адрес", variable=self._apply_flags['address']).pack(side=tk.LEFT)
ttk.Checkbutton(apply_row, text="ИНН/КПП/ОГРН", variable=self._apply_flags['requisites']).pack(side=tk.LEFT)
ttk.Checkbutton(apply_row, text="руковод.", variable=self._apply_flags['chief']).pack(side=tk.LEFT)
apply_row2 = ttk.Frame(right)
apply_row2.pack(fill=tk.X, pady=(2, 0))
ttk.Label(apply_row2, text="", font=('', 8), width=10).pack(side=tk.LEFT)
ttk.Checkbutton(apply_row2, text="ОКВЭД осн.", variable=self._apply_flags['okved']).pack(side=tk.LEFT)
ttk.Checkbutton(apply_row2, text="ОКВЭД доп.", variable=self._apply_flags['okved_extra']).pack(side=tk.LEFT)
self._verify_result = None
# ---------- ПРОВЕРКА ДАННЫХ (Морфер / DaData / ИИ) ----------
@@ -704,15 +711,18 @@ 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:
# ОКВЭД из DaData (с названием из словаря/интернета)
from .verify import resolve_okved
from .api_settings import load_api_settings as _las3
_tok3 = (_las3().get('dadata_token') or '')
if want('okved') and 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._company_entries['okved'].insert(0, resolve_okved(comp['okved'], _tok3))
if want('okved_extra') and 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())
self.okved_extra_listbox.insert(tk.END, resolve_okved(str(code).strip(), _tok3))
if want('chief'):
if comp.get('chief_position') and 'chief_position' in self._official_entries:
self._official_entries['chief_position'].delete(0, tk.END)
@@ -748,61 +758,87 @@ class DokoGenApp:
fields_frame = ttk.Frame(self._is_edit_panel, padding=10)
fields_frame.pack(fill=tk.X)
# Основные поля
# Основные поля (сверху — только название, описание, категория)
ttk.Label(fields_frame, text="Наименование:").grid(row=0, column=0, sticky=tk.W, pady=2)
self.is_name_entry = ttk.Entry(fields_frame, width=55)
self.is_name_entry = ttk.Entry(fields_frame, width=60)
self.is_name_entry.grid(row=0, column=1, padx=10, sticky=tk.EW)
ttk.Label(fields_frame, text="Описание:").grid(row=1, column=0, sticky=tk.W, pady=2)
self.is_description_entry = ttk.Entry(fields_frame, width=55)
self.is_description_entry = ttk.Entry(fields_frame, width=60)
self.is_description_entry.grid(row=1, column=1, padx=10, sticky=tk.EW)
# Чекбоксы
chk_frame = ttk.Frame(fields_frame)
chk_frame.grid(row=2, column=1, sticky=tk.W, pady=5)
self.is_lan_var = tk.BooleanVar()
ttk.Checkbutton(chk_frame, text="ЛВС", variable=self.is_lan_var).pack(side=tk.LEFT, padx=5)
self.is_internet_var = tk.BooleanVar()
ttk.Checkbutton(chk_frame, text="Интернет", variable=self.is_internet_var).pack(side=tk.LEFT, padx=5)
# Категория значимости объекта КИИ
self.is_category_var = tk.StringVar(value="без категории")
ttk.Label(fields_frame, text="Категория значимости:").grid(row=3, column=0, sticky=tk.W, pady=2)
ttk.Label(fields_frame, text="Категория значимости:").grid(row=2, column=0, sticky=tk.W, pady=2)
cat_frame = ttk.Frame(fields_frame)
cat_frame.grid(row=3, column=1, sticky=tk.W, padx=10)
cat_frame.grid(row=2, column=1, sticky=tk.W, padx=10)
for val in ["1", "2", "3", "без категории"]:
ttk.Radiobutton(cat_frame, text=val, variable=self.is_category_var,
value=val).pack(side=tk.LEFT, padx=3)
# Программное обеспечение
ttk.Label(fields_frame, text="Программное обеспечение:").grid(row=4, column=0, sticky=tk.W, pady=2)
self.is_software_entry = ttk.Entry(fields_frame, width=55)
self.is_software_entry.grid(row=4, column=1, padx=10, sticky=tk.EW)
fields_frame.columnconfigure(1, weight=1)
# Вложенные списки
# ---- Вложенные вкладки ----
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)
# ===== Вкладка «Основное» =====
main_tab = ttk.Frame(nb2)
nb2.add(main_tab, text="Основное")
main_frame = ttk.Frame(main_tab, padding=8)
main_frame.pack(fill=tk.BOTH, expand=True)
# Назначение объекта
ttk.Label(main_frame, text="Назначение объекта:").grid(row=0, column=0, sticky=tk.W, pady=2)
self.is_purpose_entry = ttk.Entry(main_frame, width=70)
self.is_purpose_entry.grid(row=0, column=1, padx=8, sticky=tk.EW, pady=1)
# Сфера деятельности — словарик + выпадающий список
ttk.Label(main_frame, text="Сфера деятельности:").grid(row=1, column=0, sticky=tk.W, pady=2)
self.is_sphere_combo = ttk.Combobox(main_frame, width=68)
self.is_sphere_combo.grid(row=1, column=1, padx=8, sticky=tk.EW, pady=1)
try:
import json as _json
_sp = os.path.join(os.path.dirname(__file__), 'dictionaries', 'spheres.json')
with open(_sp, encoding='utf-8') as _f:
self.is_sphere_combo['values'] = _json.load(_f)
except Exception:
pass
# Тип объекта — словарик + выпадающий список
ttk.Label(main_frame, text="Тип объекта:").grid(row=2, column=0, sticky=tk.W, pady=2)
self.is_object_type_combo = ttk.Combobox(main_frame, width=68)
self.is_object_type_combo.grid(row=2, column=1, padx=8, sticky=tk.EW, pady=1)
try:
_ot = os.path.join(os.path.dirname(__file__), 'dictionaries', 'object_types.json')
with open(_ot, encoding='utf-8') as _f:
self.is_object_type_combo['values'] = _json.load(_f)
except Exception:
pass
# Архитектура объекта — словарик + выпадающий список
ttk.Label(main_frame, text="Архитектура объекта:").grid(row=3, column=0, sticky=tk.W, pady=2)
self.is_architecture_combo = ttk.Combobox(main_frame, width=68)
self.is_architecture_combo.grid(row=3, column=1, padx=8, sticky=tk.EW, pady=1)
try:
_ar = os.path.join(os.path.dirname(__file__), 'dictionaries', 'architectures.json')
with open(_ar, encoding='utf-8') as _f:
self.is_architecture_combo['values'] = _json.load(_f)
except Exception:
pass
# Типовой отраслевой объект КИИ — словарик + выпадающий список
ttk.Label(main_frame, text="Типовой отраслевой объект КИИ:").grid(row=4, column=0, sticky=tk.W, pady=2)
self.is_typical_object_combo = ttk.Combobox(main_frame, width=68)
self.is_typical_object_combo.grid(row=4, column=1, padx=8, sticky=tk.EW, pady=1)
try:
_to = os.path.join(os.path.dirname(__file__), 'dictionaries', 'typical_objects.json')
with open(_to, encoding='utf-8') as _f:
self.is_typical_object_combo['values'] = _json.load(_f)
except Exception:
pass
main_frame.columnconfigure(1, weight=1)
# ===== Вкладка «Субъект» =====
subj_tab = ttk.Frame(nb2)
@@ -835,19 +871,29 @@ class DokoGenApp:
nb2.add(net_tab, text="Сети и ПО")
net_frame = ttk.Frame(net_tab, padding=8)
net_frame.pack(fill=tk.BOTH, expand=True)
# ЛВС / Интернет
chk = ttk.Frame(net_frame)
chk.grid(row=0, column=0, columnspan=2, sticky=tk.W, pady=2)
self.is_lan_var = tk.BooleanVar()
ttk.Checkbutton(chk, text="ЛВС", variable=self.is_lan_var).pack(side=tk.LEFT, padx=5)
self.is_internet_var = tk.BooleanVar()
ttk.Checkbutton(chk, text="Интернет", variable=self.is_internet_var).pack(side=tk.LEFT, padx=5)
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"),
("Категория сети электросвязи:", "is_net_category_entry", 1),
("Оператор связи/провайдер хостинга:", "is_net_operator_entry", 2),
("Цель взаимодействия с сетью:", "is_net_purpose_entry", 3),
("Способ взаимодействия/тип доступа:", "is_net_method_entry", 4),
("ПО и аппаратные средства:", "is_hw_sw_entry", 5),
("Общесистемное ПО (ОС, виртуализация):", "is_os_sw_entry", 6),
("Прикладные программы:", "is_app_sw_entry", 7),
("Программное обеспечение:", "is_software_entry", 8),
]
for i, (label, attr) in enumerate(net_fields):
ttk.Label(net_frame, text=label).grid(row=i, column=0, sticky=tk.W, pady=2)
for label, attr, row in net_fields:
ttk.Label(net_frame, text=label).grid(row=row, 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)
entry.grid(row=row, column=1, padx=8, sticky=tk.EW, pady=1)
setattr(self, attr, entry)
net_frame.columnconfigure(1, weight=1)
@@ -872,7 +918,7 @@ class DokoGenApp:
setattr(self, attr, entry)
sec_frame.columnconfigure(1, weight=1)
# СЗИ
# ===== Вкладка «Средства защиты» =====
def_tab = ttk.Frame(nb2)
nb2.add(def_tab, text="Средства защиты")
def_btn_frame = ttk.Frame(def_tab)
@@ -883,7 +929,7 @@ class DokoGenApp:
self.defense_tools_listbox = tk.Listbox(def_tab, height=6)
self.defense_tools_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Пользователи ОКИИ
# ===== Вкладка «Пользователи ОКИИ» =====
user_tab = ttk.Frame(nb2)
nb2.add(user_tab, text="Пользователи ОКИИ")
self.users_listbox = tk.Listbox(user_tab, height=6)
@@ -1124,10 +1170,10 @@ class DokoGenApp:
# Поля 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_sphere_combo', 'sphere'),
('is_object_type_combo', 'object_type'),
('is_architecture_combo', 'architecture'),
('is_typical_object_combo', 'typical_object'),
('is_subject_name_entry', 'subject_name'),
('is_subject_address_entry', 'subject_address'),
('is_subject_chief_position_entry', 'subject_chief_position'),
@@ -1364,10 +1410,10 @@ class DokoGenApp:
# Поля 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_sphere_combo', 'sphere'),
('is_object_type_combo', 'object_type'),
('is_architecture_combo', 'architecture'),
('is_typical_object_combo', 'typical_object'),
('is_subject_name_entry', 'subject_name'),
('is_subject_address_entry', 'subject_address'),
('is_subject_chief_position_entry', 'subject_chief_position'),
@@ -1397,8 +1443,12 @@ class DokoGenApp:
]:
w = getattr(self, attr, None)
if w is not None:
w.delete(0, tk.END)
w.insert(0, getattr(isys, model_field, '') or '')
val = getattr(isys, model_field, '') or ''
if isinstance(w, ttk.Combobox):
w.set(val)
else:
w.delete(0, tk.END)
w.insert(0, val)
# Списки
for lb, items in [
@@ -1970,10 +2020,16 @@ class DokoGenApp:
if data.get('okved'):
entry = self._company_entries.get('okved')
if entry:
from .verify import resolve_okved
from .api_settings import load_api_settings as _las
_tok = (_las().get('dadata_token') or '')
entry.delete(0, tk.END)
entry.insert(0, data['okved'])
entry.insert(0, resolve_okved(data['okved'], _tok))
if data.get('okvedExtra'):
if hasattr(self, 'okved_extra_listbox'):
from .verify import resolve_okved
from .api_settings import load_api_settings as _las2
_tok2 = (_las2().get('dadata_token') or '')
self.okved_extra_listbox.delete(0, tk.END)
extra = data['okvedExtra']
if isinstance(extra, str):
@@ -1982,7 +2038,7 @@ class DokoGenApp:
else:
parts = [str(p).strip() for p in extra if str(p).strip()]
for p in parts:
self.okved_extra_listbox.insert(tk.END, p)
self.okved_extra_listbox.insert(tk.END, resolve_okved(p, _tok2))
# Объекты КИИ
if data.get('informationSystems'):