Merge branch 'mysql' into dev
Resolved merge conflict in utils/material_status_validator.py by keeping both deduplication statistics and error messages for empty results. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -176,7 +176,7 @@ class DataExtractionTab(ttk.Frame):
|
||||
temp_file = f.name
|
||||
f.write('\n'.join(production_ids))
|
||||
|
||||
from utils.离散备料计划维护数据提取 import DiscreteMaterialPlanExtractor
|
||||
from utils.discrete_material_plan_extractor import DiscreteMaterialPlanExtractor
|
||||
self.extractor = DiscreteMaterialPlanExtractor(
|
||||
username=self.config.get("erp.username"),
|
||||
password=self.config.get("erp.password"),
|
||||
|
||||
@@ -385,11 +385,11 @@ class MaterialTypeManagementDialog(tk.Toplevel):
|
||||
main_container = ttk.Frame(self, padding=10)
|
||||
main_container.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 顶部:筛选区域
|
||||
filter_frame = ttk.LabelFrame(main_container, text="筛选(按负责人)", padding=10)
|
||||
filter_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
self._create_filter_area(filter_frame)
|
||||
# 顶部:筛选区域 - 仅管理员可见
|
||||
if self.session_manager.is_admin():
|
||||
filter_frame = ttk.LabelFrame(main_container, text="筛选(按负责人)", padding=10)
|
||||
filter_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
self._create_filter_area(filter_frame)
|
||||
|
||||
# 中部:数据表格
|
||||
table_frame = ttk.LabelFrame(main_container, text="数据列表(双击编辑,Delete删除,Insert新增)", padding=10)
|
||||
@@ -513,19 +513,16 @@ class MaterialTypeManagementDialog(tk.Toplevel):
|
||||
|
||||
def _create_filter_checkboxes(self):
|
||||
"""创建筛选复选框"""
|
||||
# PERMISSION CHECK: 非管理员用户不创建筛选 UI
|
||||
if not self.session_manager.is_admin():
|
||||
self.managers = [self.session_manager.get_username()]
|
||||
return
|
||||
|
||||
# 管理员:清空并重新创建复选框
|
||||
for widget in self.filter_frame.winfo_children():
|
||||
widget.destroy()
|
||||
self.manager_checkboxes.clear()
|
||||
|
||||
# PERMISSION CHECK: 非管理员用户隐藏筛选 UI
|
||||
if not self.session_manager.is_admin():
|
||||
self.managers = [self.session_manager.get_username()]
|
||||
ttk.Label(
|
||||
self.filter_frame,
|
||||
text=f"仅显示您的数据(负责人:{self.session_manager.get_username()})"
|
||||
).pack(anchor="w")
|
||||
return
|
||||
|
||||
# 管理员:获取所有负责人
|
||||
from db.materials_to_be_deleted_dao import MaterialsTypeToBeDeletedDAO
|
||||
dao = MaterialsTypeToBeDeletedDAO()
|
||||
|
||||
@@ -47,14 +47,26 @@ class CheckboxTreeview(ttk.Treeview):
|
||||
self.checkbox_column = "选择"
|
||||
self.on_checkbox_change = on_checkbox_change # checkbox 状态改变回调
|
||||
|
||||
# 排序状态
|
||||
self.sort_column = None # 当前排序列的列标识符
|
||||
self.sort_direction = None # 'asc', 'desc', 或 None
|
||||
self.sortable_columns = ["选择", "材料名称"] # 可排序的列白名单
|
||||
self.original_headings = {} # 存储原始列标题文本(不含箭头)
|
||||
|
||||
# 存储原始列标题(延迟执行以确保标题已设置)
|
||||
self.after(100, self._store_original_headings)
|
||||
|
||||
# 绑定点击事件
|
||||
self.bind("<Button-1>", self._on_click)
|
||||
# 绑定表头点击事件
|
||||
self.bind("<ButtonRelease-1>", self._on_heading_click)
|
||||
|
||||
def _on_click(self, event):
|
||||
"""处理点击事件,切换 checkbox 状态"""
|
||||
# 获取点击位置对应的 item 和 column
|
||||
region = self.identify_region(event.x, event.y)
|
||||
|
||||
# 仅处理单元格点击,不处理表头点击
|
||||
if region == "cell":
|
||||
column = self.identify_column(event.x)
|
||||
item = self.identify_row(event.y)
|
||||
@@ -129,6 +141,115 @@ class CheckboxTreeview(ttk.Treeview):
|
||||
del self.checkboxes[item]
|
||||
super().delete(*items)
|
||||
|
||||
def _store_original_headings(self):
|
||||
"""存储原始列标题文本(不含箭头)"""
|
||||
for col in self['columns']:
|
||||
self.original_headings[col] = self.heading(col, 'text')
|
||||
|
||||
def _get_column_id_from_column_index(self, column_index):
|
||||
"""将列索引 ('#1', '#2') 转换为列标识符
|
||||
|
||||
Args:
|
||||
column_index: 列索引字符串,如 '#1', '#2'
|
||||
|
||||
Returns:
|
||||
列标识符,如 '选择', '材料名称'
|
||||
"""
|
||||
index = int(column_index[1:]) - 1
|
||||
columns = self['columns']
|
||||
if 0 <= index < len(columns):
|
||||
return columns[index]
|
||||
return None
|
||||
|
||||
def _on_heading_click(self, event):
|
||||
"""处理表头点击事件,触发排序"""
|
||||
region = self.identify_region(event.x, event.y)
|
||||
|
||||
if region == "heading":
|
||||
column = self.identify_column(event.x)
|
||||
column_id = self._get_column_id_from_column_index(column)
|
||||
|
||||
# 仅对可排序列进行排序
|
||||
if column_id in self.sortable_columns:
|
||||
self._toggle_sort(column_id)
|
||||
|
||||
def _toggle_sort(self, column_id):
|
||||
"""切换指定列的排序状态
|
||||
|
||||
Args:
|
||||
column_id: 列标识符(如 '选择', '材料名称')
|
||||
"""
|
||||
# 确定新的排序方向
|
||||
if self.sort_column == column_id:
|
||||
# 同一列:asc -> desc -> None
|
||||
if self.sort_direction == 'asc':
|
||||
new_direction = 'desc'
|
||||
elif self.sort_direction == 'desc':
|
||||
new_direction = None
|
||||
else:
|
||||
new_direction = 'asc'
|
||||
else:
|
||||
# 不同列:从升序开始
|
||||
new_direction = 'asc'
|
||||
|
||||
# 应用排序
|
||||
if new_direction:
|
||||
self._sort_by_column(column_id, new_direction)
|
||||
self.sort_column = column_id
|
||||
self.sort_direction = new_direction
|
||||
else:
|
||||
# 清除排序状态
|
||||
self.sort_column = None
|
||||
self.sort_direction = None
|
||||
|
||||
# 更新表头显示
|
||||
self._update_heading_display()
|
||||
|
||||
def _sort_by_column(self, column_id, direction):
|
||||
"""按指定列和方向排序
|
||||
|
||||
Args:
|
||||
column_id: 列标识符
|
||||
direction: 'asc' 或 'desc'
|
||||
"""
|
||||
# 收集所有项目及其数据和复选框状态
|
||||
items_data = []
|
||||
for item in self.get_children():
|
||||
values = self.item(item, "values")
|
||||
checkbox_state = self.checkboxes.get(item, False)
|
||||
items_data.append({
|
||||
'item_id': item,
|
||||
'values': values,
|
||||
'checked': checkbox_state
|
||||
})
|
||||
|
||||
# 根据列和方向排序
|
||||
if column_id == "选择":
|
||||
# 按复选框状态排序(选中在前,未选中在后)
|
||||
items_data.sort(key=lambda x: x['checked'], reverse=(direction == 'desc'))
|
||||
elif column_id == "材料名称":
|
||||
# 按材料名称排序
|
||||
items_data.sort(
|
||||
key=lambda x: str(x['values'][1]) if len(x['values']) > 1 else "",
|
||||
reverse=(direction == 'desc')
|
||||
)
|
||||
|
||||
# 重新排列项目顺序(使用 detach 和 move 保留项目ID和状态)
|
||||
for item_data in items_data:
|
||||
self.move(item_data['item_id'], '', 'end')
|
||||
|
||||
def _update_heading_display(self):
|
||||
"""更新列标题显示(添加/移除排序箭头)"""
|
||||
for col in self['columns']:
|
||||
original = self.original_headings.get(col, col)
|
||||
if col == self.sort_column:
|
||||
# 添加排序箭头
|
||||
arrow = " ↑" if self.sort_direction == 'asc' else " ↓"
|
||||
self.heading(col, text=original + arrow)
|
||||
else:
|
||||
# 移除箭头,显示原始标题
|
||||
self.heading(col, text=original)
|
||||
|
||||
|
||||
class MaterialValidationTab(ttk.Frame):
|
||||
"""物料校验标签页"""
|
||||
|
||||
Reference in New Issue
Block a user