style: format all Python files with Black
Apply Black formatter to the entire codebase for consistent code style. Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,13 @@ from io import StringIO
|
||||
from contextlib import redirect_stdout
|
||||
from typing import List, Dict
|
||||
from gui.base_tab import BaseTab
|
||||
from gui.widgets import FileSelector, LogText, GuiTextHandler, DeleteProgressWindow, CheckboxTreeview
|
||||
from gui.widgets import (
|
||||
FileSelector,
|
||||
LogText,
|
||||
GuiTextHandler,
|
||||
DeleteProgressWindow,
|
||||
CheckboxTreeview,
|
||||
)
|
||||
from gui.config_manager import ConfigManager
|
||||
from gui.log_config import setup_gui_logging, get_logger
|
||||
from gui.material_type_management_dialog import MaterialTypeManagementDialog
|
||||
@@ -31,7 +37,9 @@ import tempfile
|
||||
class MaterialValidationTab(BaseTab):
|
||||
"""物料校验标签页"""
|
||||
|
||||
def __init__(self, parent, config: ConfigManager, session_manager, main_window=None):
|
||||
def __init__(
|
||||
self, parent, config: ConfigManager, session_manager, main_window=None
|
||||
):
|
||||
"""
|
||||
初始化物料校验标签页
|
||||
|
||||
@@ -52,7 +60,9 @@ class MaterialValidationTab(BaseTab):
|
||||
# 负责人筛选相关
|
||||
self.managers: List[str] = [] # 可用负责人列表
|
||||
self.manager_checkboxes: Dict[str, tk.BooleanVar] = {} # 负责人复选框状态
|
||||
self.select_all_managers_var: tk.BooleanVar = tk.BooleanVar(value=True) # 全选复选框状态
|
||||
self.select_all_managers_var: tk.BooleanVar = tk.BooleanVar(
|
||||
value=True
|
||||
) # 全选复选框状态
|
||||
self.previously_selected_managers: List[str] = [] # 保存筛选状态
|
||||
|
||||
# 初始化统一日志系统
|
||||
@@ -83,7 +93,7 @@ class MaterialValidationTab(BaseTab):
|
||||
manager_filter_frame = ttk.LabelFrame(
|
||||
main_container,
|
||||
text="筛选(按负责人)",
|
||||
padding=(10, 10, 10, 10) # 左、上、右、下 - 底部padding减小
|
||||
padding=(10, 10, 10, 10), # 左、上、右、下 - 底部padding减小
|
||||
)
|
||||
manager_filter_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
else:
|
||||
@@ -135,7 +145,9 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 右侧:Production ID 数据源选择区域
|
||||
self.source_frame = ttk.Frame(source_group)
|
||||
self.source_frame.grid(row=0, column=1, rowspan=2, sticky="nsew", padx=(20, 0))
|
||||
self.source_frame.grid(
|
||||
row=0, column=1, rowspan=2, sticky="nsew", padx=(20, 0)
|
||||
)
|
||||
|
||||
# Production ID 数据源选择
|
||||
self.production_id_source_var = tk.StringVar(value="shared")
|
||||
@@ -144,7 +156,7 @@ class MaterialValidationTab(BaseTab):
|
||||
text="使用文件:",
|
||||
variable=self.production_id_source_var,
|
||||
value="file",
|
||||
command=self._on_production_id_source_changed
|
||||
command=self._on_production_id_source_changed,
|
||||
).pack(side=tk.LEFT)
|
||||
|
||||
ttk.Radiobutton(
|
||||
@@ -152,7 +164,7 @@ class MaterialValidationTab(BaseTab):
|
||||
text="使用数据提取页面的 Production ID:",
|
||||
variable=self.production_id_source_var,
|
||||
value="shared",
|
||||
command=self._on_production_id_source_changed
|
||||
command=self._on_production_id_source_changed,
|
||||
).pack(side=tk.LEFT, padx=10)
|
||||
|
||||
# 文件选择器(放在 source_group 下方,当选择文件时显示)
|
||||
@@ -166,9 +178,7 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 共享 Production ID 提示标签
|
||||
self.shared_ids_info_label = ttk.Label(
|
||||
source_group,
|
||||
text="",
|
||||
foreground="blue"
|
||||
source_group, text="", foreground="blue"
|
||||
)
|
||||
# 初始时不显示
|
||||
|
||||
@@ -192,7 +202,7 @@ class MaterialValidationTab(BaseTab):
|
||||
button_frame,
|
||||
text="全选",
|
||||
command=lambda: self._select_all(check=True),
|
||||
state=tk.DISABLED
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.select_all_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
@@ -201,7 +211,7 @@ class MaterialValidationTab(BaseTab):
|
||||
button_frame,
|
||||
text="取消全选",
|
||||
command=lambda: self._select_all(check=False),
|
||||
state=tk.DISABLED
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.deselect_all_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
@@ -210,7 +220,7 @@ class MaterialValidationTab(BaseTab):
|
||||
button_frame,
|
||||
text="确认删除",
|
||||
command=self.confirm_deletion,
|
||||
state=tk.DISABLED
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.confirm_delete_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
@@ -219,7 +229,7 @@ class MaterialValidationTab(BaseTab):
|
||||
button_frame,
|
||||
text="执行删除",
|
||||
command=self.start_delete_execution,
|
||||
state=tk.DISABLED
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.execute_delete_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
@@ -227,9 +237,7 @@ class MaterialValidationTab(BaseTab):
|
||||
if self.session_manager.is_admin():
|
||||
self.dryrun_var = tk.BooleanVar(value=False)
|
||||
self.dryrun_checkbox = ttk.Checkbutton(
|
||||
button_frame,
|
||||
text="预览模式 (不保存)",
|
||||
variable=self.dryrun_var
|
||||
button_frame, text="预览模式 (不保存)", variable=self.dryrun_var
|
||||
)
|
||||
self.dryrun_checkbox.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
@@ -243,9 +251,7 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 类型管理按钮
|
||||
self.type_management_button = ttk.Button(
|
||||
button_frame,
|
||||
text="类型管理",
|
||||
command=self.open_type_management
|
||||
button_frame, text="类型管理", command=self.open_type_management
|
||||
)
|
||||
self.type_management_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
@@ -275,7 +281,7 @@ class MaterialValidationTab(BaseTab):
|
||||
columns=columns,
|
||||
show="headings",
|
||||
height=10,
|
||||
on_checkbox_change=self._sync_checkbox_by_material_code
|
||||
on_checkbox_change=self._sync_checkbox_by_material_code,
|
||||
)
|
||||
|
||||
# 设置列标题和宽度
|
||||
@@ -294,8 +300,12 @@ class MaterialValidationTab(BaseTab):
|
||||
self.tree.column("负责人", width=150)
|
||||
|
||||
# 添加滚动条
|
||||
scrollbar_y = ttk.Scrollbar(table_container, orient=tk.VERTICAL, command=self.tree.yview)
|
||||
scrollbar_x = ttk.Scrollbar(table_container, orient=tk.HORIZONTAL, command=self.tree.xview)
|
||||
scrollbar_y = ttk.Scrollbar(
|
||||
table_container, orient=tk.VERTICAL, command=self.tree.yview
|
||||
)
|
||||
scrollbar_x = ttk.Scrollbar(
|
||||
table_container, orient=tk.HORIZONTAL, command=self.tree.xview
|
||||
)
|
||||
|
||||
self.tree.configure(
|
||||
yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set
|
||||
@@ -319,7 +329,7 @@ class MaterialValidationTab(BaseTab):
|
||||
columns=columns,
|
||||
show="headings",
|
||||
height=10,
|
||||
on_checkbox_change=self._sync_checkbox_by_material_code
|
||||
on_checkbox_change=self._sync_checkbox_by_material_code,
|
||||
)
|
||||
|
||||
# 设置列标题和宽度
|
||||
@@ -338,7 +348,9 @@ class MaterialValidationTab(BaseTab):
|
||||
self.tree.column("负责人", width=150)
|
||||
|
||||
# 添加滚动条
|
||||
scrollbar_y = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=self.tree.yview)
|
||||
scrollbar_y = ttk.Scrollbar(
|
||||
parent, orient=tk.VERTICAL, command=self.tree.yview
|
||||
)
|
||||
scrollbar_x = ttk.Scrollbar(
|
||||
parent, orient=tk.HORIZONTAL, command=self.tree.xview
|
||||
)
|
||||
@@ -367,7 +379,7 @@ class MaterialValidationTab(BaseTab):
|
||||
button_frame,
|
||||
text="隐藏勾选",
|
||||
command=self._hide_checked_items,
|
||||
state=tk.DISABLED
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.btn_hide_checked.pack(fill=tk.X, pady=2)
|
||||
|
||||
@@ -375,19 +387,19 @@ class MaterialValidationTab(BaseTab):
|
||||
button_frame,
|
||||
text="显示全部",
|
||||
command=self._show_all_items,
|
||||
state=tk.DISABLED
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.btn_show_all.pack(fill=tk.X, pady=2)
|
||||
|
||||
# 分隔线
|
||||
ttk.Separator(button_frame, orient='horizontal').pack(fill=tk.X, pady=5)
|
||||
ttk.Separator(button_frame, orient="horizontal").pack(fill=tk.X, pady=5)
|
||||
|
||||
# 全选按钮
|
||||
self.select_all_button = ttk.Button(
|
||||
button_frame,
|
||||
text="全选",
|
||||
command=lambda: self._select_all(check=True),
|
||||
state=tk.DISABLED
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.select_all_button.pack(fill=tk.X, pady=2)
|
||||
|
||||
@@ -396,7 +408,7 @@ class MaterialValidationTab(BaseTab):
|
||||
button_frame,
|
||||
text="取消全选",
|
||||
command=lambda: self._select_all(check=False),
|
||||
state=tk.DISABLED
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.deselect_all_button.pack(fill=tk.X, pady=2)
|
||||
|
||||
@@ -405,7 +417,7 @@ class MaterialValidationTab(BaseTab):
|
||||
button_frame,
|
||||
text="确认删除",
|
||||
command=self.confirm_deletion,
|
||||
state=tk.DISABLED
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.confirm_delete_button.pack(fill=tk.X, pady=2)
|
||||
|
||||
@@ -414,7 +426,7 @@ class MaterialValidationTab(BaseTab):
|
||||
button_frame,
|
||||
text="执行删除",
|
||||
command=self.start_delete_execution,
|
||||
state=tk.DISABLED
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.execute_delete_button.pack(fill=tk.X, pady=2)
|
||||
|
||||
@@ -439,7 +451,7 @@ class MaterialValidationTab(BaseTab):
|
||||
return
|
||||
|
||||
for item in self.hidden_items:
|
||||
self.tree.move(item, '', 'end') # Restore to end of tree
|
||||
self.tree.move(item, "", "end") # Restore to end of tree
|
||||
|
||||
count = len(self.hidden_items)
|
||||
self.hidden_items.clear()
|
||||
@@ -452,10 +464,11 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 设置 GUI 日志处理器,将 logging 输出桥接到 LogText 组件
|
||||
self._gui_handler = GuiTextHandler(self.log_text)
|
||||
self._gui_handler.setFormatter(logging.Formatter(
|
||||
'%(asctime)s [%(levelname)s] %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
))
|
||||
self._gui_handler.setFormatter(
|
||||
logging.Formatter(
|
||||
"%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
)
|
||||
self.logger.addHandler(self._gui_handler)
|
||||
|
||||
def _on_source_mode_change(self):
|
||||
@@ -468,16 +481,18 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
if mode == "database_full":
|
||||
# 全表模式:隐藏所有 Production ID 相关控件
|
||||
if hasattr(self, 'source_frame') and self.source_frame:
|
||||
if hasattr(self, "source_frame") and self.source_frame:
|
||||
self.source_frame.grid_forget()
|
||||
if hasattr(self, 'db_filtered_production_id_selector'):
|
||||
if hasattr(self, "db_filtered_production_id_selector"):
|
||||
self.db_filtered_production_id_selector.grid_forget()
|
||||
if hasattr(self, 'shared_ids_info_label'):
|
||||
if hasattr(self, "shared_ids_info_label"):
|
||||
self.shared_ids_info_label.grid_forget()
|
||||
elif mode == "database_filtered":
|
||||
# 过滤模式:显示数据源选择
|
||||
if hasattr(self, 'source_frame') and self.source_frame:
|
||||
self.source_frame.grid(row=0, column=1, rowspan=2, sticky="nsew", padx=(20, 0))
|
||||
if hasattr(self, "source_frame") and self.source_frame:
|
||||
self.source_frame.grid(
|
||||
row=0, column=1, rowspan=2, sticky="nsew", padx=(20, 0)
|
||||
)
|
||||
# 根据 Production ID 数据源选择显示对应控件
|
||||
self._on_production_id_source_changed()
|
||||
|
||||
@@ -491,11 +506,15 @@ class MaterialValidationTab(BaseTab):
|
||||
if source == "shared":
|
||||
# 使用共享 Production ID
|
||||
self.db_filtered_production_id_selector.grid_forget()
|
||||
self.shared_ids_info_label.grid(row=2, column=0, columnspan=2, sticky="w", pady=(5, 0))
|
||||
self.shared_ids_info_label.grid(
|
||||
row=2, column=0, columnspan=2, sticky="w", pady=(5, 0)
|
||||
)
|
||||
else:
|
||||
# 使用文件
|
||||
self.shared_ids_info_label.grid_forget()
|
||||
self.db_filtered_production_id_selector.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(5, 0))
|
||||
self.db_filtered_production_id_selector.grid(
|
||||
row=2, column=0, columnspan=2, sticky="ew", pady=(5, 0)
|
||||
)
|
||||
|
||||
def on_production_ids_updated(self, production_ids: list):
|
||||
"""当数据提取页面的 Production ID 更新时调用"""
|
||||
@@ -506,7 +525,7 @@ class MaterialValidationTab(BaseTab):
|
||||
# User 模式:静默更新,不显示任何提示
|
||||
if is_user_only:
|
||||
# 启用执行删除按钮(如果有 Production ID)
|
||||
if production_ids and hasattr(self, 'execute_delete_button'):
|
||||
if production_ids and hasattr(self, "execute_delete_button"):
|
||||
self.execute_delete_button.config(state=tk.NORMAL)
|
||||
return
|
||||
|
||||
@@ -518,19 +537,22 @@ class MaterialValidationTab(BaseTab):
|
||||
preview += f" ... (共 {count} 个)"
|
||||
self.shared_ids_info_label.config(text=f"📋 {preview}")
|
||||
# 显示提示标签
|
||||
if hasattr(self, 'shared_ids_label') and not self.shared_ids_label.winfo_ismapped():
|
||||
if (
|
||||
hasattr(self, "shared_ids_label")
|
||||
and not self.shared_ids_label.winfo_ismapped()
|
||||
):
|
||||
self.shared_ids_label.pack(anchor="w", pady=(0, 5))
|
||||
# 启用执行删除按钮
|
||||
if hasattr(self, 'execute_delete_button'):
|
||||
if hasattr(self, "execute_delete_button"):
|
||||
self.execute_delete_button.config(state=tk.NORMAL)
|
||||
else:
|
||||
self.shared_ids_info_label.config(text="")
|
||||
# 隐藏提示标签
|
||||
if hasattr(self, 'shared_ids_label'):
|
||||
if hasattr(self, "shared_ids_label"):
|
||||
if self.shared_ids_label.winfo_ismapped():
|
||||
self.shared_ids_label.pack_forget()
|
||||
# 禁用执行删除按钮
|
||||
if hasattr(self, 'execute_delete_button'):
|
||||
if hasattr(self, "execute_delete_button"):
|
||||
self.execute_delete_button.config(state=tk.DISABLED)
|
||||
|
||||
def reload_config(self):
|
||||
@@ -557,8 +579,12 @@ class MaterialValidationTab(BaseTab):
|
||||
button_frame = ttk.Frame(main_container)
|
||||
button_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
|
||||
|
||||
ttk.Button(button_frame, text="全选", command=self._select_all_managers_button).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(button_frame, text="取消全选", command=self._deselect_all_managers_button).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(
|
||||
button_frame, text="全选", command=self._select_all_managers_button
|
||||
).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(
|
||||
button_frame, text="取消全选", command=self._deselect_all_managers_button
|
||||
).pack(fill=tk.X, pady=2)
|
||||
|
||||
# 右侧:负责人复选框区域(简化为直接使用 Frame)
|
||||
self.filter_frame = ttk.Frame(main_container)
|
||||
@@ -579,7 +605,7 @@ class MaterialValidationTab(BaseTab):
|
||||
# 不显示复选框,直接显示提示信息
|
||||
ttk.Label(
|
||||
self.filter_frame,
|
||||
text=f"仅显示您的数据(负责人:{self.session_manager.get_username()})"
|
||||
text=f"仅显示您的数据(负责人:{self.session_manager.get_username()})",
|
||||
).pack(anchor="w")
|
||||
return
|
||||
|
||||
@@ -613,7 +639,7 @@ class MaterialValidationTab(BaseTab):
|
||||
self.filter_frame,
|
||||
text=manager,
|
||||
variable=var,
|
||||
command=self._on_manager_checkbox_change
|
||||
command=self._on_manager_checkbox_change,
|
||||
).grid(row=row, column=col, sticky="w", padx=5, pady=2)
|
||||
|
||||
def _on_select_all_managers_toggle(self):
|
||||
@@ -654,8 +680,7 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 管理员:从复选框获取选中的负责人
|
||||
return [
|
||||
manager for manager, var in self.manager_checkboxes.items()
|
||||
if var.get()
|
||||
manager for manager, var in self.manager_checkboxes.items() if var.get()
|
||||
]
|
||||
|
||||
def _apply_manager_filter(self):
|
||||
@@ -677,14 +702,19 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 筛选记录:包含选中负责人的记录 + 负责人为空的记录
|
||||
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() == "")
|
||||
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
|
||||
1
|
||||
for r in filtered_records
|
||||
if not r.manager_name or r.manager_name.strip() == ""
|
||||
)
|
||||
|
||||
@@ -692,7 +722,7 @@ class MaterialValidationTab(BaseTab):
|
||||
self._update_log(
|
||||
f"筛选结果:共 {len(filtered_records)} 条记录"
|
||||
f"(其中 {empty_manager_count} 条负责人为空,待编辑)",
|
||||
"INFO"
|
||||
"INFO",
|
||||
)
|
||||
|
||||
def _refresh_filtered_results(self, filtered_records):
|
||||
@@ -706,19 +736,22 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 从数据库获取已标记删除的记录
|
||||
from db.materials_to_be_deleted_records_dao import MaterialsToBeDeletedDAO
|
||||
|
||||
record_dao = MaterialsToBeDeletedDAO()
|
||||
|
||||
# PERMISSION CHECK: 非管理员用户只获取自己的记录
|
||||
if not self.session_manager.is_admin():
|
||||
marked_records = record_dao.get_materials_by_manager(self.session_manager.get_username())
|
||||
marked_records = record_dao.get_materials_by_manager(
|
||||
self.session_manager.get_username()
|
||||
)
|
||||
else:
|
||||
marked_records = record_dao.get_all_records()
|
||||
|
||||
# Build dictionary: MaterialCode -> ManagerName
|
||||
marked_codes_dict = {
|
||||
r['MaterialCode']: r['ManagerName']
|
||||
r["MaterialCode"]: r["ManagerName"]
|
||||
for r in marked_records
|
||||
if r.get('MaterialCode') and r.get('ManagerName')
|
||||
if r.get("MaterialCode") and r.get("ManagerName")
|
||||
}
|
||||
|
||||
# 填充筛选后的记录
|
||||
@@ -771,9 +804,7 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 弹出编辑对话框
|
||||
new_value = simpledialog.askstring(
|
||||
"编辑负责人",
|
||||
f"请输入负责人姓名:",
|
||||
initialvalue=current_value
|
||||
"编辑负责人", f"请输入负责人姓名:", initialvalue=current_value
|
||||
)
|
||||
|
||||
if new_value is not None: # 用户没有取消
|
||||
@@ -853,9 +884,14 @@ class MaterialValidationTab(BaseTab):
|
||||
# User 模式:自动使用共享的 Production ID
|
||||
production_ids_list = self.shared_production_ids
|
||||
if not production_ids_list:
|
||||
messagebox.showerror("错误", "没有可用的共享 Production ID\n请在数据提取页面输入 Production ID 后再试")
|
||||
messagebox.showerror(
|
||||
"错误",
|
||||
"没有可用的共享 Production ID\n请在数据提取页面输入 Production ID 后再试",
|
||||
)
|
||||
return
|
||||
self.log_text.info(f"使用数据提取页面的 Production ID({len(production_ids_list)} 个)")
|
||||
self.log_text.info(
|
||||
f"使用数据提取页面的 Production ID({len(production_ids_list)} 个)"
|
||||
)
|
||||
else:
|
||||
# Admin 模式:检查数据源选择
|
||||
source = self.production_id_source_var.get()
|
||||
@@ -863,9 +899,14 @@ class MaterialValidationTab(BaseTab):
|
||||
# 使用共享的 Production ID
|
||||
production_ids_list = self.shared_production_ids
|
||||
if not production_ids_list:
|
||||
messagebox.showerror("错误", "没有可用的共享 Production ID\n请在数据提取页面输入 Production ID 后再试")
|
||||
messagebox.showerror(
|
||||
"错误",
|
||||
"没有可用的共享 Production ID\n请在数据提取页面输入 Production ID 后再试",
|
||||
)
|
||||
return
|
||||
self.log_text.info(f"使用共享的 Production ID({len(production_ids_list)} 个)")
|
||||
self.log_text.info(
|
||||
f"使用共享的 Production ID({len(production_ids_list)} 个)"
|
||||
)
|
||||
else:
|
||||
# 使用文件
|
||||
production_id_file = self.db_filtered_production_id_selector.get()
|
||||
@@ -873,7 +914,9 @@ class MaterialValidationTab(BaseTab):
|
||||
messagebox.showerror("错误", "请选择 ProductionID 文件")
|
||||
return
|
||||
if not os.path.exists(production_id_file):
|
||||
messagebox.showerror("错误", f"文件不存在:{production_id_file}")
|
||||
messagebox.showerror(
|
||||
"错误", f"文件不存在:{production_id_file}"
|
||||
)
|
||||
return
|
||||
else:
|
||||
messagebox.showerror("错误", f"未知的校验模式: {mode}")
|
||||
@@ -897,9 +940,9 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 禁用筛选按钮(User 模式)
|
||||
if not self.session_manager.is_admin():
|
||||
if hasattr(self, 'btn_hide_checked'):
|
||||
if hasattr(self, "btn_hide_checked"):
|
||||
self.btn_hide_checked.config(state=tk.DISABLED)
|
||||
if hasattr(self, 'btn_show_all'):
|
||||
if hasattr(self, "btn_show_all"):
|
||||
self.btn_show_all.config(state=tk.DISABLED)
|
||||
|
||||
# 保存当前筛选状态
|
||||
@@ -914,26 +957,43 @@ class MaterialValidationTab(BaseTab):
|
||||
# 在后台线程中执行校验
|
||||
validation_thread = threading.Thread(
|
||||
target=self._validation_worker_enhanced,
|
||||
args=(mode, input_file, production_id_file, output_file, production_ids_list),
|
||||
args=(
|
||||
mode,
|
||||
input_file,
|
||||
production_id_file,
|
||||
output_file,
|
||||
production_ids_list,
|
||||
),
|
||||
daemon=True,
|
||||
)
|
||||
validation_thread.start()
|
||||
|
||||
def _validation_worker_enhanced(
|
||||
self, mode: str, input_file: str, production_id_file: str, output_file: str, production_ids_list: list = None
|
||||
self,
|
||||
mode: str,
|
||||
input_file: str,
|
||||
production_id_file: str,
|
||||
output_file: str,
|
||||
production_ids_list: list = None,
|
||||
):
|
||||
"""增强的校验工作线程(使用完整记录模式)"""
|
||||
import tempfile
|
||||
|
||||
temp_production_id_file = None
|
||||
|
||||
try:
|
||||
# 如果提供了共享的 Production ID 列表,创建临时文件
|
||||
if production_ids_list:
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as f:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w", suffix=".txt", delete=False, encoding="utf-8"
|
||||
) as f:
|
||||
temp_production_id_file = f.name
|
||||
f.write('\n'.join(production_ids_list))
|
||||
f.write("\n".join(production_ids_list))
|
||||
production_id_file = temp_production_id_file
|
||||
self._update_log(f"使用共享的 Production ID 列表({len(production_ids_list)} 个)", "INFO")
|
||||
self._update_log(
|
||||
f"使用共享的 Production ID 列表({len(production_ids_list)} 个)",
|
||||
"INFO",
|
||||
)
|
||||
|
||||
# 导入校验器
|
||||
from utils.material_status_validator import MaterialStatusValidator
|
||||
@@ -953,14 +1013,13 @@ class MaterialValidationTab(BaseTab):
|
||||
with redirect_stdout(captured_output):
|
||||
if mode == "database_full":
|
||||
result_file, results = validator.validate_from_database_enhanced(
|
||||
full_table=True,
|
||||
output_file=output_file
|
||||
full_table=True, output_file=output_file
|
||||
)
|
||||
elif mode == "database_filtered":
|
||||
result_file, results = validator.validate_from_database_enhanced(
|
||||
production_id_file=production_id_file,
|
||||
full_table=False,
|
||||
output_file=output_file
|
||||
output_file=output_file,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"未知的校验模式: {mode}")
|
||||
@@ -981,13 +1040,16 @@ class MaterialValidationTab(BaseTab):
|
||||
self._load_results_with_deletion_status(result_file)
|
||||
elif results is not None and len(results) == 0:
|
||||
# 已经在 validator 中输出详细错误信息,这里只做简单提示
|
||||
self._update_log("校验失败:未找到物料记录,请查看上方日志了解详细原因", "ERROR")
|
||||
self._update_log(
|
||||
"校验失败:未找到物料记录,请查看上方日志了解详细原因", "ERROR"
|
||||
)
|
||||
else:
|
||||
self._update_log("校验失败", "ERROR")
|
||||
|
||||
except Exception as e:
|
||||
self._update_log(f"校验过程中发生错误:{str(e)}", "ERROR")
|
||||
import traceback
|
||||
|
||||
self._update_log(traceback.format_exc(), "ERROR")
|
||||
finally:
|
||||
# 清理临时文件
|
||||
@@ -1009,21 +1071,24 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 启用执行删除按钮(需要 Production ID)
|
||||
if self.shared_production_ids or (
|
||||
self.session_manager.is_admin() and
|
||||
hasattr(self, 'db_filtered_production_id_selector') and
|
||||
self.db_filtered_production_id_selector.get()
|
||||
self.session_manager.is_admin()
|
||||
and hasattr(self, "db_filtered_production_id_selector")
|
||||
and self.db_filtered_production_id_selector.get()
|
||||
):
|
||||
self.execute_delete_button.config(state=tk.NORMAL)
|
||||
|
||||
# 启用筛选按钮(User 模式)
|
||||
if not self.session_manager.is_admin():
|
||||
if hasattr(self, 'btn_hide_checked'):
|
||||
if hasattr(self, "btn_hide_checked"):
|
||||
self.btn_hide_checked.config(state=tk.NORMAL)
|
||||
if hasattr(self, 'btn_show_all'):
|
||||
if hasattr(self, "btn_show_all"):
|
||||
self.btn_show_all.config(state=tk.NORMAL)
|
||||
|
||||
# 恢复筛选状态
|
||||
if hasattr(self, 'previously_selected_managers') and self.previously_selected_managers:
|
||||
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):
|
||||
@@ -1034,20 +1099,25 @@ class MaterialValidationTab(BaseTab):
|
||||
# 在主线程中更新表格
|
||||
def update_table():
|
||||
# 从数据库获取已标记删除的 MaterialCode -> ManagerName 映射
|
||||
from db.materials_to_be_deleted_records_dao import MaterialsToBeDeletedDAO
|
||||
from db.materials_to_be_deleted_records_dao import (
|
||||
MaterialsToBeDeletedDAO,
|
||||
)
|
||||
|
||||
record_dao = MaterialsToBeDeletedDAO()
|
||||
|
||||
# PERMISSION CHECK: 非管理员用户只获取自己的记录
|
||||
if not self.session_manager.is_admin():
|
||||
marked_records = record_dao.get_materials_by_manager(self.session_manager.get_username())
|
||||
marked_records = record_dao.get_materials_by_manager(
|
||||
self.session_manager.get_username()
|
||||
)
|
||||
else:
|
||||
marked_records = record_dao.get_all_records()
|
||||
|
||||
# Build dictionary: MaterialCode -> ManagerName
|
||||
marked_codes_dict = {
|
||||
r['MaterialCode']: r['ManagerName']
|
||||
r["MaterialCode"]: r["ManagerName"]
|
||||
for r in marked_records
|
||||
if r.get('MaterialCode') and r.get('ManagerName')
|
||||
if r.get("MaterialCode") and r.get("ManagerName")
|
||||
}
|
||||
|
||||
for _, row in df.iterrows():
|
||||
@@ -1080,7 +1150,10 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
if len(df) > 0:
|
||||
self.export_button.config(state=tk.NORMAL)
|
||||
self._update_log(f"已加载 {len(df)} 条结果(其中 {len(marked_codes_dict)} 条已标记删除)", "INFO")
|
||||
self._update_log(
|
||||
f"已加载 {len(df)} 条结果(其中 {len(marked_codes_dict)} 条已标记删除)",
|
||||
"INFO",
|
||||
)
|
||||
|
||||
# 初始化负责人筛选器
|
||||
self._initialize_manager_filter()
|
||||
@@ -1129,8 +1202,8 @@ class MaterialValidationTab(BaseTab):
|
||||
return
|
||||
|
||||
# 收集所有数据
|
||||
to_upsert = [] # 需要写入/更新的记录
|
||||
to_delete = [] # 需要删除的记录
|
||||
to_upsert = [] # 需要写入/更新的记录
|
||||
to_delete = [] # 需要删除的记录
|
||||
missing_manager = [] # 缺少负责人的记录
|
||||
|
||||
for item in self.tree.get_children():
|
||||
@@ -1150,18 +1223,21 @@ class MaterialValidationTab(BaseTab):
|
||||
if not manager_name or not manager_name.strip():
|
||||
missing_manager.append(material_code)
|
||||
else:
|
||||
to_upsert.append({
|
||||
"material_code": material_code,
|
||||
"manager_name": manager_name.strip()
|
||||
})
|
||||
to_upsert.append(
|
||||
{
|
||||
"material_code": material_code,
|
||||
"manager_name": manager_name.strip(),
|
||||
}
|
||||
)
|
||||
else:
|
||||
# 未勾选:需要删除
|
||||
to_delete.append(material_code)
|
||||
|
||||
# 验证:已勾选的记录必须有负责人
|
||||
if missing_manager:
|
||||
msg = f"以下已勾选的记录缺少负责人信息,无法保存:\n\n" + \
|
||||
"\n".join(missing_manager[:10])
|
||||
msg = f"以下已勾选的记录缺少负责人信息,无法保存:\n\n" + "\n".join(
|
||||
missing_manager[:10]
|
||||
)
|
||||
if len(missing_manager) > 10:
|
||||
msg += f"\n... 共 {len(missing_manager)} 条"
|
||||
messagebox.showwarning("警告", msg)
|
||||
@@ -1183,7 +1259,10 @@ class MaterialValidationTab(BaseTab):
|
||||
return
|
||||
|
||||
# 在后台线程中执行
|
||||
self._update_log(f"开始处理:写入/更新 {len(to_upsert)} 条,删除 {len(to_delete)} 条...", "INFO")
|
||||
self._update_log(
|
||||
f"开始处理:写入/更新 {len(to_upsert)} 条,删除 {len(to_delete)} 条...",
|
||||
"INFO",
|
||||
)
|
||||
|
||||
deletion_thread = threading.Thread(
|
||||
target=self._execute_sync_in_background,
|
||||
@@ -1205,7 +1284,7 @@ class MaterialValidationTab(BaseTab):
|
||||
dao = MaterialsToBeDeletedDAO()
|
||||
|
||||
# 执行写入/更新操作
|
||||
upsert_stats = {'total': 0, 'success': 0, 'failed': 0}
|
||||
upsert_stats = {"total": 0, "success": 0, "failed": 0}
|
||||
if to_upsert:
|
||||
upsert_stats = dao.upsert_batch(to_upsert)
|
||||
|
||||
@@ -1216,9 +1295,9 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 组合统计信息
|
||||
combined_stats = {
|
||||
'upsert_success': upsert_stats['success'],
|
||||
'upsert_failed': upsert_stats['failed'],
|
||||
'deleted': delete_count
|
||||
"upsert_success": upsert_stats["success"],
|
||||
"upsert_failed": upsert_stats["failed"],
|
||||
"deleted": delete_count,
|
||||
}
|
||||
|
||||
self.after(0, lambda: self._sync_complete(combined_stats))
|
||||
@@ -1232,9 +1311,9 @@ class MaterialValidationTab(BaseTab):
|
||||
Args:
|
||||
stats: 包含 upsert_success, upsert_failed, deleted 的字典
|
||||
"""
|
||||
upsert_success = stats.get('upsert_success', 0)
|
||||
upsert_failed = stats.get('upsert_failed', 0)
|
||||
deleted = stats.get('deleted', 0)
|
||||
upsert_success = stats.get("upsert_success", 0)
|
||||
upsert_failed = stats.get("upsert_failed", 0)
|
||||
deleted = stats.get("deleted", 0)
|
||||
|
||||
msg_parts = []
|
||||
if upsert_success > 0:
|
||||
@@ -1260,14 +1339,15 @@ class MaterialValidationTab(BaseTab):
|
||||
"""导出结果到 Excel"""
|
||||
# 从配置获取输出文件路径
|
||||
data_dir = self.config.get("paths.data_dir", "data/")
|
||||
validation_filename = self.config.get("paths.validation_output", "物料状态校验结果.xlsx")
|
||||
validation_filename = self.config.get(
|
||||
"paths.validation_output", "物料状态校验结果.xlsx"
|
||||
)
|
||||
output_file = os.path.join(data_dir, validation_filename)
|
||||
|
||||
# 如果配置的文件不存在,提示用户选择位置
|
||||
if not os.path.exists(data_dir):
|
||||
output_dir = filedialog.askdirectory(
|
||||
title="选择输出目录",
|
||||
initialdir=data_dir
|
||||
title="选择输出目录", initialdir=data_dir
|
||||
)
|
||||
if output_dir:
|
||||
self.config.set("paths.data_dir", output_dir)
|
||||
@@ -1279,7 +1359,9 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 如果文件已存在,提示用户覆盖
|
||||
if os.path.exists(output_file):
|
||||
if not messagebox.askyesno("确认覆盖", f"文件已存在:{output_file}\n是否覆盖?"):
|
||||
if not messagebox.askyesno(
|
||||
"确认覆盖", f"文件已存在:{output_file}\n是否覆盖?"
|
||||
):
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -1291,14 +1373,16 @@ class MaterialValidationTab(BaseTab):
|
||||
checkbox = values[0] if values else "☐"
|
||||
is_checked = checkbox == "☑"
|
||||
|
||||
data.append([
|
||||
"是" if is_checked else "否", # 选择状态
|
||||
values[1] if len(values) > 1 else "", # 材料名称
|
||||
values[2] if len(values) > 2 else "", # 材料代码
|
||||
values[3] if len(values) > 3 else "", # 规格
|
||||
values[4] if len(values) > 4 else "", # 型号
|
||||
values[5] if len(values) > 5 else "", # 负责人
|
||||
])
|
||||
data.append(
|
||||
[
|
||||
"是" if is_checked else "否", # 选择状态
|
||||
values[1] if len(values) > 1 else "", # 材料名称
|
||||
values[2] if len(values) > 2 else "", # 材料代码
|
||||
values[3] if len(values) > 3 else "", # 规格
|
||||
values[4] if len(values) > 4 else "", # 型号
|
||||
values[5] if len(values) > 5 else "", # 负责人
|
||||
]
|
||||
)
|
||||
|
||||
if not data:
|
||||
messagebox.showwarning("警告", "没有数据可导出")
|
||||
@@ -1319,7 +1403,9 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
def open_type_management(self):
|
||||
"""打开类型管理窗口"""
|
||||
dialog = MaterialTypeManagementDialog(self, self.session_manager, title="物料类型管理")
|
||||
dialog = MaterialTypeManagementDialog(
|
||||
self, self.session_manager, title="物料类型管理"
|
||||
)
|
||||
|
||||
def start_delete_execution(self):
|
||||
"""开始执行删除"""
|
||||
@@ -1329,7 +1415,7 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
if is_admin:
|
||||
# Admin 用户:检查数据源选择
|
||||
if hasattr(self, 'production_id_source_var'):
|
||||
if hasattr(self, "production_id_source_var"):
|
||||
source = self.production_id_source_var.get()
|
||||
if source == "shared":
|
||||
production_ids = self.shared_production_ids
|
||||
@@ -1337,6 +1423,7 @@ class MaterialValidationTab(BaseTab):
|
||||
production_id_file = self.db_filtered_production_id_selector.get()
|
||||
if production_id_file and os.path.exists(production_id_file):
|
||||
from db.production_order_query import read_production_ids
|
||||
|
||||
production_ids = read_production_ids(production_id_file)
|
||||
else:
|
||||
# 普通用户:使用共享的 Production ID
|
||||
@@ -1344,7 +1431,9 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 验证 Production ID
|
||||
if not production_ids:
|
||||
messagebox.showerror("错误", "没有可用的 Production ID\n请先在校验页面获取数据")
|
||||
messagebox.showerror(
|
||||
"错误", "没有可用的 Production ID\n请先在校验页面获取数据"
|
||||
)
|
||||
return
|
||||
|
||||
# 2. 获取负责人
|
||||
@@ -1358,7 +1447,7 @@ class MaterialValidationTab(BaseTab):
|
||||
|
||||
# 3. 获取 dryrun 设置
|
||||
dryrun = False
|
||||
if is_admin and hasattr(self, 'dryrun_var'):
|
||||
if is_admin and hasattr(self, "dryrun_var"):
|
||||
# Admin 用户:使用界面上的 dryrun 复选框
|
||||
dryrun = self.dryrun_var.get()
|
||||
else:
|
||||
@@ -1384,7 +1473,7 @@ class MaterialValidationTab(BaseTab):
|
||||
title="执行删除",
|
||||
managers=manager_text,
|
||||
dryrun=dryrun,
|
||||
on_cancel=self._cancel_delete_execution
|
||||
on_cancel=self._cancel_delete_execution,
|
||||
)
|
||||
|
||||
self.log_text.info(f"开始执行删除(模式: {'预览' if dryrun else '正式'})...")
|
||||
@@ -1393,14 +1482,14 @@ class MaterialValidationTab(BaseTab):
|
||||
delete_thread = threading.Thread(
|
||||
target=self._delete_worker,
|
||||
args=(production_ids, manager_names, dryrun),
|
||||
daemon=True
|
||||
daemon=True,
|
||||
)
|
||||
delete_thread.start()
|
||||
|
||||
def _cancel_delete_execution(self):
|
||||
"""取消删除执行"""
|
||||
self.log_text.info("用户取消了执行操作")
|
||||
if hasattr(self, 'progress_window') and self.progress_window:
|
||||
if hasattr(self, "progress_window") and self.progress_window:
|
||||
self.progress_window.append_log("正在取消...", "warning")
|
||||
|
||||
def _delete_worker(self, production_ids: list, manager_names: list, dryrun: bool):
|
||||
@@ -1420,9 +1509,11 @@ class MaterialValidationTab(BaseTab):
|
||||
from db.production_order_query import query_production_order_numbers
|
||||
|
||||
# 创建临时文件保存 Production ID
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as f:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w", suffix=".txt", delete=False, encoding="utf-8"
|
||||
) as f:
|
||||
temp_file = f.name
|
||||
f.write('\n'.join(production_ids))
|
||||
f.write("\n".join(production_ids))
|
||||
|
||||
# 进度回调函数
|
||||
def progress_callback(current: int, total: int, message: str):
|
||||
@@ -1439,7 +1530,7 @@ class MaterialValidationTab(BaseTab):
|
||||
headless=self.config.get("erp.headless", True),
|
||||
verbose=True,
|
||||
dryrun=dryrun,
|
||||
progress_callback=progress_callback
|
||||
progress_callback=progress_callback,
|
||||
)
|
||||
|
||||
# 执行清理
|
||||
@@ -1464,12 +1555,13 @@ class MaterialValidationTab(BaseTab):
|
||||
progress_window.append_log(error_msg, "error")
|
||||
progress_window.set_completed()
|
||||
import traceback
|
||||
|
||||
self.after(0, lambda: self._update_log(traceback.format_exc(), "ERROR"))
|
||||
|
||||
finally:
|
||||
# 清理临时文件
|
||||
try:
|
||||
if 'temp_file' in locals() and os.path.exists(temp_file):
|
||||
if "temp_file" in locals() and os.path.exists(temp_file):
|
||||
os.unlink(temp_file)
|
||||
except OSError as e:
|
||||
self.logger.debug(f"清理临时文件失败: {e}")
|
||||
@@ -1484,11 +1576,13 @@ class MaterialValidationTab(BaseTab):
|
||||
"""
|
||||
# 更新日志
|
||||
self.log_text.info("执行完成!")
|
||||
self.log_text.info(f"处理订单: {stats.get('processed_orders', 0)}/{stats.get('total_orders', 0)}")
|
||||
self.log_text.info(
|
||||
f"处理订单: {stats.get('processed_orders', 0)}/{stats.get('total_orders', 0)}"
|
||||
)
|
||||
self.log_text.info(f"删除物料: {len(stats.get('deleted_materials', []))} 条")
|
||||
self.log_text.info(f"跳过物料: {len(stats.get('skipped_materials', []))} 条")
|
||||
self.log_text.info(f"错误数量: {len(stats.get('errors', []))} 个")
|
||||
|
||||
# 显示报告
|
||||
if hasattr(self, 'progress_window') and self.progress_window:
|
||||
if hasattr(self, "progress_window") and self.progress_window:
|
||||
self.progress_window.show_report(report)
|
||||
|
||||
Reference in New Issue
Block a user