diff --git a/dokogen/ui.py b/dokogen/ui.py index 0f96ec9..3eb63b6 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -2115,6 +2115,7 @@ class DokoGenApp: foreground='#1a6e1a', wraplength=920, justify=tk.LEFT).pack(anchor=tk.W, pady=(0, 8)) rows = [] # (num, name, values{III:..,II:..,I:..}, var, applicable) + _combos = {} # num -> комбобокс (для автозаполнения) import re as _re for crit in criteria: @@ -2150,6 +2151,7 @@ class DokoGenApp: combo = ttk.Combobox(row, textvariable=var, state='readonly', width=34, values=options) combo.pack(side=tk.RIGHT, padx=6) + _combos[num] = combo # Кнопка «🧮» — авторасчёт показателя (п. 8 «ущерб субъекту КИИ» и п. 9 «ущерб бюджетам») if grp_num in ('8', '9'): @@ -2312,10 +2314,110 @@ class DokoGenApp: btn_frame = ttk.Frame(body) btn_frame.pack(fill=tk.X, pady=(8, 0)) + ttk.Button(btn_frame, text="⚡ Заполнить автоматически (по данным)", + command=_auto_fill_criteria).pack(side=tk.LEFT, padx=2) ttk.Button(btn_frame, text="🧮 Рассчитать категорию", command=_calc).pack(side=tk.LEFT, padx=2) ttk.Button(btn_frame, text="💾 Сохранить", command=_save).pack(side=tk.LEFT, padx=2) ttk.Button(btn_frame, text="Отмена", command=dialog.destroy).pack(side=tk.LEFT, padx=2) + def _auto_fill_criteria(): + """⚡ Автозаполнение показателей ПП 127 по данным организации/объекта (рекомендации). + + - показатели, не применимые к сфере — уже «не применим» (при открытии); + - п. 8: если организация не госкорпорация/ГУП/госкомпания/стратегическое АО + → «не применим» с обоснованием; + - п. 9: если есть прибыль и налог на прибыль → расчёт по «Рекомендациям» + ФСТЭК 2021 (КНП → ΔНП → №9 → категория), оператор вводит ΔДоходов; + - остальные применимые показатели — оставляются оператору. + """ + def _num2(s): + s = str(s or '').replace(' ', '').replace('\u00a0', '') + try: + return float(s) + except Exception: + return None + + org = ' '.join([getattr(self.model, 'full_name', '') or '', + getattr(self.model, 'short_name', '') or '']).lower() + is_special = any(k in org for k in [ + 'госкорпорац', 'государственная корпорация', 'гуп', 'фгуп', + 'государственное унитарное предприятие', 'государственная компания', + 'росатом', 'роскосмос', 'ростех', 'вэб']) + filled = [] + for num, name, values, var, applicable_here in rows: + if not applicable_here: + continue # уже «не применим» (заблокирован) + m = _re.match(r'(\d+)', num) + g = m.group(1) if m else num + if g == '8': + if not is_special: + var.set('не применим') + txt = _just_text(num) or ('Субъект КИИ не относится к типам предприятий, ' + 'указанных в п. 8 перечня показателей критериев ' + 'значимости объектов КИИ.') + prefix = f"Показатель {num}. {name} — не применим." + full = prefix + " " + txt + cur = justify_txt.get('1.0', 'end-1c').strip() + if prefix not in cur: + if cur: + justify_txt.insert('end', '\n' + full) + else: + justify_txt.insert('1.0', full) + filled.append(f"п. {num}: не применим (тип организации)") + elif g == '9': + profit = _num2(getattr(self.model, 'profit', '') or '') + tax = _num2(getattr(self.model, 'profit_tax', '') or '') + if profit and tax: + dincome = simpledialog.askfloat( + "Показатель 9 — ущерб бюджетам", + "Снижение уровня доходов субъекта КИИ ΔДоходов (руб.):\n" + "(наихудший сценарий компьютерной атаки на объект)", + parent=dialog, minvalue=0) + if dincome is None: + continue + budget = 21228053357700.0 # Бусредн (доход федерального бюджета за 3 года) + knp = tax / profit + dnp = dincome * knp + if dincome > profit: + dnp = tax + val = dnp / budget * 100 + if val > 0.001: + cat = 'I' + elif val > 0.0006: + cat = 'II' + elif val > 0.0003: + cat = 'III' + else: + cat = None + combo = _combos.get(num) + if cat and combo is not None: + target = next((o for o in combo['values'] + if str(o).startswith(f"{cat} категория")), None) + if target: + var.set(target) + calc_txt = ( + f"Коэффициент КНП = налог/прибыль = {tax:,.0f} / {profit:,.0f} = {knp:.4f}\n" + f"ΔНП = ΔДоходов × КНП = {dincome:,.0f} × {knp:.4f} = {dnp:,.1f} руб.\n" + f"Бусредн (доход федерального бюджета за 3 года) = {budget:,.0f} руб.\n" + f"№9 = ΔБ / Бусредн × 100% = {dnp:,.1f} / {budget:,.0f} × 100 = {val:.6f} %\n" + f"Категория значимости: {cat or '— (не присваивается)'}" + ) + prefix = f"Показатель {num}. {name} — рекомендация по расчёту:" + full = prefix + "\n" + calc_txt + cur = justify_txt.get('1.0', 'end-1c').strip() + if prefix not in cur: + if cur: + justify_txt.insert('end', '\n' + full) + else: + justify_txt.insert('1.0', full) + filled.append(f"п. {num}: №9 = {val:.6f}% → {cat or '—'}") + messagebox.showinfo( + "⚡ Автозаполнение ПП 127", + "Заполнено автоматически (рекомендации):\n" + + ("\n".join(filled) if filled else "— нет") + + "\n\nОстальные применимые показатели заполните вручную.\n" + "Категория объекта будет рассчитана по наивысшей из выбранных.") + def _open_calc_dialog(num, name, var, combo): """Авторасчёт показателя 8 или 9 по «Рекомендациям по оценке показателей критериев экономической значимости» ФСТЭК (2021). Показывает расчёт