diff --git a/gui/material_validation_tab.py b/gui/material_validation_tab.py index 25884b5..8ee961e 100644 --- a/gui/material_validation_tab.py +++ b/gui/material_validation_tab.py @@ -16,9 +16,11 @@ from tkinter import ttk, messagebox, filedialog, simpledialog from pathlib import Path from io import StringIO from contextlib import redirect_stdout +from typing import List, Dict from gui.widgets import FileSelector, LogText from gui.config_manager import ConfigManager from gui.material_type_management_dialog import MaterialTypeManagementDialog +from db.materials_to_be_deleted_dao import MaterialsTypeToBeDeletedDAO import pandas as pd @@ -143,6 +145,12 @@ class MaterialValidationTab(ttk.Frame): self.validation_results = None self.material_records_cache = None # 缓存完整的物料记录 + # 负责人筛选相关 + self.managers: List[str] = [] # 可用负责人列表 + self.manager_checkboxes: Dict[str, tk.BooleanVar] = {} # 负责人复选框状态 + self.select_all_managers_var: tk.BooleanVar = tk.BooleanVar(value=True) # 全选复选框状态 + self.previously_selected_managers: List[str] = [] # 保存筛选状态 + self.create_widgets() # 稍后显示就绪消息 @@ -161,6 +169,14 @@ class MaterialValidationTab(ttk.Frame): control_frame = ttk.Frame(main_container) control_frame.pack(fill=tk.X, pady=(0, 10)) + # 负责人筛选区域 + manager_filter_frame = ttk.LabelFrame( + main_container, + text="筛选(按负责人)", + padding=10 + ) + manager_filter_frame.pack(fill=tk.X, pady=(0, 10)) + # 中部:结果表格 result_frame = ttk.LabelFrame(main_container, text="校验结果", padding=5) result_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) @@ -170,6 +186,7 @@ class MaterialValidationTab(ttk.Frame): log_frame.pack(fill=tk.BOTH, expand=True) self._create_control_panel(control_frame) + self._create_manager_filter_area(manager_filter_frame) self._create_result_table(result_frame) self._create_log_panel(log_frame) @@ -363,6 +380,210 @@ class MaterialValidationTab(ttk.Frame): elif mode == "database_filtered": self.db_filtered_frame.grid(row=0, column=0, columnspan=2, sticky="ew") + def _create_manager_filter_area(self, parent): + """创建负责人筛选区域""" + # 创建复选框容器 + canvas_container = ttk.Frame(parent) + canvas_container.pack(fill=tk.BOTH, expand=True) + + # Canvas和滚动条 + self.filter_canvas = tk.Canvas(canvas_container, height=80) + scrollbar = ttk.Scrollbar(canvas_container, orient="vertical", command=self.filter_canvas.yview) + self.filter_frame = ttk.Frame(self.filter_canvas) + + self.filter_frame.bind( + "", + lambda e: self.filter_canvas.configure(scrollregion=self.filter_frame.bbox("all")) + ) + + self.filter_canvas.create_window((0, 0), window=self.filter_frame, anchor="nw") + self.filter_canvas.configure(yscrollcommand=scrollbar.set) + + # 布局 + self.filter_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # 鼠标滚轮支持 + def _on_mousewheel(event): + self.filter_canvas.yview_scroll(int(-1*(event.delta/120)), "units") + self.filter_canvas.bind("", _on_mousewheel) + + # 快捷按钮 + button_frame = ttk.Frame(parent) + button_frame.pack(fill=tk.X, pady=(5, 0)) + + ttk.Button(button_frame, text="全选", command=self._select_all_managers_button).pack(side=tk.LEFT, padx=5) + ttk.Button(button_frame, text="取消全选", command=self._deselect_all_managers_button).pack(side=tk.LEFT, padx=5) + + def _create_manager_checkboxes(self): + """创建负责人复选框""" + for widget in self.filter_frame.winfo_children(): + widget.destroy() + self.manager_checkboxes.clear() + + # 从两个表获取负责人列表并合并去重 + from db.materials_to_be_deleted_records_dao import MaterialsToBeDeletedDAO + + record_dao = MaterialsToBeDeletedDAO() + record_managers = record_dao.get_managers() + + type_dao = MaterialsTypeToBeDeletedDAO() + type_managers = type_dao.get_managers() + + # 合并两个表的负责人列表并去重 + self.managers = sorted(list(set(record_managers + type_managers))) + + if not self.managers: + ttk.Label(self.filter_frame, text="暂无负责人数据").pack(anchor="w") + return + + items_per_column = 6 + + # 全选复选框 + ttk.Checkbutton( + self.filter_frame, + text="全选", + variable=self.select_all_managers_var, + command=self._on_select_all_managers_toggle + ).grid(row=0, column=0, sticky="w", padx=5, pady=2) + + # 负责人复选框 + for idx, manager in enumerate(self.managers): + var = tk.BooleanVar(value=True) + self.manager_checkboxes[manager] = var + + row = (idx // items_per_column) + 1 + col = idx % items_per_column + + ttk.Checkbutton( + self.filter_frame, + text=manager, + variable=var, + command=self._on_manager_checkbox_change + ).grid(row=row, column=col, sticky="w", padx=5, pady=2) + + def _on_select_all_managers_toggle(self): + """全选复选框状态改变""" + select_all = self.select_all_managers_var.get() + for var in self.manager_checkboxes.values(): + var.set(select_all) + self._apply_manager_filter() + + def _on_manager_checkbox_change(self): + """单个负责人复选框状态改变""" + all_selected = all(var.get() for var in self.manager_checkboxes.values()) + self.select_all_managers_var.set(all_selected) + self._apply_manager_filter() + + def _select_all_managers_button(self): + """全选所有负责人""" + self.select_all_managers_var.set(True) + for var in self.manager_checkboxes.values(): + var.set(True) + self._apply_manager_filter() + + def _deselect_all_managers_button(self): + """取消全选""" + self.select_all_managers_var.set(False) + for var in self.manager_checkboxes.values(): + var.set(False) + self._apply_manager_filter() + + def _get_selected_managers(self) -> List[str]: + """获取选中的负责人列表""" + return [ + manager for manager, var in self.manager_checkboxes.items() + if var.get() + ] + + def _apply_manager_filter(self): + """应用负责人筛选""" + if not self.material_records_cache: + return + + selected_managers = self._get_selected_managers() + + if not selected_managers: + # 没有选中任何负责人,清空表格 + self._refresh_filtered_results([]) + self._update_log("未选择任何负责人,表格已清空", "WARNING") + return + + # 筛选记录:包含选中负责人的记录 + 负责人为空的记录 + filtered_records = [ + record for record in self.material_records_cache + if (record.manager_name in selected_managers or + not record.manager_name or record.manager_name.strip() == "") + ] + + # 统计空负责人的记录数 + empty_manager_count = sum( + 1 for r in filtered_records + if not r.manager_name or r.manager_name.strip() == "" + ) + + self._refresh_filtered_results(filtered_records) + self._update_log( + f"筛选结果:共 {len(filtered_records)} 条记录" + f"(其中 {empty_manager_count} 条负责人为空,待编辑)", + "INFO" + ) + + def _refresh_filtered_results(self, filtered_records): + """刷新表格显示筛选后的结果""" + # 清空表格 + for item in self.tree.get_children(): + self.tree.delete(item) + + if not filtered_records: + return + + # 从数据库获取已标记删除的记录 + from db.materials_to_be_deleted_records_dao import MaterialsToBeDeletedDAO + record_dao = MaterialsToBeDeletedDAO() + marked_records = record_dao.get_all_records() + + # Build dictionary: MaterialCode -> ManagerName + marked_codes_dict = { + r['MaterialCode']: r['ManagerName'] + for r in marked_records + if r.get('MaterialCode') and r.get('ManagerName') + } + + # 填充筛选后的记录 + for record in filtered_records: + # MaterialValidationResult 对象使用属性访问 + material_code = str(record.material_code) + + # 优先使用 MaterialsToBeDeleted 中的 ManagerName + manager_name = marked_codes_dict.get(material_code) + is_marked = manager_name is not None + + # 如果没有在 MaterialsToBeDeleted 中找到,使用记录中的负责人 + if not manager_name: + manager_name = str(record.manager_name if record.manager_name else "") + + # 准备显示值 + checkbox = "☑" if is_marked else "☐" + values = ( + checkbox, + str(record.material_name), + material_code, + str(record.specification if record.specification else ""), + str(record.model if record.model else ""), + manager_name, + ) + + item = self.tree.insert("", tk.END, values=values) + + # 设置 checkbox 状态 + if is_marked: + self.tree.set_checked(item, True) + + # 更新按钮状态 + if len(filtered_records) > 0: + self.export_button.config(state=tk.NORMAL) + def _on_cell_double_click(self, event): """处理单元格双击事件,编辑负责人""" # 获取点击位置 @@ -474,6 +695,10 @@ class MaterialValidationTab(ttk.Frame): self.select_all_button.config(state=tk.DISABLED) self.deselect_all_button.config(state=tk.DISABLED) self.log_text.clear() + + # 保存当前筛选状态 + self.previously_selected_managers = self._get_selected_managers() + self.log_text.info(f"开始物料校验(模式: {mode})...") # 清空结果表格 @@ -556,6 +781,10 @@ class MaterialValidationTab(ttk.Frame): self.deselect_all_button.config(state=tk.NORMAL) self.confirm_delete_button.config(state=tk.NORMAL) + # 恢复筛选状态 + if hasattr(self, 'previously_selected_managers') and self.previously_selected_managers: + self._restore_manager_filter_state(self.previously_selected_managers) + def _load_results_with_deletion_status(self, file_path: str): """加载结果并设置 checkbox 选中状态""" try: @@ -607,11 +836,32 @@ class MaterialValidationTab(ttk.Frame): self.export_button.config(state=tk.NORMAL) self._update_log(f"已加载 {len(df)} 条结果(其中 {len(marked_codes_dict)} 条已标记删除)", "INFO") + # 初始化负责人筛选器 + self._initialize_manager_filter() + self.after(0, update_table) except Exception as e: self._update_log(f"加载结果失败:{str(e)}", "ERROR") + def _initialize_manager_filter(self): + """加载结果后初始化负责人筛选器""" + try: + self._create_manager_checkboxes() + self._apply_manager_filter() + except Exception as e: + self._update_log(f"初始化筛选器失败:{str(e)}", "ERROR") + + def _restore_manager_filter_state(self, selected_managers: List[str]): + """恢复负责人筛选状态""" + try: + for manager in selected_managers: + if manager in self.manager_checkboxes: + self.manager_checkboxes[manager].set(True) + self._apply_manager_filter() + except Exception as e: + self._update_log(f"恢复筛选状态失败:{str(e)}", "ERROR") + def confirm_deletion(self): """确认删除主流程 @@ -748,6 +998,9 @@ class MaterialValidationTab(ttk.Frame): messagebox.showinfo("成功", msg) self._update_log(msg, "SUCCESS") + # 刷新筛选器和结果(可能添加了新的负责人) + self._initialize_manager_filter() + def _update_log(self, message: str, level: str = "INFO"): """线程安全的日志更新"""