/
kuzminaa
/
PDM
Обзор
Документация
Войти
/
kuzminaa
/
PDM
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/assembly_window.py
198 строк
10 KB
kuzminaa
refactor: migrate modules to src/, add logging system, add assembly workshop
29 июл 2026, 23:52
29 июл 2026, 23:52
a5323aa
Код
Авторство
О чём код?
# -*- coding: utf-8 -*- """ Модуль: assembly_window.py Назначение: Графическое окно верификации и полуавтоматической сборки сканов КД. Платформа: ОС Debian 13 (Чистый Tkinter, интеграция с движками СЭД). """ import os import logging import datetime import tkinter as tk from tkinter import ttk, messagebox, filedialog from .assembly_engine import SmartAssemblyEngine from . import db_manager as db logger = logging.getLogger(__name__) class AssemblyWorkshopWindow(tk.Toplevel): def __init__(self, parent): super().__init__(parent.root) self.parent = parent self.db = db self.title("Интеллектуальный сборочный цех сканов ЕСКД") self.geometry("1100x680") self.minsize(1000, 600) self.transient(parent.root) self.grab_set() self.engine = SmartAssemblyEngine() self._build_ui() def _build_ui(self): # Верхняя командная панель top_frame = ttk.Frame(self, padding=10) top_frame.pack(fill=tk.X) ttk.Button(top_frame, text="📁 Выбрать папку со сканами...", command=self.select_source_folder).pack(side=tk.LEFT, padx=5) self.lbl_folder_status = ttk.Label(top_frame, text="Папка не выбрана", font=("Helvetica", 9, "italic")) self.lbl_folder_status.pack(side=tk.LEFT, padx=10) # Главный контейнер (Сплиттер: Таблица результатов | Панель коллизий и Превью) main_paned = ttk.Panedwindow(self, orient=tk.HORIZONTAL) main_paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # Левая часть: Таблица распознанных КД документов left_frame = ttk.LabelFrame(main_paned, text=" Автоматически сгруппированные документы ЕСКД ", padding=5) main_paned.add(left_frame, weight=3) columns = ("des", "total", "status") self.tree_docs = ttk.Treeview(left_frame, columns=columns, show="headings") self.tree_docs.heading("des", text="Обозначение КД") self.tree_docs.heading("total", text="Листов (Штамп / Факт)") self.tree_docs.heading("status", text="Статус валидации состава") self.tree_docs.column("des", width=180, anchor=tk.W) self.tree_docs.column("total", width=130, anchor=tk.CENTER) self.tree_docs.column("status", width=320, anchor=tk.W) self.tree_docs.pack(fill=tk.BOTH, expand=True) # Настройка цветовых тегов для индикации ошибок структуры КД self.tree_docs.tag_configure("error", background="#ffcdd2", foreground="#b71c1c") self.tree_docs.tag_configure("success", background="#c8e6c9", foreground="#1b5e20") # Правая часть: Контроль коллизий и ручные действия right_frame = ttk.Frame(main_paned, padding=5) main_paned.add(right_frame, weight=2) # Блок коллизий self.collision_frame = ttk.LabelFrame(right_frame, text=" Разрешение коллизий и дубликатов ", padding=5) self.collision_frame.pack(fill=tk.X, pady=(0, 10)) self.lbl_collision_info = ttk.Label(self.collision_frame, text="Конфликты структуры не обнаружены", foreground="green") self.lbl_collision_info.pack(pady=10) # Нижняя панель сохранения в архив bottom_frame = ttk.Frame(self, padding=10) bottom_frame.pack(fill=tk.X, side=tk.BOTTOM) self.btn_compile = tk.Button( bottom_frame, text="📥 Утвердить состав и импортировать документ в архив PDM", bg="#2e7d32", fg="white", font=("Helvetica", 10, "bold"), state=tk.DISABLED, command=self.compile_and_import ) self.btn_compile.pack(fill=tk.X, ipady=5) self.tree_docs.bind("<<TreeviewSelect>>", self.on_document_selected) def select_source_folder(self): folder = filedialog.askdirectory(title="Выберите каталог с разрозненными сканами PDF") if not folder: return self.lbl_folder_status.config(text=f"Анализ папки: {folder}...") self.update_idletasks() # Запуск сессионного OCR анализа self.engine.run_session_analysis(folder) self.refresh_tables() count = len(self.engine.assembled_docs) self.lbl_folder_status.config(text=f"Анализ завершен. Найдено документов: {count}") logger.info(f"Анализ папки '{folder}': найдено {count} документов") def refresh_tables(self): for item in self.tree_docs.get_children(): self.tree_docs.delete(item) for des, data in self.engine.assembled_docs.items(): expected = data['expected_total'] if data['expected_total'] else "?" fact = len(data['sheets']) total_str = f"{expected} / {fact}" # Если есть ошибки состава КД, красим строку в красный tag = "error" if "Готов" not in data['status_msg'] else "success" self.tree_docs.insert("", "end", iid=des, values=(des, total_str, data['status_msg']), tags=(tag,)) def on_document_selected(self, event): selected = self.tree_docs.selection() if not selected: self.btn_compile.config(state=tk.DISABLED) return des = selected[0] doc_entry = self.engine.assembled_docs[des] # Активируем кнопку импорта только если к структуре документа нет претензий со стороны ЕСКД if "Готов" in doc_entry['status_msg']: self.btn_compile.config(state=tk.NORMAL) else: self.btn_compile.config(state=tk.DISABLED) # Вывод информации о коллизиях, если они есть for widget in self.collision_frame.winfo_children(): widget.destroy() if doc_entry['collisions']: for sheet_num, pages in doc_entry['collisions'].items(): ttk.Label(self.collision_frame, text=f"Лист {sheet_num} найден {len(pages)} раз!", font=("Helvetica", 9, "bold"), foreground="red").pack(anchor=tk.W) for p in pages: btn_text = f"Оставить из: {os.path.basename(p['path'])} (стр. {p['idx']+1})" ttk.Button(self.collision_frame, text=btn_text, command=lambda d=des, p=p, s=sheet_num: self.resolve_user_collision(d, s, p)).pack(fill=tk.X, pady=2) else: ttk.Label(self.collision_frame, text="Конфликты дублирования листов отсутствуют", foreground="green").pack(pady=10) def resolve_user_collision(self, des, sheet_num, chosen_page_info): self.engine.resolve_collision(des, sheet_num, chosen_page_info['idx'], chosen_page_info['path']) self.refresh_tables() self.tree_docs.selection_set(des) def compile_and_import(self): selected = self.tree_docs.selection() if not selected: return des = selected[0] filepath, filename = self._resolve_archive_path(des) if not filepath: return success, msg = self.engine.physical_compile(des, filepath) if success: main_data = self._build_main_data(des, filepath) try: self.db.insert_document(main_data, []) messagebox.showinfo("Успех", f"Документ {des} успешно собран, выровнен и импортирован в архив PDM!") self.parent.refresh_archive_table() self.destroy() except Exception as e: messagebox.showerror("Ошибка СУБД", f"Файл собран, но не удалось создать карточку в БД:\n{e}") else: messagebox.showerror("Ошибка сборки", f"Не удалось выполнить склейку: {msg}") def _resolve_archive_path(self, designation): """Возвращает (filepath, filename) для архивации, запрашивает перезапись при конфликте.""" filename, filepath = self.db.generate_paths(designation, "dummy.pdf") if os.path.exists(filepath): if not messagebox.askyesno("Документ существует", f"Изделие {designation} уже есть в архиве. Перезаписать файл физической сборкой сканов?"): return None, None return filepath, filename def _build_main_data(self, designation, filepath): """Формирует метаданные карточки документа для OCR-сборки.""" doc_entry = self.engine.assembled_docs[designation] return { "filename": filepath.rsplit("/", 1)[-1] if "/" in filepath else filepath.split("\\")[-1], "filepath": filepath, "checksum": self.db.calculate_md5(filepath), "designation": designation, "title": f"Оцифрованный скан {designation}", "version": 1, "last_change": 0, "dev_fio": "Система OCR", "dev_date": datetime.date.today().strftime("%Y-%m-%d"), "chk_fio": "", "chk_date": "", "ctl_fio": "", "ctl_date": "", "app_fio": "", "app_date": "" }