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:
@@ -29,16 +29,18 @@ class CheckboxTreeview(ttk.Treeview):
|
|||||||
- ☑ 选中
|
- ☑ 选中
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent, **kwargs):
|
def __init__(self, parent, on_checkbox_change=None, **kwargs):
|
||||||
"""初始化 CheckboxTreeview
|
"""初始化 CheckboxTreeview
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
parent: 父容器
|
parent: 父容器
|
||||||
|
on_checkbox_change: checkbox 状态改变时的回调函数
|
||||||
**kwargs: 传递给 Treeview 的参数
|
**kwargs: 传递给 Treeview 的参数
|
||||||
"""
|
"""
|
||||||
super().__init__(parent, **kwargs)
|
super().__init__(parent, **kwargs)
|
||||||
self.checkboxes = {} # item_id -> bool
|
self.checkboxes = {} # item_id -> bool
|
||||||
self.checkbox_column = "选择"
|
self.checkbox_column = "选择"
|
||||||
|
self.on_checkbox_change = on_checkbox_change # checkbox 状态改变回调
|
||||||
|
|
||||||
# 绑定点击事件
|
# 绑定点击事件
|
||||||
self.bind("<Button-1>", self._on_click)
|
self.bind("<Button-1>", self._on_click)
|
||||||
@@ -56,7 +58,13 @@ class CheckboxTreeview(ttk.Treeview):
|
|||||||
if column == "#1" and item:
|
if column == "#1" and item:
|
||||||
# 切换 checkbox 状态
|
# 切换 checkbox 状态
|
||||||
current_state = self.checkboxes.get(item, False)
|
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" # 阻止默认行为
|
return "break" # 阻止默认行为
|
||||||
|
|
||||||
def set_checked(self, item, checked: bool):
|
def set_checked(self, item, checked: bool):
|
||||||
@@ -283,7 +291,13 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
"""创建结果表格"""
|
"""创建结果表格"""
|
||||||
# 新列定义:选择、材料名称、材料代码、规格、型号、负责人
|
# 新列定义:选择、材料名称、材料代码、规格、型号、负责人
|
||||||
columns = ("选择", "材料名称", "材料代码", "规格", "型号", "负责人")
|
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="选择")
|
self.tree.heading("选择", text="选择")
|
||||||
@@ -369,6 +383,38 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
|
|
||||||
self.log_text.info(f"已更新负责人: {current_value} -> {new_value}")
|
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):
|
def _select_all(self, check: bool = True):
|
||||||
"""全选或取消全选
|
"""全选或取消全选
|
||||||
|
|
||||||
@@ -558,87 +604,136 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
self._update_log(f"加载结果失败:{str(e)}", "ERROR")
|
self._update_log(f"加载结果失败:{str(e)}", "ERROR")
|
||||||
|
|
||||||
def confirm_deletion(self):
|
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
|
return
|
||||||
|
|
||||||
# 收集数据
|
# 收集所有数据
|
||||||
materials = []
|
to_upsert = [] # 需要写入/更新的记录
|
||||||
missing_manager = []
|
to_delete = [] # 需要删除的记录
|
||||||
|
missing_manager = [] # 缺少负责人的记录
|
||||||
|
|
||||||
for item in checked_items:
|
for item in self.tree.get_children():
|
||||||
values = self.tree.item(item, "values")
|
values = self.tree.item(item, "values")
|
||||||
|
is_checked = self.tree.checkboxes.get(item, False)
|
||||||
material_code = values[2] if len(values) > 2 else ""
|
material_code = values[2] if len(values) > 2 else ""
|
||||||
manager_name = values[5] if len(values) > 5 else ""
|
manager_name = values[5] if len(values) > 5 else ""
|
||||||
|
|
||||||
|
# 跳过材料代码为空的行
|
||||||
if not material_code or not material_code.strip():
|
if not material_code or not material_code.strip():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not manager_name or not manager_name.strip():
|
material_code = material_code.strip()
|
||||||
missing_manager.append(material_code)
|
|
||||||
else:
|
|
||||||
materials.append({
|
|
||||||
"material_code": material_code.strip(),
|
|
||||||
"manager_name": manager_name.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:
|
if missing_manager:
|
||||||
msg = f"以下选中的记录缺少负责人信息,无法删除:\n\n" + \
|
msg = f"以下已勾选的记录缺少负责人信息,无法保存:\n\n" + \
|
||||||
"\n".join(missing_manager[:10]) # 最多显示10个
|
"\n".join(missing_manager[:10])
|
||||||
if len(missing_manager) > 10:
|
if len(missing_manager) > 10:
|
||||||
msg += f"\n... 共 {len(missing_manager)} 条"
|
msg += f"\n... 共 {len(missing_manager)} 条"
|
||||||
messagebox.showwarning("警告", msg)
|
messagebox.showwarning("警告", msg)
|
||||||
return
|
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
|
return
|
||||||
|
|
||||||
# 显示确认对话框
|
confirm_msg = "确认以下操作吗?\n\n" + "\n".join(confirm_parts)
|
||||||
confirm_msg = f"确认要将 {len(materials)} 条记录写入删除表吗?\n\n"
|
if not messagebox.askyesno("确认操作", confirm_msg):
|
||||||
confirm_msg += "这些记录将被标记为待删除状态。"
|
|
||||||
|
|
||||||
if not messagebox.askyesno("确认删除", confirm_msg):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# 执行删除操作(后台线程)
|
# 在后台线程中执行
|
||||||
self._update_log(f"开始处理 {len(materials)} 条记录...", "INFO")
|
self._update_log(f"开始处理:写入/更新 {len(to_upsert)} 条,删除 {len(to_delete)} 条...", "INFO")
|
||||||
|
|
||||||
deletion_thread = threading.Thread(
|
deletion_thread = threading.Thread(
|
||||||
target=self._execute_deletion_in_background,
|
target=self._execute_sync_in_background,
|
||||||
args=(materials,),
|
args=(to_upsert, to_delete),
|
||||||
daemon=True,
|
daemon=True,
|
||||||
)
|
)
|
||||||
deletion_thread.start()
|
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:
|
try:
|
||||||
from db.materials_to_be_deleted_records_dao import MaterialsToBeDeletedDAO
|
from db.materials_to_be_deleted_records_dao import MaterialsToBeDeletedDAO
|
||||||
|
|
||||||
dao = 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:
|
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):
|
def _sync_complete(self, stats: dict):
|
||||||
"""删除完成回调,更新 UI"""
|
"""同步完成回调,更新 UI
|
||||||
total = stats.get('total', 0)
|
|
||||||
success = stats.get('success', 0)
|
|
||||||
failed = stats.get('failed', 0)
|
|
||||||
|
|
||||||
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:
|
msg_parts = []
|
||||||
messagebox.showwarning("完成", msg)
|
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")
|
self._update_log(msg, "WARNING")
|
||||||
else:
|
else:
|
||||||
messagebox.showinfo("成功", msg)
|
messagebox.showinfo("成功", msg)
|
||||||
|
|||||||
Reference in New Issue
Block a user