fix: кнопки /🧮 в окне ПП 127 — UnboundLocalError/NameError

Кнопки command=_auto_fill_criteria и lambda _open_calc_dialog создавались
ДО определения этих функций внутри _open_criteria_127_dialog — Python
бросал UnboundLocalError/NameError при нажатии. Определения функций
перенесены выше мест использования (проверено: def < кнопка для всех).
This commit is contained in:
2026-08-04 23:22:35 +04:00
parent c1ad496319
commit acb7aeaa64
+201 -199
View File
@@ -2140,6 +2140,200 @@ class DokoGenApp:
f"считает его применимым.",
foreground='#1a6e1a', wraplength=920, justify=tk.LEFT).pack(anchor=tk.W, pady=(0, 8))
def _open_calc_dialog(num, name, var, combo):
"""Авторасчёт показателя 8 или 9 по «Рекомендациям по оценке показателей
критериев экономической значимости» ФСТЭК (2021). Показывает расчёт
и предлагает категорию как рекомендацию."""
import re as _re2
def _num(s):
s = str(s or '').replace(' ', '').replace('\u00a0', '')
try:
return float(s)
except Exception:
return None
# Данные организации (прибыль/налог) из модели
profit = getattr(self.model, 'profit', '') or ''
profit_tax = getattr(self.model, 'profit_tax', '') or ''
w = tk.Toplevel(self.root)
w.title(f"🧮 Авторасчёт показателя {num}{name[:60]}")
w.geometry("720x560")
w.transient(self.root)
frm = ttk.Frame(w, padding=10)
frm.pack(fill=tk.BOTH, expand=True)
if num == '9':
ttk.Label(frm, text="Показатель 9 — ущерб бюджетам РФ (применим ко всем субъектам КИИ)",
font=('Arial', 10, 'bold')).pack(anchor=tk.W)
ttk.Label(frm, text="Формула: №9 = ΔБ / Бусредн × 100%, где ΔБ = ΔНП + ΔНДПИ + ΔДИВ + ΔВСО + ΔНС\n"
"ΔНП = ΔДоходов × (налог на прибыль / прибыль); если ΔДоходов > прибыли — весь налог.",
foreground='gray', wraplength=660, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
def _row(label, key, default=''):
ttk.Label(frm, text=label).pack(anchor=tk.W, pady=(4, 0))
e = ttk.Entry(frm, width=60)
e.insert(0, str(default or ''))
e.pack(fill=tk.X)
return e
e_profit = _row("Прибыль субъекта КИИ за прошедший год (руб.):", 'profit', profit)
e_tax = _row("Налог на прибыль за прошедший год (руб.):", 'tax', profit_tax)
e_dincome = _row("Снижение уровня доходов ΔДоходов (руб., наихудший сценарий):", 'dincome', '')
e_budget = _row("Доход федерального бюджета, усреднённый за 3 года Бусредн (руб.):", 'budget',
'21228053357700')
ttk.Label(frm, text="Прочие составляющие ΔБ (если применимы, иначе 0):",
font=('', 9, 'bold')).pack(anchor=tk.W, pady=(8, 0))
e_ndpi = _row("Снижение выплат от налога на добычу ПИ ΔНДПИ (руб.):", 'ndpi', '0')
e_div = _row("Снижение выплат от дивидендов ΔДИВ (руб.):", 'div', '0')
e_vso = _row("Снижение выплат сторонних организаций ΔВСО (руб.):", 'vso', '0')
e_ns = _row("Снижение сборов ΔНС (руб.):", 'ns', '0')
result = tk.Text(frm, height=10, wrap=tk.WORD, state=tk.NORMAL)
result.pack(fill=tk.BOTH, expand=True, pady=(8, 0))
else:
ttk.Label(frm, text="Показатель 8 — ущерб субъекту КИИ",
font=('Arial', 10, 'bold')).pack(anchor=tk.W)
ttk.Label(frm, text="Применим только для: госкорпорации, ГУП, госкомпании, стратегического АО/предприятия "
"(Указ Президента № 1009).\nФормула: №8 = ΔДоходов / Дусредн × 100%.",
foreground='gray', wraplength=660, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
def _row(label, key, default=''):
ttk.Label(frm, text=label).pack(anchor=tk.W, pady=(4, 0))
e = ttk.Entry(frm, width=60)
e.insert(0, str(default or ''))
e.pack(fill=tk.X)
return e
e_profit = e_tax = None
# Доходы за 5 лет — из данных организации (поля «Доходы за 5 лет»)
_incs = [_num(getattr(self.model, f'income_year_{i}', '') or '') for i in range(1, 6)]
_incs = [x for x in _incs if x is not None]
_d_avg = (sum(_incs) / len(_incs)) if _incs else None
_d_loss = _num(getattr(self.model, 'income_loss', '') or '')
e_dincome = _row("Суммарный экономический ущерб от нарушения критических процессов (руб.):",
'dincome', f"{_d_loss:,.0f}" if _d_loss else '')
e_budget = _row("Доходы субъекта КИИ, усреднённые за 5 лет (руб.):",
'budget', f"{_d_avg:,.0f}" if _d_avg else '')
result = tk.Text(frm, height=10, wrap=tk.WORD, state=tk.NORMAL)
result.pack(fill=tk.BOTH, expand=True, pady=(8, 0))
def _calc():
lines = []
if num == '8':
d = _num(e_dincome.get())
b = _num(e_budget.get())
if d is None or b is None or b <= 0:
result.delete('1.0', tk.END)
result.insert('1.0', "Заполните оба поля (ΔДоходов и Дусредн).")
return
val = d / b * 100
lines.append(f"Суммарный экономический ущерб от нарушения критических процессов: {d:,.0f} руб.")
lines.append(f"Доходы субъекта КИИ, усреднённые за 5 лет: {b:,.0f} руб.")
lines.append(f"№8 = ущерб / доходы за 5 лет × 100% = {d:,.0f} / {b:,.0f} × 100 = {val:.2f} %")
if val > 20:
cat = 'I'
elif val > 10:
cat = 'II'
elif val >= 1:
cat = 'III'
else:
cat = None
if cat:
lines.append(f"Категория значимости: {cat} (более 20% → I; 10-20% → II; 1-10% → III)")
else:
lines.append("Значение менее 1% — ни одна категория не присваивается («—»).")
else:
p = _num(e_profit.get())
t = _num(e_tax.get())
d = _num(e_dincome.get())
b = _num(e_budget.get())
if d is None or b is None or b <= 0:
result.delete('1.0', tk.END)
result.insert('1.0', "Заполните ΔДоходов и Бусредн (прибыль/налог — из данных организации).")
return
if p and t is not None and p > 0:
knp = t / p
dnp = d * knp
if d > p:
dnp = t
lines.append(f"Коэффициент КНП = налог на прибыль / прибыль = {t:,.0f} / {p:,.0f} = {knp:.4f}")
lines.append(f"ΔНП = ΔДоходов × КНП = {d:,.0f} × {knp:.4f} = {dnp:,.1f} руб."
+ (" (ΔДоходов > прибыли → весь налог)" if d > p else ""))
else:
dnp = 0
lines.append("Прибыль/налог не заполнены — ΔНП принят равным 0 (расчёт неполный).")
ndpi = _num(e_ndpi.get()) or 0
div = _num(e_div.get()) or 0
vso = _num(e_vso.get()) or 0
ns = _num(e_ns.get()) or 0
db = dnp + ndpi + div + vso + ns
lines.append(f"ΔНДПИ = {ndpi:,.0f} руб., ΔДИВ = {div:,.0f} руб., ΔВСО = {vso:,.0f} руб., ΔНС = {ns:,.0f} руб.")
lines.append(f"ΔБ = ΔНП + ΔНДПИ + ΔДИВ + ΔВСО + ΔНС = {db:,.1f} руб.")
lines.append(f"Бусредн (доход федерального бюджета за 3 года): {b:,.0f} руб.")
val = db / b * 100
lines.append(f"№9 = ΔБ / Бусредн × 100% = {db:,.1f} / {b:,.0f} × 100 = {val:.6f} %")
if val > 0.001:
cat = 'I'
elif val > 0.0006:
cat = 'II'
elif val > 0.0003:
cat = 'III'
else:
cat = None
if cat:
lines.append(f"Категория значимости: {cat} (более 0,001% → I; 0,0006-0,001% → II; 0,0003-0,0006% → III)")
else:
lines.append("Значение ≤ 0,0003% — ни одна категория не присваивается («—»).")
result.delete('1.0', tk.END)
result.insert('1.0', '\n'.join(lines))
def _apply():
_calc()
txt = result.get('1.0', 'end-1c').strip()
lines = txt.split('\n')
cat_line = next((l for l in lines if l.startswith('Категория значимости:')), '')
cat = None
if 'Категория значимости: I' in cat_line:
cat = 'I'
elif 'Категория значимости: II' in cat_line:
cat = 'II'
elif 'Категория значимости: III' in cat_line:
cat = 'III'
if cat:
# Выбрать в комбобоксе соответствующую категорию
opts = list(combo['values'])
target = next((o for o in opts if o.startswith(f"{cat} категория")), None)
if target:
var.set(target)
# Обоснование с расчётом
prefix = f"Показатель {num}. {name}{cat} категория."
full = prefix + "\n" + 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)
else:
var.set('')
prefix = f"Показатель {num}. {name} — применим, категория не присвоена (—)."
full = prefix + "\n" + 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)
w.destroy()
btns = ttk.Frame(frm)
btns.pack(fill=tk.X, pady=(8, 0))
ttk.Button(btns, text="🧮 Рассчитать", command=_calc).pack(side=tk.LEFT, padx=2)
ttk.Button(btns, text="✔️ Применить категорию", command=_apply).pack(side=tk.LEFT, padx=2)
ttk.Button(btns, text="Отмена", command=w.destroy).pack(side=tk.LEFT, padx=2)
rows = [] # (num, name, values{III:..,II:..,I:..}, var, applicable)
_combos = {} # num -> комбобокс (для автозаполнения)
@@ -2338,14 +2532,6 @@ class DokoGenApp:
self.log(f"📋 Критерии значимости (ПП 127) сохранены: категория {cat}, показателей: {len(chosen)}")
dialog.destroy()
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 по данным организации/объекта (рекомендации).
@@ -2457,198 +2643,14 @@ class DokoGenApp:
if left else "\n\nВсе применимые показатели заполнены!") +
"\n\nКатегория объекта пересчитана по наивысшей из выбранных.")
def _open_calc_dialog(num, name, var, combo):
"""Авторасчёт показателя 8 или 9 по «Рекомендациям по оценке показателей
критериев экономической значимости» ФСТЭК (2021). Показывает расчёт
и предлагает категорию как рекомендацию."""
import re as _re2
def _num(s):
s = str(s or '').replace(' ', '').replace('\u00a0', '')
try:
return float(s)
except Exception:
return None
# Данные организации (прибыль/налог) из модели
profit = getattr(self.model, 'profit', '') or ''
profit_tax = getattr(self.model, 'profit_tax', '') or ''
w = tk.Toplevel(self.root)
w.title(f"🧮 Авторасчёт показателя {num}{name[:60]}")
w.geometry("720x560")
w.transient(self.root)
frm = ttk.Frame(w, padding=10)
frm.pack(fill=tk.BOTH, expand=True)
if num == '9':
ttk.Label(frm, text="Показатель 9 — ущерб бюджетам РФ (применим ко всем субъектам КИИ)",
font=('Arial', 10, 'bold')).pack(anchor=tk.W)
ttk.Label(frm, text="Формула: №9 = ΔБ / Бусредн × 100%, где ΔБ = ΔНП + ΔНДПИ + ΔДИВ + ΔВСО + ΔНС\n"
"ΔНП = ΔДоходов × (налог на прибыль / прибыль); если ΔДоходов > прибыли — весь налог.",
foreground='gray', wraplength=660, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
def _row(label, key, default=''):
ttk.Label(frm, text=label).pack(anchor=tk.W, pady=(4, 0))
e = ttk.Entry(frm, width=60)
e.insert(0, str(default or ''))
e.pack(fill=tk.X)
return e
e_profit = _row("Прибыль субъекта КИИ за прошедший год (руб.):", 'profit', profit)
e_tax = _row("Налог на прибыль за прошедший год (руб.):", 'tax', profit_tax)
e_dincome = _row("Снижение уровня доходов ΔДоходов (руб., наихудший сценарий):", 'dincome', '')
e_budget = _row("Доход федерального бюджета, усреднённый за 3 года Бусредн (руб.):", 'budget',
'21228053357700')
ttk.Label(frm, text="Прочие составляющие ΔБ (если применимы, иначе 0):",
font=('', 9, 'bold')).pack(anchor=tk.W, pady=(8, 0))
e_ndpi = _row("Снижение выплат от налога на добычу ПИ ΔНДПИ (руб.):", 'ndpi', '0')
e_div = _row("Снижение выплат от дивидендов ΔДИВ (руб.):", 'div', '0')
e_vso = _row("Снижение выплат сторонних организаций ΔВСО (руб.):", 'vso', '0')
e_ns = _row("Снижение сборов ΔНС (руб.):", 'ns', '0')
result = tk.Text(frm, height=10, wrap=tk.WORD, state=tk.NORMAL)
result.pack(fill=tk.BOTH, expand=True, pady=(8, 0))
else:
ttk.Label(frm, text="Показатель 8 — ущерб субъекту КИИ",
font=('Arial', 10, 'bold')).pack(anchor=tk.W)
ttk.Label(frm, text="Применим только для: госкорпорации, ГУП, госкомпании, стратегического АО/предприятия "
"(Указ Президента № 1009).\nФормула: №8 = ΔДоходов / Дусредн × 100%.",
foreground='gray', wraplength=660, justify=tk.LEFT).pack(anchor=tk.W, pady=(2, 6))
def _row(label, key, default=''):
ttk.Label(frm, text=label).pack(anchor=tk.W, pady=(4, 0))
e = ttk.Entry(frm, width=60)
e.insert(0, str(default or ''))
e.pack(fill=tk.X)
return e
e_profit = e_tax = None
# Доходы за 5 лет — из данных организации (поля «Доходы за 5 лет»)
_incs = [_num(getattr(self.model, f'income_year_{i}', '') or '') for i in range(1, 6)]
_incs = [x for x in _incs if x is not None]
_d_avg = (sum(_incs) / len(_incs)) if _incs else None
_d_loss = _num(getattr(self.model, 'income_loss', '') or '')
e_dincome = _row("Суммарный экономический ущерб от нарушения критических процессов (руб.):",
'dincome', f"{_d_loss:,.0f}" if _d_loss else '')
e_budget = _row("Доходы субъекта КИИ, усреднённые за 5 лет (руб.):",
'budget', f"{_d_avg:,.0f}" if _d_avg else '')
result = tk.Text(frm, height=10, wrap=tk.WORD, state=tk.NORMAL)
result.pack(fill=tk.BOTH, expand=True, pady=(8, 0))
def _calc():
lines = []
if num == '8':
d = _num(e_dincome.get())
b = _num(e_budget.get())
if d is None or b is None or b <= 0:
result.delete('1.0', tk.END)
result.insert('1.0', "Заполните оба поля (ΔДоходов и Дусредн).")
return
val = d / b * 100
lines.append(f"Суммарный экономический ущерб от нарушения критических процессов: {d:,.0f} руб.")
lines.append(f"Доходы субъекта КИИ, усреднённые за 5 лет: {b:,.0f} руб.")
lines.append(f"№8 = ущерб / доходы за 5 лет × 100% = {d:,.0f} / {b:,.0f} × 100 = {val:.2f} %")
if val > 20:
cat = 'I'
elif val > 10:
cat = 'II'
elif val >= 1:
cat = 'III'
else:
cat = None
if cat:
lines.append(f"Категория значимости: {cat} (более 20% → I; 10-20% → II; 1-10% → III)")
else:
lines.append("Значение менее 1% — ни одна категория не присваивается («—»).")
else:
p = _num(e_profit.get())
t = _num(e_tax.get())
d = _num(e_dincome.get())
b = _num(e_budget.get())
if d is None or b is None or b <= 0:
result.delete('1.0', tk.END)
result.insert('1.0', "Заполните ΔДоходов и Бусредн (прибыль/налог — из данных организации).")
return
if p and t is not None and p > 0:
knp = t / p
dnp = d * knp
if d > p:
dnp = t
lines.append(f"Коэффициент КНП = налог на прибыль / прибыль = {t:,.0f} / {p:,.0f} = {knp:.4f}")
lines.append(f"ΔНП = ΔДоходов × КНП = {d:,.0f} × {knp:.4f} = {dnp:,.1f} руб."
+ (" (ΔДоходов > прибыли → весь налог)" if d > p else ""))
else:
dnp = 0
lines.append("Прибыль/налог не заполнены — ΔНП принят равным 0 (расчёт неполный).")
ndpi = _num(e_ndpi.get()) or 0
div = _num(e_div.get()) or 0
vso = _num(e_vso.get()) or 0
ns = _num(e_ns.get()) or 0
db = dnp + ndpi + div + vso + ns
lines.append(f"ΔНДПИ = {ndpi:,.0f} руб., ΔДИВ = {div:,.0f} руб., ΔВСО = {vso:,.0f} руб., ΔНС = {ns:,.0f} руб.")
lines.append(f"ΔБ = ΔНП + ΔНДПИ + ΔДИВ + ΔВСО + ΔНС = {db:,.1f} руб.")
lines.append(f"Бусредн (доход федерального бюджета за 3 года): {b:,.0f} руб.")
val = db / b * 100
lines.append(f"№9 = ΔБ / Бусредн × 100% = {db:,.1f} / {b:,.0f} × 100 = {val:.6f} %")
if val > 0.001:
cat = 'I'
elif val > 0.0006:
cat = 'II'
elif val > 0.0003:
cat = 'III'
else:
cat = None
if cat:
lines.append(f"Категория значимости: {cat} (более 0,001% → I; 0,0006-0,001% → II; 0,0003-0,0006% → III)")
else:
lines.append("Значение ≤ 0,0003% — ни одна категория не присваивается («—»).")
result.delete('1.0', tk.END)
result.insert('1.0', '\n'.join(lines))
def _apply():
_calc()
txt = result.get('1.0', 'end-1c').strip()
lines = txt.split('\n')
cat_line = next((l for l in lines if l.startswith('Категория значимости:')), '')
cat = None
if 'Категория значимости: I' in cat_line:
cat = 'I'
elif 'Категория значимости: II' in cat_line:
cat = 'II'
elif 'Категория значимости: III' in cat_line:
cat = 'III'
if cat:
# Выбрать в комбобоксе соответствующую категорию
opts = list(combo['values'])
target = next((o for o in opts if o.startswith(f"{cat} категория")), None)
if target:
var.set(target)
# Обоснование с расчётом
prefix = f"Показатель {num}. {name}{cat} категория."
full = prefix + "\n" + 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)
else:
var.set('')
prefix = f"Показатель {num}. {name} — применим, категория не присвоена (—)."
full = prefix + "\n" + 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)
w.destroy()
btns = ttk.Frame(frm)
btns.pack(fill=tk.X, pady=(8, 0))
ttk.Button(btns, text="🧮 Рассчитать", command=_calc).pack(side=tk.LEFT, padx=2)
ttk.Button(btns, text="✔️ Применить категорию", command=_apply).pack(side=tk.LEFT, padx=2)
ttk.Button(btns, text="Отмена", command=w.destroy).pack(side=tk.LEFT, padx=2)
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)
dialog.wait_window()