refactor: sync all rows with database on confirmation

Changed "确认删除" button to process ALL table rows (both checked and unchecked):
- Checked rows (☑): Upsert MaterialCode and ManagerName to database
- Unchecked rows (☐): Delete MaterialCode from database
- Validation: Checked rows must have ManagerName

This replaces the previous behavior that only processed checked items.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-02-06 17:42:32 +08:00
parent cd21e54abf
commit 13d6c20383

View File

@@ -29,16 +29,18 @@ class CheckboxTreeview(ttk.Treeview):
- ☑ 选中
"""
def __init__(self, parent, **kwargs):
def __init__(self, parent, on_checkbox_change=None, **kwargs):
"""初始化 CheckboxTreeview
Args:
parent: 父容器
on_checkbox_change: checkbox 状态改变时的回调函数
**kwargs: 传递给 Treeview 的参数
"""
super().__init__(parent, **kwargs)
self.checkboxes = {} # item_id -> bool
self.checkbox_column = "选择"
self.on_checkbox_change = on_checkbox_change # checkbox 状态改变回调
# 绑定点击事件
self.bind("<Button-1>", self._on_click)
@@ -56,7 +58,13 @@ class CheckboxTreeview(ttk.Treeview):
if column == "#1" and item:
# 切换 checkbox 状态
current_state = self.checkboxes.get(item, False)
self.set_checked(item, not current_state)
new_state = not current_state
self.set_checked(item, new_state)
# 通知父组件 checkbox 状态已改变
if self.on_checkbox_change:
self.on_checkbox_change(item, new_state)
return "break" # 阻止默认行为
def set_checked(self, item, checked: bool):
@@ -283,7 +291,13 @@ class MaterialValidationTab(ttk.Frame):
"""创建结果表格"""
# 新列定义:选择、材料名称、材料代码、规格、型号、负责人
columns = ("选择", "材料名称", "材料代码", "规格", "型号", "负责人")
self.tree = CheckboxTreeview(parent, columns=columns, show="headings", height=10)
self.tree = CheckboxTreeview(
parent,
columns=columns,
show="headings",
height=10,
on_checkbox_change=self._sync_checkbox_by_material_code
)
# 设置列标题和宽度
self.tree.heading("选择", text="选择")
@@ -369,6 +383,38 @@ class MaterialValidationTab(ttk.Frame):
self.log_text.info(f"已更新负责人: {current_value} -> {new_value}")
def _sync_checkbox_by_material_code(self, changed_item: str, new_state: bool):
"""同步相同材料代码的所有记录的选择状态
当用户点击某个记录的 checkbox 时,自动将所有具有相同材料代码的记录
的 checkbox 状态同步更新。
Args:
changed_item: 被点击的 item ID
new_state: 新的 checkbox 状态
"""
# 获取被点击行的材料代码
values = self.tree.item(changed_item, "values")
if not values or len(values) <= 2:
return
material_code = values[2] # 材料代码在第3列索引2
# 同步所有具有相同材料代码的记录
synced_count = 0
for item in self.tree.get_children():
item_values = self.tree.item(item, "values")
if item_values and len(item_values) > 2:
if item_values[2] == material_code and item != changed_item:
# 只更新状态不同的行,避免重复更新
current_state = self.tree.checkboxes.get(item, False)
if current_state != new_state:
self.tree.set_checked(item, new_state)
synced_count += 1
if synced_count > 0:
action = "选中" if new_state else "取消选中"
self.log_text.info(f"已同步 {synced_count} 条相同材料代码的记录{action}")
def _select_all(self, check: bool = True):
"""全选或取消全选
@@ -558,87 +604,136 @@ class MaterialValidationTab(ttk.Frame):
self._update_log(f"加载结果失败:{str(e)}", "ERROR")
def confirm_deletion(self):
"""确认删除主流程"""
# 获取选中的 item
checked_items = self.tree.get_checked_items()
"""确认删除主流程
if not checked_items:
messagebox.showwarning("警告", "请先选择要删除的记录")
处理所有行:
- 已勾选的行:写入/更新到数据库(负责人不能为空)
- 未勾选的行:从数据库中删除
"""
if not self.tree.get_children():
messagebox.showwarning("警告", "没有可处理的数据")
return
# 收集数据
materials = []
missing_manager = []
# 收集所有数据
to_upsert = [] # 需要写入/更新的记录
to_delete = [] # 需要删除的记录
missing_manager = [] # 缺少负责人的记录
for item in checked_items:
for item in self.tree.get_children():
values = self.tree.item(item, "values")
is_checked = self.tree.checkboxes.get(item, False)
material_code = values[2] if len(values) > 2 else ""
manager_name = values[5] if len(values) > 5 else ""
# 跳过材料代码为空的行
if not material_code or not material_code.strip():
continue
if not manager_name or not manager_name.strip():
missing_manager.append(material_code)
else:
materials.append({
"material_code": material_code.strip(),
"manager_name": manager_name.strip()
})
material_code = material_code.strip()
# 验证负责人
if is_checked:
# 已勾选:需要写入/更新
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()
})
else:
# 未勾选:需要删除
to_delete.append(material_code)
# 验证:已勾选的记录必须有负责人
if missing_manager:
msg = f"以下选的记录缺少负责人信息,无法删除\n\n" + \
"\n".join(missing_manager[:10]) # 最多显示10个
msg = f"以下已勾选的记录缺少负责人信息,无法保存\n\n" + \
"\n".join(missing_manager[:10])
if len(missing_manager) > 10:
msg += f"\n... 共 {len(missing_manager)}"
messagebox.showwarning("警告", msg)
return
if not materials:
messagebox.showwarning("警告", "没有有效的记录可删除")
# 构建确认消息
confirm_parts = []
if to_upsert:
confirm_parts.append(f"写入/更新 {len(to_upsert)} 条记录")
if to_delete:
confirm_parts.append(f"删除 {len(to_delete)} 条记录")
if not to_upsert and not to_delete:
messagebox.showinfo("提示", "没有需要处理的记录")
return
# 显示确认对话框
confirm_msg = f"确认要将 {len(materials)} 条记录写入删除表吗?\n\n"
confirm_msg += "这些记录将被标记为待删除状态。"
if not messagebox.askyesno("确认删除", confirm_msg):
confirm_msg = "确认以下操作吗?\n\n" + "\n".join(confirm_parts)
if not messagebox.askyesno("确认操作", confirm_msg):
return
# 执行删除操作(后台线程
self._update_log(f"开始处理 {len(materials)}记录...", "INFO")
# 后台线程中执行
self._update_log(f"开始处理:写入/更新 {len(to_upsert)} 条,删除 {len(to_delete)} 条...", "INFO")
deletion_thread = threading.Thread(
target=self._execute_deletion_in_background,
args=(materials,),
target=self._execute_sync_in_background,
args=(to_upsert, to_delete),
daemon=True,
)
deletion_thread.start()
def _execute_deletion_in_background(self, materials: list):
"""后台线程执行删除操作"""
def _execute_sync_in_background(self, to_upsert: list, to_delete: list):
"""后台线程执行同步操作
Args:
to_upsert: 需要写入/更新的记录列表
to_delete: 需要删除的材料代码列表
"""
try:
from db.materials_to_be_deleted_records_dao import MaterialsToBeDeletedDAO
dao = MaterialsToBeDeletedDAO()
stats = dao.upsert_batch(materials)
self.after(0, lambda: self._deletion_complete(stats))
# 执行写入/更新操作
upsert_stats = {'total': 0, 'success': 0, 'failed': 0}
if to_upsert:
upsert_stats = dao.upsert_batch(to_upsert)
# 执行删除操作
delete_count = 0
if to_delete:
delete_count = dao.delete_by_material_codes(to_delete)
# 组合统计信息
combined_stats = {
'upsert_success': upsert_stats['success'],
'upsert_failed': upsert_stats['failed'],
'deleted': delete_count
}
self.after(0, lambda: self._sync_complete(combined_stats))
except Exception as e:
self.after(0, lambda: self._update_log(f"删除操作失败:{str(e)}", "ERROR"))
self.after(0, lambda: self._update_log(f"同步操作失败:{str(e)}", "ERROR"))
def _deletion_complete(self, stats: dict):
"""删除完成回调,更新 UI"""
total = stats.get('total', 0)
success = stats.get('success', 0)
failed = stats.get('failed', 0)
def _sync_complete(self, stats: dict):
"""同步完成回调,更新 UI
msg = f"删除操作完成!\n\n总计:{total}\n成功:{success}\n失败:{failed}"
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)
if failed > 0:
messagebox.showwarning("完成", msg)
msg_parts = []
if upsert_success > 0:
msg_parts.append(f"写入/更新成功:{upsert_success}")
if upsert_failed > 0:
msg_parts.append(f"写入/更新失败:{upsert_failed}")
if deleted > 0:
msg_parts.append(f"删除成功:{deleted}")
msg = "同步操作完成!\n\n" + "\n".join(msg_parts)
if upsert_failed > 0:
messagebox.showwarning("完成(部分失败)", msg)
self._update_log(msg, "WARNING")
else:
messagebox.showinfo("成功", msg)