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:
@@ -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"):
|
||||
"""线程安全的日志更新"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user