feat: add manager filter to material validation tab

Add checkbox-based manager filter functionality to material validation page:
- Canvas-based filter UI with 6-column grid layout for manager checkboxes
- Select All / Deselect All quick action buttons
- Filter source: union of MaterialsToBeDeleted and MaterialsTypeToBeDeleted tables
- Auto-include records with empty manager field for editing
- Preserve filter state across validation runs
- Support for MaterialValidationResult dataclass objects
- Refresh filter and results after confirm deletion operation

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-02-09 17:32:59 +08:00
parent 2496694f05
commit 57c23f4608

View File

@@ -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(
"<Configure>",
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("<MouseWheel>", _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"):
"""线程安全的日志更新"""