feat: пометка ★ для ОКВЭД, подходящих под 187-ФЗ (из 360-р)

- dictionaries/okved_187.json: 78 кодов ОКВЭД из Распоряжения ПРФ № 360-р
  (виды деятельности субъектов КИИ по типовым отраслевым объектам)
- okved.json: добавлен недостающий код 09.90 (всего 267)
- ui.py: _load_okved_187_codes/_is_okved_187 — проверка по коду/подкоду
- окно выбора основного ОКВЭД: радио-кнопки с пометкой «★ ...»
- окно выбора доп. ОКВЭД: display_map со звёздочками (в значение
  звёздочка не попадает), легенда «★ — код подходит под 187-ФЗ»
- при применении сохраняется чистый код без ★ (импорт/документы не ломаются)
This commit is contained in:
2026-08-04 23:07:28 +04:00
parent 29ba4b0532
commit 4bffd0e08d
3 changed files with 392 additions and 272 deletions
+2 -1
View File
@@ -264,5 +264,6 @@
"96.0 Деятельность по предоставлению прочих персональных услуг", "96.0 Деятельность по предоставлению прочих персональных услуг",
"97.0 Деятельность домашних хозяйств с наемными работниками", "97.0 Деятельность домашних хозяйств с наемными работниками",
"98.0 Деятельность домашних хозяйств по производству товаров для собственного использования", "98.0 Деятельность домашних хозяйств по производству товаров для собственного использования",
"99.0 Деятельность экстерриториальных организаций и органов" "99.0 Деятельность экстерриториальных организаций и органов",
"09.90 Предоставление услуг в других областях добычи полезных ископаемых"
] ]
+80
View File
@@ -0,0 +1,80 @@
[
"05.20",
"06.1",
"06.2",
"09.90",
"19.20",
"20.13",
"22.21",
"24.46",
"25.30",
"25.40",
"25.99",
"26.20",
"26.30",
"26.51",
"27.90",
"28.13",
"28.14",
"28.99",
"30.11",
"33.11",
"33.12",
"33.20",
"35.11",
"35.12",
"35.13",
"35.14",
"35.3",
"35.30",
"38.12",
"38.22",
"41.20",
"45.20",
"46.46",
"47.73",
"49",
"49.10",
"49.20",
"49.32",
"49.39",
"49.4",
"49.41",
"49.42",
"49.50",
"50",
"50.20",
"50.30",
"50.40",
"51",
"51.10",
"51.21",
"52",
"52.10",
"52.2",
"52.22",
"52.23",
"52.24",
"61",
"61.1",
"61.20",
"61.3",
"62.01",
"62.09",
"63",
"63.11",
"64",
"64.11",
"71.12",
"71.20",
"72",
"72.19",
"74.90",
"84",
"84.11",
"84.13",
"84.30",
"86.1",
"86.2",
"86.90"
]
+44 -5
View File
@@ -3060,10 +3060,13 @@ class DokoGenApp:
listbox.delete(sel[0]) listbox.delete(sel[0])
# ==================== ВЫБОР ИЗ СПИСКА (словари) ==================== # ==================== ВЫБОР ИЗ СПИСКА (словари) ====================
def _select_from_dialog(self, title: str, items: list, listbox, allow_refine=False): def _select_from_dialog(self, title: str, items: list, listbox, allow_refine=False,
display_map=None):
"""Открывает окно с чекбоксами для выбора элементов из списка. """Открывает окно с чекбоксами для выбора элементов из списка.
allow_refine=True — показывать кнопку «✏️ Уточнить модель/кол-во». allow_refine=True — показывать кнопку «✏️ Уточнить модель/кол-во».
display_map — dict {элемент: текст_для_отображения} (пометки, например «★»),
при применении сохраняется сам элемент (без пометки).
""" """
dialog = tk.Toplevel(self.root) dialog = tk.Toplevel(self.root)
dialog.title(title) dialog.title(title)
@@ -3099,10 +3102,14 @@ class DokoGenApp:
items_map = {} items_map = {}
for item in items: for item in items:
var = tk.BooleanVar(value=item in existing) var = tk.BooleanVar(value=item in existing)
cb = ttk.Checkbutton(check_frame, text=item, variable=var) label = (display_map or {}).get(item, item)
cb = ttk.Checkbutton(check_frame, text=label, variable=var)
cb.pack(anchor=tk.W, pady=1) cb.pack(anchor=tk.W, pady=1)
vars[item] = var vars[item] = var
items_map[item] = (var, cb) items_map[item] = (var, cb)
if display_map:
ttk.Label(check_frame, text="★ — код подходит под 187-ФЗ (из Распоряжения ПРФ № 360-р)",
foreground='#1a6e1a').pack(anchor=tk.W, pady=(4, 0))
# Фильтр по поиску: скрываем неподходящие чекбоксы # Фильтр по поиску: скрываем неподходящие чекбоксы
def _apply_search(*_): def _apply_search(*_):
@@ -3238,6 +3245,32 @@ class DokoGenApp:
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь ОКВЭД:\n{e}") messagebox.showerror("Ошибка", f"Не удалось загрузить словарь ОКВЭД:\n{e}")
return [] return []
def _load_okved_187_codes(self):
"""Коды ОКВЭД, подходящие под 187-ФЗ (из Распоряжения ПРФ № 360-р)."""
import json
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'okved_187.json')
try:
with open(dict_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return []
def _is_okved_187(self, item):
"""Подходит ли строка ОКВЭД под 187-ФЗ (код совпадает/является подкодом из 360-р)."""
import re
m = re.match(r'^(\d+(?:\.\d+)*)', item or '')
if not m:
return False
code = m.group(1)
codes = getattr(self, '_okved_187_cache', None)
if codes is None:
codes = self._load_okved_187_codes()
self._okved_187_cache = codes
for c in codes:
if code == c or code.startswith(c + '.') or c.startswith(code + '.'):
return True
return False
def _select_main_okved(self): def _select_main_okved(self):
"""Выбор основного ОКВЭД из словаря (всегда один).""" """Выбор основного ОКВЭД из словаря (всегда один)."""
items = self._load_okved_dict() items = self._load_okved_dict()
@@ -3264,11 +3297,14 @@ class DokoGenApp:
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y) scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Радио-кнопки: основной всегда один # Радио-кнопки: основной всегда один (★ — подходит под 187-ФЗ)
var = tk.StringVar(value=current) var = tk.StringVar(value=current)
for item in items: for item in items:
ttk.Radiobutton(check_frame, text=item, variable=var, label = ('' + item) if self._is_okved_187(item) else item
ttk.Radiobutton(check_frame, text=label, variable=var,
value=item).pack(anchor=tk.W, pady=1) value=item).pack(anchor=tk.W, pady=1)
ttk.Label(check_frame, text="★ — код подходит под 187-ФЗ (из Распоряжения ПРФ № 360-р)",
foreground='#1a6e1a').pack(anchor=tk.W, pady=(4, 0))
btn_frame = ttk.Frame(dialog, padding=10) btn_frame = ttk.Frame(dialog, padding=10)
btn_frame.pack(fill=tk.X) btn_frame.pack(fill=tk.X)
@@ -3291,7 +3327,10 @@ class DokoGenApp:
items = self._load_okved_dict() items = self._load_okved_dict()
if not items: if not items:
return return
self._select_from_dialog("Выбор дополнительных ОКВЭД", items, self.okved_extra_listbox) display_map = {item: ('' + item) if self._is_okved_187(item) else item
for item in items}
self._select_from_dialog("Выбор дополнительных ОКВЭД", items, self.okved_extra_listbox,
display_map=display_map)
def _add_okved_manual(self): def _add_okved_manual(self):
"""Ручное добавление дополнительного ОКВЭД.""" """Ручное добавление дополнительного ОКВЭД."""