feat: переделка под ОКИИ (187-ФЗ) — объекты КИИ вместо ИС, категории 1-3, ОКВЭД, убраны ПДн/бумажные носители
- вкладка «Информационные системы» → «Объекты КИИ», убрана «Структура объекта» - «Тип угроз»/«УЗ» → категория значимости 1/2/3/без категории - удалены Категории ПДн, кол-во записей, перечень/субъекты/действия с ПДн + словари - «Пользователи ИС» → «Пользователи ОКИИ», средства защиты оставлены - вкладка «Бумажные документы» удалена полностью (GUI + логика + модели) - ОКВЭД: поля во вкладке «Организация», словарь okved.json, выбор галочками, импорт - нормализация регистра адреса DaData (КАПС → нормальный) - importer: объекты КИИ (наименование объекта, ПО, СЗИ, пользователи)
This commit is contained in:
+158
-362
@@ -11,7 +11,7 @@ from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from . import (
|
||||
Company, CommissionMember, InformationSystem, PaperDocument,
|
||||
Company, CommissionMember, InformationSystem,
|
||||
inflect_name,
|
||||
build_replacements, VARIABLE_DEFS,
|
||||
process_template,
|
||||
@@ -157,7 +157,6 @@ class DokoGenApp:
|
||||
self._build_is_tab()
|
||||
self._build_commission_tab()
|
||||
self._build_users_tab()
|
||||
self._build_paper_tab()
|
||||
self._build_generation_tab()
|
||||
self._build_log_tab()
|
||||
|
||||
@@ -270,6 +269,33 @@ class DokoGenApp:
|
||||
|
||||
f2.columnconfigure(1, weight=1)
|
||||
|
||||
# --- ОКВЭД ---
|
||||
f_okved = ttk.LabelFrame(left, text="ОКВЭД", padding=10)
|
||||
f_okved.pack(fill=tk.X, pady=5)
|
||||
|
||||
# Основной ОКВЭД — один, из словаря или вручную
|
||||
ttk.Label(f_okved, text="Основной ОКВЭД:").grid(row=0, column=0, sticky=tk.W, pady=2)
|
||||
okved_entry = ttk.Entry(f_okved, width=50)
|
||||
okved_entry.grid(row=0, column=1, padx=10, sticky=tk.EW, pady=1)
|
||||
self._company_entries['okved'] = okved_entry
|
||||
ttk.Button(f_okved, text="Выбрать из словаря",
|
||||
command=self._select_main_okved).grid(row=0, column=2, padx=5)
|
||||
|
||||
# Дополнительные ОКВЭД — сколько угодно
|
||||
ttk.Label(f_okved, text="Дополнительные ОКВЭД:").grid(row=1, column=0, sticky=tk.NW, pady=2)
|
||||
self.okved_extra_listbox = tk.Listbox(f_okved, height=4, width=50)
|
||||
self.okved_extra_listbox.grid(row=1, column=1, padx=10, sticky=tk.EW, pady=1)
|
||||
okved_btn = ttk.Frame(f_okved)
|
||||
okved_btn.grid(row=1, column=2, padx=5, sticky=tk.N)
|
||||
ttk.Button(okved_btn, text="Выбрать из словаря",
|
||||
command=self._select_extra_okved).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(okved_btn, text="+ Добавить",
|
||||
command=self._add_okved_manual).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(okved_btn, text="- Удалить",
|
||||
command=self._delete_okved_extra).pack(fill=tk.X, pady=1)
|
||||
|
||||
f_okved.columnconfigure(1, weight=1)
|
||||
|
||||
# --- Должностные лица ---
|
||||
f3 = ttk.LabelFrame(left, text="Должностные лица", padding=10)
|
||||
f3.pack(fill=tk.X, pady=5)
|
||||
@@ -686,12 +712,12 @@ class DokoGenApp:
|
||||
# ---------- ВКЛАДКА "ИНФОРМАЦИОННЫЕ СИСТЕМЫ" ----------
|
||||
def _build_is_tab(self):
|
||||
tab = ttk.Frame(self.notebook)
|
||||
self.notebook.add(tab, text="Информационные системы")
|
||||
self.notebook.add(tab, text="Объекты КИИ")
|
||||
|
||||
# Левая панель — список ИС
|
||||
# Левая панель — список объектов КИИ
|
||||
left = ttk.Frame(tab)
|
||||
left.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
|
||||
ttk.Label(left, text="Информационные системы:").pack()
|
||||
ttk.Label(left, text="Объекты КИИ:").pack()
|
||||
self.is_listbox = tk.Listbox(left, width=35, height=18, exportselection=False)
|
||||
self.is_listbox.pack(fill=tk.BOTH, expand=True, pady=5)
|
||||
self.is_listbox.bind('<<ListboxSelect>>', self._on_is_select)
|
||||
@@ -703,7 +729,7 @@ class DokoGenApp:
|
||||
ttk.Button(btn_frame, text="- Удалить", command=self._delete_is).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
# Правая панель — редактирование
|
||||
self._is_edit_panel = ttk.LabelFrame(tab, text="Редактирование ИС")
|
||||
self._is_edit_panel = ttk.LabelFrame(tab, text="Редактирование объекта КИИ")
|
||||
self._is_edit_panel.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
fields_frame = ttk.Frame(self._is_edit_panel, padding=10)
|
||||
@@ -726,51 +752,19 @@ class DokoGenApp:
|
||||
self.is_internet_var = tk.BooleanVar()
|
||||
ttk.Checkbutton(chk_frame, text="Интернет", variable=self.is_internet_var).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# Тип угроз
|
||||
self.is_threat_var = tk.StringVar(value="3")
|
||||
self.is_threat_var.trace_add("write", lambda *_: self._on_categories_or_count_changed())
|
||||
ttk.Label(fields_frame, text="Тип угроз:").grid(row=3, column=0, sticky=tk.W, pady=2)
|
||||
threat_frame = ttk.Frame(fields_frame)
|
||||
threat_frame.grid(row=3, column=1, sticky=tk.W, padx=10)
|
||||
for val, text in [("1", "1 (НДВ в СПО)"), ("2", "2 (НДВ в ППО)"), ("3", "3 (НДВ нет)")]:
|
||||
ttk.Radiobutton(threat_frame, text=text, variable=self.is_threat_var,
|
||||
# Категория значимости объекта КИИ
|
||||
self.is_category_var = tk.StringVar(value="без категории")
|
||||
ttk.Label(fields_frame, text="Категория значимости:").grid(row=3, 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)
|
||||
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)
|
||||
|
||||
# Уровень защищённости
|
||||
self.is_defence_var = tk.StringVar(value="4")
|
||||
ttk.Label(fields_frame, text="Ур. защищённости:").grid(row=4, column=0, sticky=tk.W, pady=2)
|
||||
def_frame = ttk.Frame(fields_frame)
|
||||
def_frame.grid(row=4, column=1, sticky=tk.W, padx=10)
|
||||
for lvl in ["1", "2", "3", "4"]:
|
||||
ttk.Radiobutton(def_frame, text=f"{lvl} ур.",
|
||||
variable=self.is_defence_var, value=lvl).pack(side=tk.LEFT, padx=3)
|
||||
|
||||
|
||||
# Категории ПДн
|
||||
ttk.Label(fields_frame, text="Категории ПДн:").grid(row=5, column=0, sticky=tk.NW, pady=2)
|
||||
cat_frame = ttk.Frame(fields_frame)
|
||||
cat_frame.grid(row=5, column=1, sticky=tk.W, padx=10)
|
||||
self.category_vars = []
|
||||
for cat in ["специальные", "биометрические", "общедоступные", "иные"]:
|
||||
var = tk.BooleanVar()
|
||||
var.trace_add('write', lambda *_: self._on_categories_or_count_changed())
|
||||
cb = ttk.Checkbutton(cat_frame, text=cat, variable=var)
|
||||
cb.pack(anchor=tk.W)
|
||||
self.category_vars.append((cat, var))
|
||||
|
||||
# Количество записей ПДн (выпадающий список)
|
||||
ttk.Label(fields_frame, text="Кол-во записей:").grid(row=6, column=0, sticky=tk.W, pady=2)
|
||||
self.is_pd_count_var = tk.StringVar(value="менее 100 000")
|
||||
self.is_pd_count_combo = ttk.Combobox(fields_frame, textvariable=self.is_pd_count_var,
|
||||
values=["менее 100 000", "более 100 000"],
|
||||
state='readonly', width=28)
|
||||
self.is_pd_count_combo.grid(row=6, column=1, padx=10, sticky=tk.W)
|
||||
self.is_pd_count_combo.bind('<<ComboboxSelected>>', lambda e: self._on_categories_or_count_changed())
|
||||
|
||||
# Структура ИС
|
||||
ttk.Label(fields_frame, text="Структура ИС:").grid(row=7, column=0, sticky=tk.W, pady=2)
|
||||
self.is_structure_entry = ttk.Entry(fields_frame, width=55)
|
||||
self.is_structure_entry.grid(row=7, column=1, padx=10, sticky=tk.EW)
|
||||
# Программное обеспечение
|
||||
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)
|
||||
|
||||
@@ -778,39 +772,6 @@ class DokoGenApp:
|
||||
nb2 = ttk.Notebook(self._is_edit_panel)
|
||||
nb2.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# Список ПДн
|
||||
pd_tab = ttk.Frame(nb2)
|
||||
nb2.add(pd_tab, text="Перечень ПДн")
|
||||
pd_btn_frame = ttk.Frame(pd_tab)
|
||||
pd_btn_frame.pack(fill=tk.X)
|
||||
ttk.Button(pd_btn_frame, text="+ Добавить", command=self._add_pd_item).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(pd_btn_frame, text="- Удалить", command=lambda: self._del_from_listbox(self.pd_listbox)).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(pd_btn_frame, text="Выбрать из списка", command=self._select_from_pd_list).pack(side=tk.LEFT, padx=2)
|
||||
self.pd_listbox = tk.Listbox(pd_tab, height=6)
|
||||
self.pd_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Субъекты ПДн
|
||||
subj_tab = ttk.Frame(nb2)
|
||||
nb2.add(subj_tab, text="Субъекты ПДн")
|
||||
subj_btn_frame = ttk.Frame(subj_tab)
|
||||
subj_btn_frame.pack(fill=tk.X)
|
||||
ttk.Button(subj_btn_frame, text="+ Добавить", command=self._add_pd_subjects_item).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(subj_btn_frame, text="- Удалить", command=lambda: self._del_from_listbox(self.pd_subjects_listbox)).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(subj_btn_frame, text="Выбрать из списка", command=self._select_from_subjects_list).pack(side=tk.LEFT, padx=2)
|
||||
self.pd_subjects_listbox = tk.Listbox(subj_tab, height=6)
|
||||
self.pd_subjects_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Действия с ПДн
|
||||
act_tab = ttk.Frame(nb2)
|
||||
nb2.add(act_tab, text="Действия с ПДн")
|
||||
act_btn_frame = ttk.Frame(act_tab)
|
||||
act_btn_frame.pack(fill=tk.X)
|
||||
ttk.Button(act_btn_frame, text="+ Добавить", command=self._add_pd_action_item).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(act_btn_frame, text="- Удалить", command=lambda: self._del_from_listbox(self.pd_actions_listbox)).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(act_btn_frame, text="Выбрать из списка", command=self._select_from_actions_list).pack(side=tk.LEFT, padx=2)
|
||||
self.pd_actions_listbox = tk.Listbox(act_tab, height=6)
|
||||
self.pd_actions_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# СЗИ
|
||||
def_tab = ttk.Frame(nb2)
|
||||
nb2.add(def_tab, text="Средства защиты")
|
||||
@@ -822,9 +783,9 @@ 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="Пользователи ИС")
|
||||
nb2.add(user_tab, text="Пользователи ОКИИ")
|
||||
self.users_listbox = tk.Listbox(user_tab, height=6)
|
||||
self.users_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
btnf = ttk.Frame(user_tab)
|
||||
@@ -886,7 +847,7 @@ class DokoGenApp:
|
||||
frame = ttk.Frame(scrollable, padding=10)
|
||||
frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
ttk.Label(frame, text="Сотрудники, имеющие доступ к ПДн",
|
||||
ttk.Label(frame, text="Сотрудники, имеющие доступ к объектам КИИ",
|
||||
font=('Arial', 11, 'bold')).pack(anchor=tk.W, pady=(0, 10))
|
||||
|
||||
# Заголовки
|
||||
@@ -971,102 +932,6 @@ class DokoGenApp:
|
||||
if emp['position'] or emp['fio']:
|
||||
self.model.employees_access.append(emp)
|
||||
|
||||
# ---------- ВКЛАДКА "БУМАЖНЫЕ ДОКУМЕНТЫ" ----------
|
||||
def _build_paper_tab(self):
|
||||
tab = ttk.Frame(self.notebook)
|
||||
self.notebook.add(tab, text="Бумажные документы")
|
||||
|
||||
canvas = tk.Canvas(tab, highlightthickness=0)
|
||||
scrollbar = ttk.Scrollbar(tab, orient=tk.VERTICAL, command=canvas.yview)
|
||||
scrollable = ttk.Frame(canvas)
|
||||
scrollable.bind("<Configure>",
|
||||
lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
|
||||
canvas.create_window((0, 0), window=scrollable, anchor="nw")
|
||||
canvas.configure(yscrollcommand=scrollbar.set)
|
||||
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
frame = ttk.Frame(scrollable, padding=10)
|
||||
frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
ttk.Label(frame, text="Бумажные носители ПДн (документы)",
|
||||
font=('Arial', 11, 'bold')).pack(anchor=tk.W, pady=(0, 10))
|
||||
|
||||
# Заголовки
|
||||
header = ttk.Frame(frame)
|
||||
header.pack(fill=tk.X)
|
||||
for w, col in [(45, "Название документа"), (45, "Место хранения")]:
|
||||
ttk.Label(header, text=col, font=('', 9, 'bold'),
|
||||
width=w, anchor=tk.W).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
self._paper_frame = ttk.Frame(frame)
|
||||
self._paper_frame.pack(fill=tk.X, pady=5)
|
||||
|
||||
btn_frame = ttk.Frame(frame)
|
||||
btn_frame.pack(fill=tk.X, pady=5)
|
||||
ttk.Button(btn_frame, text="+ Добавить документ",
|
||||
command=self._add_paper_row_ui).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(btn_frame, text="- Удалить выбранный",
|
||||
command=self._delete_paper_selected).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
self._paper_count_label = ttk.Label(frame, text="Добавлено: 0")
|
||||
self._paper_count_label.pack(anchor=tk.W, pady=5)
|
||||
|
||||
self._paper_entries = []
|
||||
# Загрузка существующих
|
||||
for p in self.model.paper_documents_list:
|
||||
self._add_paper_row(p.name, p.storage)
|
||||
self._update_paper_count()
|
||||
|
||||
def _add_paper_row(self, name="", storage=""):
|
||||
"""Добавляет одну строку документа."""
|
||||
row = ttk.Frame(self._paper_frame)
|
||||
row.pack(fill=tk.X, pady=2)
|
||||
|
||||
name_entry = ttk.Entry(row, width=45)
|
||||
name_entry.insert(0, name)
|
||||
name_entry.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
storage_entry = ttk.Entry(row, width=45)
|
||||
storage_entry.insert(0, storage)
|
||||
storage_entry.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
del_btn = ttk.Button(row, text="✖", width=3,
|
||||
command=lambda: self._delete_paper_row(row))
|
||||
del_btn.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self._paper_entries.append((row, name_entry, storage_entry))
|
||||
self._update_paper_count()
|
||||
|
||||
def _add_paper_row_ui(self):
|
||||
self._add_paper_row()
|
||||
|
||||
def _delete_paper_row(self, row_frame):
|
||||
self._paper_entries = [e for e in self._paper_entries if e[0] != row_frame]
|
||||
row_frame.destroy()
|
||||
self._update_paper_count()
|
||||
|
||||
def _delete_paper_selected(self):
|
||||
if self._paper_entries:
|
||||
row_frame = self._paper_entries[-1][0]
|
||||
self._delete_paper_row(row_frame)
|
||||
|
||||
def _update_paper_count(self):
|
||||
count = len(self._paper_entries)
|
||||
if hasattr(self, '_paper_count_label'):
|
||||
self._paper_count_label.config(text=f"Добавлено: {count}")
|
||||
|
||||
def _collect_paper_documents(self):
|
||||
"""Собирает данные бумажных документов из GUI в модель."""
|
||||
self.model.paper_documents_list.clear()
|
||||
for row, name_e, storage_e in self._paper_entries:
|
||||
name = name_e.get().strip()
|
||||
storage = storage_e.get().strip()
|
||||
if name or storage:
|
||||
self.model.paper_documents_list.append(
|
||||
PaperDocument(name=name, storage=storage)
|
||||
)
|
||||
|
||||
# ---------- ВКЛАДКА "ГЕНЕРАЦИЯ" ----------
|
||||
def _build_generation_tab(self):
|
||||
tab = ttk.Frame(self.notebook)
|
||||
@@ -1142,29 +1007,21 @@ class DokoGenApp:
|
||||
setattr(c, key, entry.get().strip())
|
||||
c.contract_number = self.doc_number_var.get()
|
||||
c.contract_date = self.doc_date_var.get()
|
||||
if hasattr(self, 'okved_extra_listbox'):
|
||||
c.okved_extra = list(self.okved_extra_listbox.get(0, tk.END))
|
||||
|
||||
def _collect_is(self):
|
||||
"""Собирает данные ИС из GUI в модель."""
|
||||
"""Собирает данные объекта КИИ из GUI в модель."""
|
||||
if self._current_is_idx is not None and self._current_is_idx < len(self.model.information_systems):
|
||||
isys = self.model.information_systems[self._current_is_idx]
|
||||
isys.name = self.is_name_entry.get().strip()
|
||||
isys.description = self.is_description_entry.get().strip()
|
||||
if hasattr(self, 'is_structure_entry'):
|
||||
isys.structure = self.is_structure_entry.get().strip()
|
||||
if hasattr(self, 'is_software_entry'):
|
||||
isys.software = self.is_software_entry.get().strip()
|
||||
isys.is_local_network = self.is_lan_var.get()
|
||||
isys.is_internet = self.is_internet_var.get()
|
||||
isys.threat_type = self.is_threat_var.get()
|
||||
isys.defence_level = self.is_defence_var.get()
|
||||
cats = [c for c, v in self.category_vars if v.get()]
|
||||
isys.personal_data_category = cats
|
||||
if hasattr(self, 'is_pd_count_entry'):
|
||||
isys.pd_count = self.is_pd_count_entry.get().strip() or 'менее 100 000'
|
||||
isys.category = self.is_category_var.get()
|
||||
# Списки
|
||||
isys.personal_data_list = list(self.pd_listbox.get(0, tk.END))
|
||||
isys.pd_subjects_list = list(self.pd_subjects_listbox.get(0, tk.END))
|
||||
isys.pd_actions_list = list(self.pd_actions_listbox.get(0, tk.END))
|
||||
isys.defense_tools_list = list(self.defense_tools_listbox.get(0, tk.END))
|
||||
isys.users_list = list(self.users_listbox.get(0, tk.END))
|
||||
|
||||
@@ -1199,6 +1056,12 @@ class DokoGenApp:
|
||||
self.doc_number_var.set(c.contract_number or "")
|
||||
self.doc_date_var.set(c.contract_date or "")
|
||||
|
||||
# Дополнительные ОКВЭД
|
||||
if hasattr(self, 'okved_extra_listbox'):
|
||||
self.okved_extra_listbox.delete(0, tk.END)
|
||||
for item in (c.okved_extra or []):
|
||||
self.okved_extra_listbox.insert(tk.END, item)
|
||||
|
||||
# Склонения
|
||||
idx_map = {'nomn': 1, 'gent': 2, 'datv': 3, 'accs': 4, 'ablt': 5, 'loct': 6}
|
||||
for code, entry in self.decl_entries.items():
|
||||
@@ -1236,15 +1099,6 @@ class DokoGenApp:
|
||||
)
|
||||
self._update_emp_count()
|
||||
|
||||
# Бумажные документы
|
||||
if hasattr(self, '_paper_entries'):
|
||||
for row_frame, _, _ in self._paper_entries:
|
||||
row_frame.destroy()
|
||||
self._paper_entries.clear()
|
||||
for p in c.paper_documents_list:
|
||||
self._add_paper_row(p.name, p.storage)
|
||||
self._update_paper_count()
|
||||
|
||||
# Шаблоны
|
||||
self._on_templates_changed()
|
||||
|
||||
@@ -1296,64 +1150,6 @@ class DokoGenApp:
|
||||
self.log(f"🏙 Город определён: {city}")
|
||||
|
||||
# ==================== АВТОРАСЧЁТ УРОВНЯ ЗАЩИЩЁННОСТИ ====================
|
||||
def _auto_calc_defence_level(self):
|
||||
"""Автоматический расчёт УЗ по ПП №1119: категории ПДн, тип субъектов,
|
||||
кол-во записей, тип угроз."""
|
||||
if self._current_is_idx is None:
|
||||
return
|
||||
|
||||
categories = [cat for cat, var in self.category_vars if var.get()]
|
||||
pd_count = self.is_pd_count_var.get() if hasattr(self, 'is_pd_count_var') else 'менее 100 000'
|
||||
threat_type = self.is_threat_var.get() if hasattr(self, 'is_threat_var') else '3'
|
||||
is_large = 'более' in pd_count.lower()
|
||||
# Автоопределение типа субъектов: если в Субъектах ПДн выбраны только "Сотрудники" → работники, иначе → иные
|
||||
subj_items = self.pd_subjects_listbox.get(0, tk.END) if hasattr(self, 'pd_subjects_listbox') else []
|
||||
has_only_employees = len(subj_items) > 0 and all('сотрудник' in s.lower() for s in subj_items)
|
||||
is_employees = has_only_employees or len(subj_items) == 0
|
||||
|
||||
# Таблица ПП №1119: (категория, тип_субъектов, объём, тип_угроз) → УЗ
|
||||
def lookup(cat_group, employees, large, threat):
|
||||
# Общедоступные → всегда 4
|
||||
if cat_group == 'public':
|
||||
return 4
|
||||
# Специальные/биометрические
|
||||
if cat_group == 'special_bio':
|
||||
tbl = {
|
||||
('emp', False, '1'): 2, ('emp', True, '1'): 1,
|
||||
('other', False, '1'): 1, ('other', True, '1'): 1,
|
||||
('emp', False, '2'): 2, ('emp', True, '2'): 1,
|
||||
('other', False, '2'): 2, ('other', True, '2'): 1,
|
||||
('emp', False, '3'): 3, ('emp', True, '3'): 2,
|
||||
('other', False, '3'): 3, ('other', True, '3'): 2,
|
||||
}
|
||||
else: # иные
|
||||
tbl = {
|
||||
('emp', False, '1'): 3, ('emp', True, '1'): 2,
|
||||
('other', False, '1'): 2, ('other', True, '1'): 1,
|
||||
('emp', False, '2'): 4, ('emp', True, '2'): 3,
|
||||
('other', False, '2'): 3, ('other', True, '2'): 2,
|
||||
('emp', False, '3'): 4, ('emp', True, '3'): 4,
|
||||
('other', False, '3'): 4, ('other', True, '3'): 3,
|
||||
}
|
||||
key = ('emp' if employees else 'other', large, threat)
|
||||
return tbl.get(key, 4)
|
||||
|
||||
# Определяем группу категорий
|
||||
if 'общедоступные' in categories and not any(c in categories for c in ['специальные', 'биометрические', 'иные']):
|
||||
cat_group = 'public'
|
||||
elif any(c in categories for c in ['специальные', 'биометрические']):
|
||||
cat_group = 'special_bio'
|
||||
else:
|
||||
cat_group = 'other'
|
||||
|
||||
level = lookup(cat_group, is_employees, is_large, threat_type)
|
||||
self.is_defence_var.set(str(level))
|
||||
|
||||
def _on_categories_or_count_changed(self):
|
||||
"""Обработчик изменения категорий ПДн или количества записей."""
|
||||
if self._current_is_idx is not None:
|
||||
self._auto_calc_defence_level()
|
||||
|
||||
# ==================== КОМИССИЯ ====================
|
||||
def _add_commission_row(self, role="Член комиссии", position="", fio=""):
|
||||
row = ttk.Frame(self._comm_members_frame)
|
||||
@@ -1421,28 +1217,15 @@ class DokoGenApp:
|
||||
self.is_name_entry.insert(0, isys.name or "")
|
||||
self.is_description_entry.delete(0, tk.END)
|
||||
self.is_description_entry.insert(0, isys.description or "")
|
||||
if hasattr(self, 'is_structure_entry'):
|
||||
self.is_structure_entry.delete(0, tk.END)
|
||||
self.is_structure_entry.insert(0, isys.structure or "")
|
||||
if hasattr(self, 'is_software_entry'):
|
||||
self.is_software_entry.delete(0, tk.END)
|
||||
self.is_software_entry.insert(0, isys.software or "")
|
||||
self.is_lan_var.set(isys.is_local_network)
|
||||
self.is_internet_var.set(isys.is_internet)
|
||||
self.is_threat_var.set(isys.threat_type or "3")
|
||||
self.is_defence_var.set(isys.defence_level or "4")
|
||||
for cat, var in self.category_vars:
|
||||
var.set(cat in isys.personal_data_category)
|
||||
# Заполняем кол-во записей ПДн
|
||||
if hasattr(self, 'is_pd_count_entry'):
|
||||
self.is_pd_count_entry.delete(0, tk.END)
|
||||
self.is_pd_count_entry.insert(0, isys.pd_count or 'менее 100 000')
|
||||
self.is_category_var.set(isys.category or "без категории")
|
||||
|
||||
# Списки
|
||||
for lb, items in [
|
||||
(self.pd_listbox, isys.personal_data_list or []),
|
||||
(self.pd_subjects_listbox, isys.pd_subjects_list or []),
|
||||
(self.pd_actions_listbox, isys.pd_actions_list or []),
|
||||
(self.defense_tools_listbox, isys.defense_tools_list or []),
|
||||
(self.users_listbox, isys.users_list or []),
|
||||
]:
|
||||
@@ -1471,13 +1254,7 @@ class DokoGenApp:
|
||||
description=src.description,
|
||||
is_local_network=src.is_local_network,
|
||||
is_internet=src.is_internet,
|
||||
defence_level=src.defence_level,
|
||||
subject_type=src.subject_type,
|
||||
threat_type=src.threat_type,
|
||||
personal_data_category=list(src.personal_data_category),
|
||||
personal_data_list=list(src.personal_data_list),
|
||||
pd_actions_list=list(src.pd_actions_list),
|
||||
pd_subjects_list=list(src.pd_subjects_list),
|
||||
category=src.category,
|
||||
defense_tools_list=list(src.defense_tools_list),
|
||||
users_list=list(src.users_list),
|
||||
)
|
||||
@@ -1497,16 +1274,6 @@ class DokoGenApp:
|
||||
self.is_listbox.selection_set(0)
|
||||
self._on_is_select()
|
||||
|
||||
def _add_pd_item(self):
|
||||
item = simpledialog.askstring("Добавить ПДн", "Введите персональные данные:")
|
||||
if item:
|
||||
self.pd_listbox.insert(tk.END, item)
|
||||
|
||||
def _add_pd_subjects_item(self):
|
||||
item = simpledialog.askstring("Добавить категорию", "Введите категорию субъектов:")
|
||||
if item:
|
||||
self.pd_subjects_listbox.insert(tk.END, item)
|
||||
|
||||
def _add_defense_item(self):
|
||||
item = simpledialog.askstring("Добавить СЗИ", "Введите средство защиты:")
|
||||
if item:
|
||||
@@ -1533,7 +1300,7 @@ class DokoGenApp:
|
||||
if not names:
|
||||
messagebox.showinfo(
|
||||
"Выбор пользователей",
|
||||
"Список пуст.\nЗаполните вкладку «Пользователи» (сотрудники с доступом к ПДн) "
|
||||
"Список пуст.\nЗаполните вкладку «Пользователи» (сотрудники с доступом к объектам КИИ) "
|
||||
"или добавьте пользователей в другой ИС."
|
||||
)
|
||||
return
|
||||
@@ -1607,44 +1374,6 @@ class DokoGenApp:
|
||||
|
||||
dialog.wait_window()
|
||||
|
||||
def _add_pd_action_item(self):
|
||||
item = simpledialog.askstring("Добавить действие", "Введите действие с ПДн:")
|
||||
if item:
|
||||
self.pd_actions_listbox.insert(tk.END, item)
|
||||
|
||||
def _select_from_actions_list(self):
|
||||
"""Выбор действий с ПДн из JSON словаря."""
|
||||
import json
|
||||
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'pd_actions.json')
|
||||
try:
|
||||
with open(dict_path, 'r', encoding='utf-8') as f:
|
||||
items = json.load(f)
|
||||
self._select_from_dialog("Выбор действий с персональными данными", items, self.pd_actions_listbox)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь: {e}")
|
||||
|
||||
def _select_from_pd_list(self):
|
||||
"""Выбор ПДн из JSON словаря."""
|
||||
import json
|
||||
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'pd_items.json')
|
||||
try:
|
||||
with open(dict_path, 'r', encoding='utf-8') as f:
|
||||
items = json.load(f)
|
||||
self._select_from_dialog("Выбор персональных данных", items, self.pd_listbox)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь: {e}")
|
||||
|
||||
def _select_from_subjects_list(self):
|
||||
"""Выбор категорий субъектов из JSON словаря."""
|
||||
import json
|
||||
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'pd_categories.json')
|
||||
try:
|
||||
with open(dict_path, 'r', encoding='utf-8') as f:
|
||||
items = json.load(f)
|
||||
self._select_from_dialog("Выбор категорий субъектов ПДн", items, self.pd_subjects_listbox)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь: {e}")
|
||||
|
||||
def _select_from_defense_list(self):
|
||||
"""Выбор СЗИ из JSON словаря."""
|
||||
import json
|
||||
@@ -1656,6 +1385,84 @@ class DokoGenApp:
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь: {e}")
|
||||
|
||||
# ==================== ОКВЭД ====================
|
||||
def _load_okved_dict(self):
|
||||
"""Загружает словарь ОКВЭД (dictionaries/okved.json)."""
|
||||
import json
|
||||
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'okved.json')
|
||||
try:
|
||||
with open(dict_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь ОКВЭД:\n{e}")
|
||||
return []
|
||||
|
||||
def _select_main_okved(self):
|
||||
"""Выбор основного ОКВЭД из словаря (всегда один)."""
|
||||
items = self._load_okved_dict()
|
||||
if not items:
|
||||
return
|
||||
current = self._company_entries.get('okved').get().strip() if 'okved' in self._company_entries else ''
|
||||
dialog = tk.Toplevel(self.root)
|
||||
dialog.title("Основной ОКВЭД")
|
||||
dialog.geometry("500x500")
|
||||
dialog.transient(self.root)
|
||||
dialog.grab_set()
|
||||
|
||||
frame = ttk.Frame(dialog, padding=10)
|
||||
frame.pack(fill=tk.BOTH, expand=True)
|
||||
ttk.Label(frame, text="Выберите основной ОКВЭД (один):",
|
||||
font=('Arial', 10, 'bold')).pack(anchor=tk.W, pady=(0, 5))
|
||||
|
||||
canvas = tk.Canvas(frame, highlightthickness=0)
|
||||
scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=canvas.yview)
|
||||
check_frame = ttk.Frame(canvas)
|
||||
check_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
|
||||
canvas.create_window((0, 0), window=check_frame, anchor="nw")
|
||||
canvas.configure(yscrollcommand=scrollbar.set)
|
||||
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
# Радио-кнопки: основной всегда один
|
||||
var = tk.StringVar(value=current)
|
||||
for item in items:
|
||||
ttk.Radiobutton(check_frame, text=item, variable=var,
|
||||
value=item).pack(anchor=tk.W, pady=1)
|
||||
|
||||
btn_frame = ttk.Frame(dialog, padding=10)
|
||||
btn_frame.pack(fill=tk.X)
|
||||
|
||||
def _apply():
|
||||
val = var.get()
|
||||
if val:
|
||||
entry = self._company_entries.get('okved')
|
||||
if entry:
|
||||
entry.delete(0, tk.END)
|
||||
entry.insert(0, val)
|
||||
dialog.destroy()
|
||||
|
||||
ttk.Button(btn_frame, text="✅ Применить", command=_apply).pack(side=tk.RIGHT, padx=2)
|
||||
ttk.Button(btn_frame, text="Отмена", command=dialog.destroy).pack(side=tk.RIGHT, padx=2)
|
||||
dialog.wait_window()
|
||||
|
||||
def _select_extra_okved(self):
|
||||
"""Выбор дополнительных ОКВЭД из словаря (можно несколько)."""
|
||||
items = self._load_okved_dict()
|
||||
if not items:
|
||||
return
|
||||
self._select_from_dialog("Выбор дополнительных ОКВЭД", items, self.okved_extra_listbox)
|
||||
|
||||
def _add_okved_manual(self):
|
||||
"""Ручное добавление дополнительного ОКВЭД."""
|
||||
item = simpledialog.askstring("Добавить ОКВЭД", "Введите код ОКВЭД:")
|
||||
if item:
|
||||
self.okved_extra_listbox.insert(tk.END, item.strip())
|
||||
|
||||
def _delete_okved_extra(self):
|
||||
sel = self.okved_extra_listbox.curselection()
|
||||
if sel:
|
||||
self.okved_extra_listbox.delete(sel[0])
|
||||
|
||||
# ==================== ШАБЛОНЫ ====================
|
||||
def select_templates(self):
|
||||
folder = filedialog.askdirectory(title="Папка шаблонов")
|
||||
@@ -1720,7 +1527,6 @@ class DokoGenApp:
|
||||
self._collect_commission()
|
||||
self._collect_is()
|
||||
self._collect_employees()
|
||||
self._collect_paper_documents()
|
||||
|
||||
# Проверка
|
||||
if not self._validate_before_generate():
|
||||
@@ -1939,7 +1745,24 @@ class DokoGenApp:
|
||||
if data.get('ispdnDepartment'):
|
||||
self.model.security_department = data['ispdnDepartment']
|
||||
|
||||
# Информационные системы
|
||||
# ОКВЭД (основной и дополнительные)
|
||||
if data.get('okved'):
|
||||
entry = self._company_entries.get('okved')
|
||||
if entry:
|
||||
entry.delete(0, tk.END)
|
||||
entry.insert(0, data['okved'])
|
||||
if data.get('okvedExtra'):
|
||||
if hasattr(self, 'okved_extra_listbox'):
|
||||
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()]
|
||||
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)
|
||||
|
||||
# Объекты КИИ
|
||||
if data.get('informationSystems'):
|
||||
self.model.information_systems.clear()
|
||||
for is_data in data['informationSystems']:
|
||||
@@ -1948,17 +1771,12 @@ class DokoGenApp:
|
||||
description=is_data.get('description', ''),
|
||||
is_local_network=is_data.get('is_has_lan', is_data.get('is_local_network', False)),
|
||||
is_internet=is_data.get('isInternet', False),
|
||||
pd_count=is_data.get('personalDataCount', 'менее 100 000'),
|
||||
personal_data_list=is_data.get('personalDataList', []),
|
||||
pd_subjects_list=is_data.get('pd_subjects_list', []),
|
||||
category=is_data.get('category', ''),
|
||||
defense_tools_list=is_data.get('defense_tools', []),
|
||||
users_list=is_data.get('usersList', []),
|
||||
personal_data_category=is_data.get('personalDataCategory', []),
|
||||
processing_modes=is_data.get('processing_mode', ''),
|
||||
purpose=is_data.get('description', ''),
|
||||
room=is_data.get('room', ''),
|
||||
purpose=is_data.get('purpose', is_data.get('description', '')),
|
||||
structure=is_data.get('structure', ''),
|
||||
pd_actions_list=is_data.get('pd_actions', []),
|
||||
software=is_data.get('software', ''),
|
||||
)
|
||||
self.model.information_systems.append(isys)
|
||||
self._refresh_is_listbox()
|
||||
@@ -2005,26 +1823,6 @@ class DokoGenApp:
|
||||
)
|
||||
self._update_emp_count()
|
||||
|
||||
# Бумажные документы (из «Названия документов» / «Место хранения»)
|
||||
if data.get('paperDocuments'):
|
||||
self.model.paper_documents_list.clear()
|
||||
names = [n.strip() for n in data['paperDocuments'].replace('\n', ';').split(';') if n.strip()]
|
||||
storages = [s.strip() for s in (data.get('paperStorage') or '').replace('\n', ';').split(';') if s.strip()]
|
||||
for i, name in enumerate(names):
|
||||
storage = storages[i] if i < len(storages) else (storages[0] if storages else '')
|
||||
self.model.paper_documents_list.append(
|
||||
PaperDocument(name=name, storage=storage)
|
||||
)
|
||||
# Обновляем GUI
|
||||
if hasattr(self, '_paper_entries'):
|
||||
for row_frame, _, _ in self._paper_entries:
|
||||
row_frame.destroy()
|
||||
self._paper_entries.clear()
|
||||
for p in self.model.paper_documents_list:
|
||||
self._add_paper_row(p.name, p.storage)
|
||||
self._update_paper_count()
|
||||
self.log(f" Бумажные документы: {len(self.model.paper_documents_list)} шт.")
|
||||
|
||||
self.log("✅ Импорт применён")
|
||||
self.root.after(0, _apply)
|
||||
|
||||
@@ -2069,7 +1867,6 @@ class DokoGenApp:
|
||||
self._collect_commission()
|
||||
self._collect_is()
|
||||
self._collect_employees()
|
||||
self._collect_paper_documents()
|
||||
try:
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.model.to_dict(), f, ensure_ascii=False, indent=2)
|
||||
@@ -2095,7 +1892,6 @@ class DokoGenApp:
|
||||
self._collect_commission()
|
||||
self._collect_is()
|
||||
self._collect_employees()
|
||||
self._collect_paper_documents()
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self._autosave_path), exist_ok=True)
|
||||
with open(self._autosave_path, 'w', encoding='utf-8') as f:
|
||||
|
||||
Reference in New Issue
Block a user