diff --git a/docs/MATERIAL_VALIDATION_INTERFACE.md b/docs/MATERIAL_VALIDATION_INTERFACE.md new file mode 100644 index 0000000..a80ec0f --- /dev/null +++ b/docs/MATERIAL_VALIDATION_INTERFACE.md @@ -0,0 +1,991 @@ +# 物料校验界面实现说明文档 + +## 1. 概述 + +### 功能简介 +物料校验界面是一个用于校验物料状态并管理待删除物料的核心功能模块。该界面支持两种校验模式,实现了物料状态与数据库记录的智能匹配,并提供可视化的管理界面。 + +### 核心文件路径 +``` +gui/material_validation_tab.py - 主界面实现 +utils/material_status_validator.py - 校验器核心逻辑 +db/discrete_material_plan_dao.py - 物料数据访问 +db/materials_to_be_deleted_dao.py - 待删除类型数据访问 +db/materials_to_be_deleted_records_dao.py - 已标记记录数据访问 +db/production_contract_data_dao.py - 生产合同数据访问 +auth/session_manager.py - 会话管理(权限控制) +``` + +### 相关数据库表 +- **DiscreteMaterialPlanData** - 离散备料计划数据表(主数据源) +- **MaterialsTypeToBeDeleted** - 待删除物料类型表(按物料名称匹配) +- **MaterialsToBeDeleted** - 已标记删除的物料记录表(按物料代码精确匹配) +- **[productionContractData].[26年压力表合同数据]** - 生产合同数据表 + +--- + +## 2. 系统架构 + +### 2.1 整体架构图 + +```mermaid +graph TD + A[用户界面层] --> B[MaterialValidationTab] + B --> C[CheckboxTreeview] + B --> D[控制面板] + B --> E[负责人筛选器] + B --> F[日志面板] + + D --> G[校验模式选择] + G --> H[database_full
全表校验] + G --> I[database_filtered
ProductionID过滤] + + H --> J[MaterialStatusValidator] + I --> J + + J --> K[DiscreteMaterialPlanDAO] + J --> L[ProductionContractDataDAO] + J --> M[MaterialsTypeToBeDeletedDAO] + J --> N[MaterialsToBeDeletedDAO] + + K --> O[(DiscreteMaterialPlanData)] + L --> P[(26年压力表合同数据)] + M --> Q[(MaterialsTypeToBeDeleted)] + N --> R[(MaterialsToBeDeleted)] + + B --> S[SessionManager] + S --> T{用户类型} + T -->|Admin| U[完整权限] + T -->|User| V[受限权限] + + style A fill:#e1f5ff + style B fill:#fff4e1 + style J fill:#ffe1f5 + style O fill:#e1ffe1 + style P fill:#e1ffe1 + style Q fill:#e1ffe1 + style R fill:#e1ffe1 +``` + +### 2.2 组件关系图 + +```mermaid +classDiagram + class MaterialValidationTab { + +start_validation() + +confirm_deletion() + +export_results() + -_validation_worker_enhanced() + -_apply_manager_filter() + -_sync_checkbox_by_material_code() + } + + class CheckboxTreeview { + +set_checked(item, checked) + +get_checked_items() + +check_all(checked) + -_on_click(event) + } + + class MaterialStatusValidator { + +validate_from_database_enhanced() + +match_materials_detailed() + -_get_source_numbers_from_production_ids() + -_get_material_names_from_db() + } + + class DiscreteMaterialPlanDAO { + +query_all() + +query_by_source_numbers() + +get_unique_material_names() + } + + class ProductionContractDataDAO { + +get_source_numbers_by_总排号() + +query_by_总排号() + } + + class MaterialsTypeToBeDeletedDAO { + +get_all_materials() + +get_managers() + } + + class MaterialsToBeDeletedDAO { + +get_all_records() + +upsert_batch() + +delete_by_material_codes() + } + + class SessionManager { + +is_admin() + +get_username() + +login() + } + + class MaterialValidationResult { + +material_name + +material_code + +manager_name + +is_marked_for_deletion + +matched_type_keyword + } + + MaterialValidationTab --> CheckboxTreeview : contains + MaterialValidationTab --> MaterialStatusValidator : uses + MaterialValidationTab --> SessionManager : uses + MaterialStatusValidator --> DiscreteMaterialPlanDAO : queries + MaterialStatusValidator --> ProductionContractDataDAO : queries + MaterialStatusValidator --> MaterialsTypeToBeDeletedDAO : queries + MaterialStatusValidator --> MaterialsToBeDeletedDAO : queries + MaterialStatusValidator --> MaterialValidationResult : creates +``` + +--- + +## 3. 数据流程 + +### 3.1 校验流程图 + +```mermaid +flowchart TD + Start([开始校验]) --> CheckMode{选择校验模式} + + CheckMode -->|database_full| FullMode[全表校验] + CheckMode -->|database_filtered| FilterMode[过滤校验] + + FullMode --> QueryAll[查询 DiscreteMaterialPlanData
获取所有记录] + FilterMode --> ReadProductionID[读取 ProductionID.txt] + ReadProductionID --> QueryContract[查询生产合同数据
获取 SourceNumber] + QueryContract --> QueryBySource[按 SourceNumber
查询物料记录] + + QueryAll --> FetchType[获取待删除物料类型] + QueryBySource --> FetchType + + FetchType --> GetTypeRecords[MaterialsTypeToBeDeleted
获取所有记录] + GetTypeRecords --> GetMarkedRecords[MaterialsToBeDeleted
获取已标记记录] + + GetMarkedRecords --> BuildDict[构建 MaterialCode->ManagerName
映射字典] + BuildDict --> Match[执行双优先级匹配] + + Match --> Priority1{优先级1:
精确匹配?} + Priority1 -->|是| SetMarked[设置 is_marked=true
使用 MaterialsToBeDeleted.ManagerName] + Priority1 -->|否| Priority2{优先级2:
模糊匹配?} + + Priority2 -->|是| SetKeyword[设置 matched_keyword
使用 MaterialsTypeToBeDeleted.ManagerName] + Priority2 -->|否| SetUnmatched[设置 manager_name=null
is_marked=false] + + SetMarked --> CreateResult[创建 MaterialValidationResult] + SetKeyword --> CreateResult + SetUnmatched --> CreateResult + + CreateResult --> Cache[缓存结果记录] + Cache --> Display[显示在界面表格] + Display --> InitFilter[初始化负责人筛选器] + InitFilter --> End([完成]) + + style Start fill:#e1f5ff + style End fill:#e1f5ff + style Match fill:#ffe1f5 + style CreateResult fill:#fff4e1 +``` + +### 3.2 数据查询链路图 + +```mermaid +flowchart LR + A[ProductionID.txt
总排号列表] --> B[ProductionContractDataDAO] + B --> C[[26年压力表合同数据]] + C --> D[SourceNumber
生产订单号] + + D --> E[DiscreteMaterialPlanDAO] + E --> F[[DiscreteMaterialPlanData]] + F --> G[物料记录列表
MaterialCode, MaterialName
Specification, Model] + + G --> H[MaterialStatusValidator] + H --> I[MaterialsTypeToBeDeletedDAO] + H --> J[MaterialsToBeDeletedDAO] + + I --> K[[MaterialsTypeToBeDeleted]] + J --> L[[MaterialsToBeDeleted]] + + K --> M[物料类型匹配
MaterialName 包含匹配] + L --> N[精确匹配
MaterialCode 精确匹配] + + M --> O[双优先级匹配算法] + N --> O + + O --> P[MaterialValidationResult
校验结果] + + style A fill:#ffe1f5 + style F fill:#ffe1f5 + style K fill:#ffe1f5 + style L fill:#ffe1f5 + style P fill:#e1f5ff +``` + +### 3.3 交互时序图 + +```mermaid +sequenceDiagram + actor User as 用户 + participant UI as MaterialValidationTab + participant Validator as MaterialStatusValidator + participant DAO1 as DiscreteMaterialPlanDAO + participant DAO2 as ProductionContractDataDAO + participant DAO3 as MaterialsTypeToBeDeletedDAO + participant DAO4 as MaterialsToBeDeletedDAO + + User->>UI: 点击"开始校验" + UI->>UI: 验证输入文件 + UI->>Validator: 创建校验器实例 + UI->>UI: 启动后台线程 + + alt 全表校验模式 + Validator->>DAO1: query_all() + DAO1-->>Validator: 返回所有物料记录 + else ProductionID过滤模式 + Validator->>DAO2: get_source_numbers_by_总排号() + DAO2-->>Validator: 返回 SourceNumber 列表 + Validator->>DAO1: query_by_source_numbers() + DAO1-->>Validator: 返回过滤后的物料记录 + end + + Validator->>DAO3: get_all_materials() + DAO3-->>Validator: 返回物料类型记录 + + Validator->>DAO4: get_all_records() + DAO4-->>Validator: 返回已标记记录 + + Validator->>Validator: match_materials_detailed() + Note over Validator: 执行双优先级匹配 + + Validator-->>UI: 返回校验结果 + UI->>UI: 缓存结果记录 + UI->>UI: 更新表格显示 + UI->>UI: 初始化负责人筛选器 + + UI-->>User: 显示校验完成 +``` + +--- + +## 4. 核心组件说明 + +### 4.1 MaterialValidationTab 类 + +**职责**: 物料校验标签页的主界面类 + +**核心方法**: + +| 方法名 | 功能说明 | +|--------|----------| +| `start_validation()` | 启动校验流程,根据选择的模式调用相应的校验方法 | +| `_validation_worker_enhanced()` | 后台工作线程,执行增强的数据库校验 | +| `confirm_deletion()` | 确认删除操作主流程,处理勾选状态 | +| `_execute_sync_in_background()` | 后台执行数据库同步操作(upsert/delete) | +| `_apply_manager_filter()` | 应用负责人筛选,更新表格显示 | +| `_sync_checkbox_by_material_code()` | 同步相同材料代码的所有记录的选择状态 | +| `_load_results_with_deletion_status()` | 加载结果并设置 checkbox 选中状态 | +| `export_results()` | 导出结果到 Excel 文件 | + +**权限控制**: +- **Admin 用户**: 可以选择数据来源(全表/过滤),可以看到所有负责人的数据,可以使用负责人筛选 +- **User 用户**: 只能使用 ProductionID 过滤模式,只能看到自己的数据,自动筛选到当前用户 + +### 4.2 CheckboxTreeview 类 + +**职责**: 支持复选框功能的 Treeview 组件 + +**核心特性**: +- 使用 Unicode 字符 `☐` 和 `☑` 模拟 checkbox +- 支持单个点击切换状态 +- 支持全选/取消全选操作 +- 支持状态变化回调 + +**核心方法**: + +| 方法名 | 功能说明 | +|--------|----------| +| `set_checked(item, checked)` | 设置指定 item 的 checkbox 状态 | +| `get_checked_items()` | 获取所有选中的 item | +| `check_all(checked)` | 全选或取消全选 | +| `_on_click(event)` | 处理点击事件,切换 checkbox 状态 | + +### 4.3 MaterialStatusValidator 类 + +**职责**: 物料状态校验器,负责数据查询和匹配逻辑 + +**核心方法**: + +| 方法名 | 功能说明 | +|--------|----------| +| `validate_from_database_enhanced()` | 增强的数据库校验(完整记录模式) | +| `match_materials_detailed()` | 匹配物料并返回详细结果 | +| `_read_production_ids()` | 读取 ProductionID.txt 文件 | +| `_get_source_numbers_from_production_ids()` | 通过总排号查询获取生产订单号 | +| `_get_material_names_from_db()` | 从数据库获取材料名称 | + +**数据结构**: + +```python +@dataclass +class MaterialValidationResult: + material_name: str # 材料名称 + material_code: str # 材料代码 + specification: Optional[str] # 规格 + model: Optional[str] # 型号 + manager_name: Optional[str] # 负责人 + is_marked_for_deletion: bool # 是否已标记删除 + matched_type_keyword: Optional[str] # 匹配的关键词 +``` + +--- + +## 5. 数据库表结构 + +### 5.1 DiscreteMaterialPlanData(离散备料计划数据表) + +**用途**: 存储离散备料计划的主数据,是校验的主要数据源 + +**关键字段**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| PlanNumber | varchar | 备料计划单号 | +| SourceNumber | varchar | 来源单号(生产订单号) | +| MaterialCode | varchar | 材料编码(用于精确匹配) | +| MaterialName | varchar | 材料名称 | +| Specification | varchar | 规格 | +| Model | varchar | 型号 | +| ManagerName | varchar | 负责人 | + +**查询示例**: +```python +# 查询所有记录 +dao = DiscreteMaterialPlanDAO() +records = dao.query_all() + +# 按生产订单号查询 +records = dao.query_by_source_numbers(source_numbers) + +# 获取唯一材料名称 +material_names = dao.get_unique_material_names(source_numbers) +``` + +### 5.2 MaterialsTypeToBeDeleted(待删除物料类型表) + +**用途**: 存储按物料名称匹配的待删除物料(模糊匹配) + +**关键字段**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| MaterialName | varchar | 物料名称(用于包含匹配) | +| ManagerName | varchar | 负责人 | + +**匹配规则**: 如果 `DiscreteMaterialPlanData.MaterialName` 包含 `MaterialsTypeToBeDeleted.MaterialName`,则匹配成功 + +**查询示例**: +```python +dao = MaterialsTypeToBeDeletedDAO() + +# 获取所有物料类型 +materials = dao.get_all_materials() + +# 获取所有负责人 +managers = dao.get_managers() + +# 按负责人查询 +materials = dao.get_materials_by_manager('张三') +``` + +### 5.3 MaterialsToBeDeleted(已标记删除的物料记录表) + +**用途**: 存储已标记删除的具体物料记录(精确匹配) + +**关键字段**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| ID | int | 主键 | +| MaterialCode | varchar | 物料代码(用于精确匹配) | +| ManagerName | varchar | 负责人 | + +**匹配规则**: 如果 `DiscreteMaterialPlanData.MaterialCode` 等于 `MaterialsToBeDeleted.MaterialCode`,则匹配成功 + +**操作示例**: +```python +dao = MaterialsToBeDeletedDAO() + +# 获取所有记录 +records = dao.get_all_records() + +# 批量插入/更新 +stats = dao.upsert_batch([ + {'material_code': 'M001', 'manager_name': '张三'}, + {'material_code': 'M002', 'manager_name': '李四'} +]) + +# 按物料代码删除 +dao.delete_by_material_code('M001') + +# 批量删除 +dao.delete_by_material_codes(['M001', 'M002', 'M003']) +``` + +--- + +## 6. 匹配算法逻辑 + +### 6.1 双优先级匹配机制 + +物料校验采用双优先级匹配机制,确保精确匹配优先于模糊匹配: + +```mermaid +flowchart TD + Start[物料记录] --> CheckP1{优先级1:
MaterialsToBeDeleted
精确匹配?} + + CheckP1 -->|MaterialCode 精确匹配| Marked[已标记删除] + Marked --> SetM1[设置 ManagerName
= MaterialsToBeDeleted.ManagerName] + SetM1 --> SetFlag1[is_marked_for_deletion = true] + SetFlag1 --> End1[返回结果] + + CheckP1 -->|未匹配| CheckP2{优先级2:
MaterialsTypeToBeDeleted
模糊匹配?} + + CheckP2 -->|MaterialName 包含匹配| Keyword[匹配到关键词] + Keyword --> SetM2[设置 ManagerName
= MaterialsTypeToBeDeleted.ManagerName] + SetM2 --> SetKeyword[matched_type_keyword
= 匹配的 MaterialName] + SetKeyword --> SetFlag2[is_marked_for_deletion = false] + SetFlag2 --> End2[返回结果] + + CheckP2 -->|未匹配| Unmatched[未匹配] + Unmatched --> SetNull[manager_name = null] + SetNull --> SetFlag3[is_marked_for_deletion = false
matched_type_keyword = null] + SetFlag3 --> End3[返回结果] + + style Start fill:#e1f5ff + style Marked fill:#ffe1f5 + style Keyword fill:#fff4e1 + style Unmatched fill:#f5f5f5 +``` + +### 6.2 匹配代码实现 + +```python +def match_materials_detailed( + self, + material_records: List[Dict[str, Any]], + type_keywords: List[Dict[str, Any]], + marked_codes_dict: Dict[str, str] +) -> List[MaterialValidationResult]: + """ + 双优先级匹配算法 + + Args: + material_records: DiscreteMaterialPlanData 的完整记录 + type_keywords: MaterialsTypeToBeDeleted 记录(模糊匹配) + marked_codes_dict: MaterialsToBeDeleted 的 MaterialCode->ManagerName 映射(精确匹配) + + Returns: + List[MaterialValidationResult]: 匹配结果列表 + """ + results = [] + + for record in material_records: + material_name = record.get('MaterialName', '') or '' + material_code = record.get('MaterialCode', '') or '' + specification = record.get('Specification', '') or None + model = record.get('Model', '') or None + + # ===== 优先级 1: 精确匹配 ===== + # 检查 MaterialsToBeDeleted 表(MaterialCode 精确匹配) + # 这是最高优先级 - 如果 MaterialCode 存在,使用其 ManagerName + manager_name = marked_codes_dict.get(material_code) if material_code else None + is_marked = manager_name is not None + matched_keyword = None + + # ===== 优先级 2: 模糊匹配 ===== + # 如果不在 MaterialsToBeDeleted 中,匹配 MaterialsTypeToBeDeleted + # (MaterialName 包含匹配) + if not manager_name: + for type_record in type_keywords: + type_material_name = type_record.get('MaterialName', '') + if type_material_name and type_material_name in material_name: + matched_keyword = type_material_name + manager_name = type_record.get('ManagerName') + break + + result = MaterialValidationResult( + material_name=material_name, + material_code=material_code, + specification=specification, + model=model, + manager_name=manager_name, + is_marked_for_deletion=is_marked, + matched_type_keyword=matched_keyword + ) + results.append(result) + + return results +``` + +### 6.3 匹配示例 + +| MaterialCode | MaterialName | MaterialsToBeDeleted | MaterialsTypeToBeDeleted | 匹配结果 | is_marked | ManagerName | +|--------------|--------------|----------------------|--------------------------|----------|-----------|-------------| +| M001 | 螺栓 M8×20 | M001 → 张三 | - | 优先级1精确匹配 | true | 张三 | +| M002 | 垫圈 Φ8 | - | 垫圈 → 李四 | 优先级2模糊匹配 | false | 李四 | +| M003 | 螺母 M6 | - | - | 未匹配 | false | null | +| M004 | 不锈钢螺栓 M10×30 | M004 → 王五 | 螺栓 → 赵六 | 优先级1精确匹配(忽略螺栓) | true | 王五 | + +--- + +## 7. 权限控制 + +### 7.1 用户类型 + +系统通过 `SessionManager` 实现基于角色的访问控制(RBAC): + +```mermaid +classDiagram + class SessionManager { + <> + -_current_user: Dict + +login(username, password) + +is_admin() bool + +get_username() str + +get_user_type() str + } + + class User { + <> + +username: str + +user_type: str + } + + class Admin { + +user_type: 'Admin' + +can_select_data_source: true + +can_view_all_managers: true + +can_use_manager_filter: true + } + + class NormalUser { + +user_type: 'User' + +can_select_data_source: false + +can_view_all_managers: false + +can_use_manager_filter: false + } + + SessionManager --> User : manages + User <|-- Admin + User <|-- NormalUser +``` + +### 7.2 Admin vs User 权限差异 + +| 功能 | Admin | User | +|------|-------|------| +| **数据来源选择** | ✅ 可选择全表/过滤 | ❌ 仅限过滤模式 | +| **ProductionID 数据源** | ✅ 可选择文件或共享 | ❌ 仅限共享 ID | +| **查看数据范围** | ✅ 所有负责人的数据 | ❌ 仅自己的数据 | +| **负责人筛选** | ✅ 可使用复选框筛选 | ❌ 自动筛选到当前用户 | +| **编辑负责人** | ✅ 可编辑任何人 | ✅ 可编辑任何人 | +| **删除确认** | ✅ 可操作所有人 | ✅ 可操作所有人 | + +### 7.3 权限检查实现 + +```python +# 检查是否为管理员 +if self.session_manager.is_admin(): + # 显示管理员专属控件 + self.source_mode = tk.StringVar(value="database_full") + # 显示负责人筛选区域 + manager_filter_frame = ttk.LabelFrame(...) +else: + # 普通用户默认设置 + self.source_mode = tk.StringVar(value="database_filtered") + # 隐藏管理员专属控件 + manager_filter_frame = ttk.Frame(...) +``` + +### 7.4 数据过滤逻辑 + +```python +def _get_selected_managers(self) -> List[str]: + """获取选中的负责人列表""" + # PERMISSION CHECK: 非管理员用户直接返回当前用户名 + if not self.session_manager.is_admin(): + return [self.session_manager.get_username()] + + # 管理员:从复选框获取选中的负责人 + return [ + manager for manager, var in self.manager_checkboxes.items() + if var.get() + ] +``` + +--- + +## 8. 交互功能 + +### 8.1 复选框同步机制 + +**功能**: 当用户点击某个记录的 checkbox 时,自动将所有具有相同材料代码的记录的 checkbox 状态同步更新。 + +**实现逻辑**: + +```mermaid +flowchart TD + UserClick[用户点击 checkbox] --> GetItem[获取点击的 item] + GetItem --> GetCode[获取该行的 MaterialCode] + GetCode --> Iterate[遍历表格所有行] + + Iterate --> CheckCode{MaterialCode
相同?} + CheckCode -->|是| CheckState{状态
不同?} + CheckState -->|是| Update[更新 checkbox 状态] + CheckState -->|否| Next[继续下一行] + CheckCode -->|否| Next + + Update --> Next + Next --> MoreRows{还有行?} + MoreRows -->|是| Iterate + MoreRows -->|否| Log[记录同步数量] + Log --> End[完成] + + style UserClick fill:#e1f5ff + style Update fill:#ffe1f5 + style End fill:#e1ffe1 +``` + +**代码实现**: + +```python +def _sync_checkbox_by_material_code(self, changed_item: str, new_state: bool): + """同步相同材料代码的所有记录的选择状态""" + # 获取被点击行的材料代码 + 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}") +``` + +### 8.2 负责人筛选 + +**功能**: 管理员可以通过复选框筛选显示特定负责人的物料记录。 + +**界面布局**: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 筛选(按负责人) │ +├─────────────────────────────────────────────────────────────┤ +│ ☑ 全选 ☑ 张三 ☑ 李四 ☑ 王五 ☑ 赵六 ☑ 钱七 │ +│ ☑ 孙八 ☑ 周九 ☑ 吴十 ☑ 郑十一 ☑ 陈十二 ☑ 沈十三 │ +│ │ +│ ┌─────────┐ ┌──────────┐ │ +│ │ 全选 │ │ 取消全选 │ │ +│ └─────────┘ └──────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**筛选规则**: +- 显示选中负责人的记录 +- 同时显示负责人为空的记录(待编辑) +- 未选中任何负责人时,表格清空 + +**代码实现**: + +```python +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() == "") + ] + + self._refresh_filtered_results(filtered_records) + self._update_log(f"筛选结果:共 {len(filtered_records)} 条记录", "INFO") +``` + +### 8.3 表格编辑 + +**功能**: 支持双击"负责人"单元格进行编辑。 + +**编辑流程**: + +```mermaid +sequenceDiagram + actor User + participant Tree as CheckboxTreeview + participant Dialog as SimpleDialog + participant Log as LogText + + User->>Tree: 双击"负责人"单元格 + Tree->>Tree: 识别点击位置和列 + Tree->>Tree: 获取当前值 + Tree->>Dialog: 弹出编辑对话框 + Dialog-->>User: 显示输入框 + User->>Dialog: 输入新值 + Dialog-->>Tree: 返回新值 + Tree->>Tree: 更新单元格显示 + Tree->>Log: 记录更新日志 + Log-->>User: 显示更新成功消息 +``` + +**代码实现**: + +```python +def _on_cell_double_click(self, event): + """处理单元格双击事件,编辑负责人""" + # 获取点击位置 + region = self.tree.identify_region(event.x, event.y) + + if region == "cell": + column = self.tree.identify_column(event.x) + item = self.tree.identify_row(event.y) + + # 检查是否点击了"负责人"列(第6列) + if column == "#6" and item: + values = self.tree.item(item, "values") + current_value = values[5] if len(values) > 5 else "" + + # 弹出编辑对话框 + new_value = simpledialog.askstring( + "编辑负责人", + f"请输入负责人姓名:", + initialvalue=current_value + ) + + if new_value is not None: # 用户没有取消 + # 更新单元格值 + new_values = list(values) + new_values[5] = new_value + self.tree.item(item, values=new_values) + + self.log_text.info(f"已更新负责人: {current_value} -> {new_value}") +``` + +--- + +## 9. 使用场景 + +### 9.1 场景一:管理员全表校验 + +**需求**: 管理员需要查看所有物料记录并进行校验 + +**操作步骤**: +1. 选择"数据库 - 全表校验"模式 +2. 点击"开始校验" +3. 系统查询整个 `DiscreteMaterialPlanData` 表 +4. 使用负责人筛选器筛选特定负责人 +5. 双击编辑负责人 +6. 勾选需要删除的记录 +7. 点击"确认删除"保存到数据库 + +### 9.2 场景二:普通用户按 ProductionID 校验 + +**需求**: 普通用户需要校验特定生产订单的物料 + +**操作步骤**: +1. 在"数据提取"页面输入 ProductionID +2. 切换到"物料校验"标签页 +3. 系统自动使用共享的 ProductionID +4. 点击"开始校验" +5. 仅显示当前用户负责的物料 +6. 编辑负责人或勾选删除 +7. 点击"确认删除"保存 + +### 9.3 场景三:批量删除确认 + +**需求**: 用户需要批量确认删除多个物料记录 + +**操作流程**: + +```mermaid +flowchart TD + Start([开始]) --> Select[选择物料记录] + Select --> CheckAll[点击全选按钮] + CheckAll --> AutoSync[系统自动同步相同 MaterialCode 的记录] + AutoSync --> Confirm[点击确认删除按钮] + Confirm --> Validate{验证负责人} + + Validate -->|有缺失| ShowWarning[显示警告并列出缺少负责人的记录] + ShowWarning --> Edit[用户编辑缺失的负责人] + Edit --> Confirm + + Validate -->|全部完整| ShowConfirm[显示确认对话框] + ShowConfirm --> UserConfirm{用户确认?} + + UserConfirm -->|否| Cancel[取消操作] + UserConfirm -->|是| Execute[执行后台同步] + + Execute --> Upsert[执行 upsert_batch 写入/更新勾选记录] + Execute --> Delete[执行 delete_by_material_codes 删除未勾选记录] + + Upsert --> Complete[显示完成消息] + Delete --> Complete + Complete --> Refresh[刷新筛选器和结果] + Refresh --> End([结束]) + + Cancel --> End + + style Start fill:#e1f5ff + style End fill:#e1f5ff + style ShowWarning fill:#fff4e1 + style Execute fill:#ffe1f5 + style Complete fill:#e1ffe1 +``` + +--- + +## 10. 技术要点 + +### 10.1 线程安全 + +校验操作在后台线程中执行,避免阻塞 UI: + +```python +# 在后台线程中执行校验 +validation_thread = threading.Thread( + target=self._validation_worker_enhanced, + args=(mode, input_file, production_id_file, output_file, production_ids_list), + daemon=True, +) +validation_thread.start() +``` + +日志更新通过 `after()` 方法确保线程安全: + +```python +def _update_log(self, message: str, level: str = "INFO"): + """线程安全的日志更新""" + def update(): + if level == "INFO": + self.log_text.info(message) + elif level == "ERROR": + self.log_text.error(message) + # ... + + self.after(0, update) # 在主线程中执行 +``` + +### 10.2 数据库兼容性 + +系统通过 `_convert_sql()` 和 `_get_placeholder()` 方法实现 SQL Server 和 MySQL 的兼容: + +```python +# SQL Server +SELECT [MaterialName], [ManagerName] +FROM [dbo].[MaterialsTypeToBeDeleted] +WHERE [MaterialName] = ? + +# MySQL +SELECT MaterialName, ManagerName +FROM `MaterialsTypeToBeDeleted` +WHERE MaterialName = ? +``` + +### 10.3 批量操作优化 + +为避免 SQL Server 参数限制(2100 个),批量操作采用分批处理: + +```python +def _batch_insert(self, db, df: pd.DataFrame, batch_size: int = 72) -> int: + """ + Batch insert records (max 72 per batch due to SQL Server 2100 param limit). + With 29 fields, the maximum batch size is floor(2100 / 29) = 72 records. + """ + total_inserted = 0 + records = self._convert_df_to_records(df) + + for i in range(0, len(records), batch_size): + batch = records[i:i + batch_size] + for record in batch: + db.execute_update(sql, record) + total_inserted += 1 + + return total_inserted +``` + +--- + +## 11. 常见问题 + +### Q1: 为什么同一个 MaterialCode 会有多条记录? + +**A**: 因为同一个物料代码可能出现在不同的备料计划单号或生产订单号中。复选框同步机制确保了相同 MaterialCode 的记录会被一起选中/取消。 + +### Q2: 已标记删除(is_marked=true)和匹配到关键词有什么区别? + +**A**: +- **已标记删除**: 来自 `MaterialsToBeDeleted` 表,通过 `MaterialCode` 精确匹配,优先级最高 +- **匹配到关键词**: 来自 `MaterialsTypeToBeDeleted` 表,通过 `MaterialName` 包含匹配,优先级次之 + +### Q3: User 用户为什么看不到"数据来源"选项? + +**A**: 这是权限控制的设计。User 用户只能使用 ProductionID 过滤模式,确保他们只能访问相关的数据,而不是整个数据库。 + +### Q4: 编辑负责人后,为什么需要重新点击"确认删除"? + +**A**: 编辑负责人只是修改了界面显示,并未保存到数据库。只有点击"确认删除"后,修改才会被写入数据库。 + +### Q5: 如何理解"双优先级匹配"? + +**A**: +1. **优先级1(精确匹配)**: 如果 `MaterialCode` 在 `MaterialsToBeDeleted` 表中存在,使用该表的 `ManagerName`,并标记为 `is_marked=true` +2. **优先级2(模糊匹配)**: 如果优先级1未匹配,检查 `MaterialName` 是否包含 `MaterialsTypeToBeDeleted` 表中的任何 `MaterialName`,如果包含,使用该表的 `ManagerName` + +--- + +## 12. 扩展建议 + +### 12.1 性能优化 +- 对于大数据量的全表查询,考虑添加分页功能 +- 实现查询结果缓存,减少重复查询 + +### 12.2 功能增强 +- 添加批量导入功能,支持从 Excel 导入负责人信息 +- 实现导出模板功能,方便离线编辑 +- 添加校验历史记录,追溯修改历史 + +### 12.3 用户体验 +- 实现拖拽排序功能 +- 添加列筛选和排序功能 +- 支持自定义列显示/隐藏 + +--- + +**文档版本**: 1.0 +**最后更新**: 2026-02-24 +**维护者**: Development Team