# Admin 用户物料清理数据流说明文档 ## 概述 本文档详细说明 ERPAuto 系统中,Admin 用户执行物料清理操作时,被清理物料的完整获取流程、数据来源和处理链路。 ## 核心结论 **Admin 用户清理的物料来源**:被清理的物料代码从数据库表 `dbo.MaterialsToBeDeleted` 中获取,根据 Admin 用户在 UI 界面选择的负责人(Manager)进行过滤。 --- ## 数据流总览 ```mermaid sequenceDiagram participant UI as CleanerPage (UI) participant Hook as useCleaner Hook participant API as Renderer API participant IPC as IPC Channel participant Service as ValidationService participant DB as Database UI->>Hook: handleExecuteDeletion() Hook->>API: runCleanerExecution() API->>IPC: validation.getCleanerData({selectedManagers}) IPC->>Service: getCleanerData(userInfo, selectedManagers) alt Admin User Service->>Service: loadMaterialCodesForCleaner() Service->>DB: SELECT MaterialCode FROM dbo.MaterialsToBeDeleted
WHERE ManagerName IN (selectedManagers) DB-->>Service: materialCodes[] Service-->>IPC: {orderNumbers[], materialCodes[]} else Regular User Service->>DB: SELECT MaterialCode FROM dbo.MaterialsToBeDeleted
WHERE ManagerName = username DB-->>Service: materialCodes[] Service-->>IPC: {orderNumbers[], materialCodes[]} end IPC-->>API: cleanerData API->>IPC: cleaner.runCleaner({orderNumbers, materialCodes}) IPC->>Service: CleanerApplicationService.runCleaner() Service->>UI: 执行清理 (ERP 删除) ``` --- ## 架构分层 ```mermaid graph TB subgraph "渲染进程 (Renderer)" UI[CleanerPage.tsx] HOOK[useCleaner.ts] RAPI[renderer/api.ts] end subgraph "主进程 (Main)" IPC_VAL[IPC: validation.getCleanerData] IPC_CLEAN[IPC: cleaner.runCleaner] VAS[ValidationApplicationService] CAS[CleanerApplicationService] ERP["CleanerService (ERP)"] end subgraph "数据库 (Database)" MTBD[(dbo.MaterialsToBeDeleted)] DMPD[(dbo.DiscreteMaterialPlanData)] MTTD[(dbo.MaterialsTypeToBeDeleted)] end UI --> HOOK HOOK --> RAPI RAPI --> IPC_VAL RAPI --> IPC_CLEAN IPC_VAL --> VAS IPC_CLEAN --> CAS VAS --> MTBD VAS --> DMPD VAS --> MTTD CAS --> ERP style MTBD fill:#f9f,stroke:#333 style MTTD fill:#f9f,stroke:#333 ``` --- ## 关键数据表 ### 1. `dbo.MaterialsToBeDeleted` (核心来源表) **作用**:存储所有被标记为待删除的物料代码及其负责人。 **表结构**: | 字段 | 类型 | 说明 | |------|------|------| | `ID` | int | 主键 | | `MaterialCode` | varchar | **物料代码 (被清理的目标)** | | `ManagerName` | varchar | 负责人姓名 | **Admin 获取物料的 SQL 查询**: ```sql SELECT MaterialCode FROM dbo.MaterialsToBeDeleted WHERE ManagerName IN (@manager0, @manager1, ...) AND MaterialCode IS NOT NULL ``` ### 2. `dbo.MaterialsTypeToBeDeleted` (物料类型配置表) **作用**:定义物料名称关键词与负责人的映射关系,用于自动分配负责人。 **表结构**: | 字段 | 类型 | 说明 | |------|------|------| | `MaterialName` | varchar | 物料名称关键词 | | `ManagerName` | varchar | 对应的负责人 | **示例**: | MaterialName | ManagerName | |-------------|-------------| | "电阻" | "张三" | | "电容" | "李四" | ### 3. `dbo.DiscreteMaterialPlanData` (物料计划数据表) **作用**:存储从 ERP 提取的完整物料计划数据,用于校验和展示物料详情。 **关键字段**: - `MaterialCode` - 物料代码 - `MaterialName` - 物料名称 - `Specification` - 规格 - `Model` - 型号 - `SourceNo` - 订单号 --- ## Admin 用户完整数据流 ### 阶段 1: 校验 (Validation) ```mermaid graph LR subgraph "步骤 1: 用户触发的数据来源" A1[Admin 点击'数据校验'] end subgraph "步骤 2: 数据获取模式" A2{校验模式?} A21[Full Mode
全量数据] A22[Filtered Mode
筛选数据] end subgraph "步骤 3: 数据库查询" A3[DiscreteMaterialPlanDAO] A31["queryAllDistinctByMaterialCode()"] A32["queryBySourceNumbersDistinct(orderNumbers)"] end subgraph "步骤 4: 数据增强" A4[加载负责人信息] A41[加载 MaterialsTypeToBeDeleted
关键词匹配] A42[加载 MaterialsToBeDeleted
已标记记录] end subgraph "结果" A5["ValidationResult[]
包含 isMarkedForDeletion 标志"] end A1 --> A2 A2 -->|Full| A21 A2 -->|Filtered| A22 A21 --> A31 A22 --> A32 A31 --> A4 A32 --> A4 A4 --> A41 A4 --> A42 A4 --> A5 style A5 fill:#9f9,stroke:#333 ``` **Admin 特殊逻辑**: - Admin 可以看到**所有负责人**的物料 - UI 会显示 Manager 列下拉筛选器 - Admin 默认选中所有 Manager ### 阶段 2: 保存删除计划 (可选) 用户在 UI 上勾选物料 → 点击"确认删除" → 数据写入 `MaterialsToBeDeleted` 表: ```mermaid sequenceDiagram participant UI as 用户界面 participant DAO as MaterialsToBeDeletedDAO participant DB as Database UI->>DAO: upsertBatch(materials[]) loop 对每个物料 DAO->>DB: MERGE INTO dbo.MaterialsToBeDeleted
ON MaterialCode
WHEN MATCHED UPDATE
WHEN NOT MATCHED INSERT end DB-->>DAO: 成功/失败统计 DAO-->>UI: { total, success, failed } ``` **SQL 逻辑**(MERGE UPSERT): ```sql MERGE INTO dbo.MaterialsToBeDeleted AS target USING (VALUES (@MaterialCode, @ManagerName)) AS source (MaterialCode, ManagerName) ON target.MaterialCode = source.MaterialCode WHEN MATCHED THEN UPDATE SET ManagerName = source.ManagerName WHEN NOT MATCHED THEN INSERT (MaterialCode, ManagerName) VALUES (source.MaterialCode, source.ManagerName); ``` ### 阶段 3: 执行清理 (关键步骤) 这是 Admin 用户执行实际删除操作的核心流程: ```mermaid graph TD subgraph "Renderer 层" S1["handleExecuteDeletion()"] S1-->S2["runCleanerExecution {selectedManagers}"] end subgraph "获取清理数据 (主进程)" S2-->S3[ValidationApplicationService.getCleanerData] S3-->S4{用户类型?} S4-->|Admin + selectedManagers| S5[loadMaterialCodesForCleaner
Admin With Managers] S5-->S6[SELECT FROM MaterialsToBeDeleted
WHERE ManagerName IN selectedManagers] S4-->|Admin no managers| S7[Admin Without Managers] S7-->S8[SELECT FROM DiscreteMaterialPlanData
WHERE SourceNo IN orderNumbers] S4-->|普通用户 | S9[SELECT FROM MaterialsToBeDeleted
WHERE ManagerName = username] end subgraph "执行清理" S6-->S10["返回 materialCodes[]"] S8-->S10 S9-->S10 S10-->S11[CleanerApplicationService.runCleaner] S11-->S12[CleanerService.clean
连接 ERP 执行删除] end S12-->S13[清理完成报告] style S5 fill:#ff9,stroke:#333 style S6 fill:#f96,stroke:#333 style S12 fill:#f99,stroke:#333 ``` **关键代码路径**: ```typescript // src/main/services/validation/validation-application-service.ts // loadMaterialCodesForCleaner() 方法 // Admin with selected managers: if (isAdmin && selectedManagers && selectedManagers.length > 0) { const materialCodes = await this.queryMaterialCodesByManagers( dbService, markedTableName, // dbo.MaterialsToBeDeleted selectedManagers ) return materialCodes } // Admin without selected managers (fallback): if (isAdmin) { if (orderNumbers.length === 0) { return [] // 无数据可处理 } const materialDao = new DiscreteMaterialPlanDAO() const records = await materialDao.queryBySourceNumbersDistinct(orderNumbers) const materialCodes = [...new Set(records.map((r) => r.MaterialCode as string).filter(Boolean))] return materialCodes } ``` ### 阶段 4: ERP 删除执行 ```mermaid sequenceDiagram participant CAS as CleanerApplicationService participant Cleaner as CleanerService participant ERP as ERP System CAS->>Cleaner: clean({orderNumbers, materialCodes}) loop 每个订单号 Cleaner->>ERP: 登录 (一次) loop 每个物料代码 Cleaner->>ERP: 查询物料计划 alt 物料存在 Cleaner->>ERP: 执行删除 ERP-->>Cleaner: 删除结果 else 物料不存在 Cleaner-->>Cleaner: 跳过 end end end Cleaner-->>CAS: CleanerResult ``` --- ## Admin 用户的数据来源路径 整理出 Admin 用户获取物料的完整路径: ```mermaid graph TB Start[Admin 点击执行清理] --> Check{有 selectedManagers?} Check -->|Yes| Path1[路径 1: MaterialsToBeDeleted 表] Path1 --> Q1["SELECT MaterialCode FROM MaterialsToBeDeleted
WHERE ManagerName IN (selectedManagers)"] Q1 --> Merge[合并所有物料代码] Check -->|No| Path2[路径 2: DiscreteMaterialPlanData 表] Path2 --> NeedProd{有共享 Production ID?} NeedProd -->|Yes| GetOrder[从共享 ID 解析订单号] GetOrder --> Q2["SELECT DISTINCT MaterialCode FROM DiscreteMaterialPlanData
WHERE SourceNo IN (orderNumbers)"] Q2 --> Merge NeedProd -->|No| Empty[返回空数组
无法执行清理] Merge --> Execute[传递给 CleanerService
执行 ERP 删除] style Path1 fill:#9f9,stroke:#333,stroke-width:2px style Path2 fill:#ff9,stroke:#333,stroke-width:2px style Empty fill:#f99,stroke:#333 style Execute fill:#f96,stroke:#333,stroke-width:3px ``` --- ## 关键代码位置参考 ### 渲染层 (Renderer) | 文件 | 函数 | 说明 | | ---------------------------------------- | ----------------------- | -------------------------- | | `src/renderer/src/pages/CleanerPage.tsx` | `handleExecuteDeletion` | 清理入口 | | `src/renderer/src/hooks/useCleaner.ts` | `handleExecuteDeletion` | 调用 `runCleanerExecution` | | `src/renderer/src/hooks/cleaner/api.ts` | `runCleanerExecution` | 先获取数据再执行清理 | ### 主进程层 (Main) | 文件 | 类/函数 | 说明 | | ---------------------------------------------------------------- | ----------------------------------------------- | ---------------- | | `src/main/ipc/validation-handler.ts` | `VALIDATION_GET_CLEANER_DATA` | IPC 入口 | | `src/main/services/validation/validation-application-service.ts` | `getCleanerData`, `loadMaterialCodesForCleaner` | **核心逻辑** | | `src/main/services/cleaner/cleaner-application-service.ts` | `runCleaner` | 执行 ERP 清理 | | `src/main/services/erp/cleaner.ts` | `CleanerService.clean` | ERP 浏览器自动化 | ### 数据库层 (DAO) | 文件 | 类 | 说明 | | ----------------------------------------------------------- | ------------------------- | ---------------------------- | | `src/main/services/database/materials-to-be-deleted-dao.ts` | `MaterialsToBeDeletedDAO` | `MaterialsToBeDeleted`表操作 | | `src/main/services/database/discrete-material-plan-dao.ts` | `DiscreteMaterialPlanDAO` | 物料计划数据查询 | --- ## 数据流状态图 ```mermaid stateDiagram-v2 [*] --> 物料录入:用户在 UI 输入物料 物料录入 --> 待校验:保存到 MaterialsToBeDeleted 表 待校验 --> 校验中:点击"数据校验" 校验中 --> 已标记:通过关键词匹配负责人 已标记 --> 待清理:用户勾选物料 待清理 --> 清理执行中:点击"执行清理" 清理执行中 --> 清理完成:ERP 删除成功 清理执行中 --> 部分失败:部分物料删除失败 清理完成 --> [*] 部分失败 --> [*] note right of 待清理 Admin 可以查看和选择 所有负责人的物料 end note note right of 清理执行中 从 MaterialsToBeDeleted 表 根据 selectedManagers 过滤 获取要删除的物料代码 end note ``` --- ## 数据流对比:Admin vs 普通用户 ```mermaid graph TB subgraph "Admin 用户" A1[勾选多个 Manager] A2[SELECT FROM MaterialsToBeDeleted
WHERE ManagerName IN selectedManagers] A3[获取所有选中负责人的物料] end subgraph "普通用户" B1[只能看到自己的物料] B2[SELECT FROM MaterialsToBeDeleted
WHERE ManagerName = username] B3[只能删除自己的物料] end A1 --> A2 --> A3 --> Exec[执行清理] B1 --> B2 --> B3 --> Exec ``` --- ## 常见问题解答 ### Q1: **物料代码是如何被录入到 `MaterialsToBeDeleted` 表的?** **A**: 有三种方式: 1. **用户手动勾选** → 在 CleanerPage 勾选物料 → 点击"确认删除" → `upsertBatch` 2. **关键词自动匹配** → 校验时根据 `MaterialsTypeToBeDeleted` 配置自动分配负责人和标记 3. **API 直接写入** → 其他服务调用 `materials.upsertBatch` IPC ### Q2: **Admin 如果不选 Manager 会怎样?** **A**: Admin 可以不选 Manager,此时: - 系统会尝试从"共享 Production ID"解析订单号 - 然后从 `DiscreteMaterialPlanData` 表查询所有物料代码(不经过 `MaterialsToBeDeleted` 过滤) - 如果没有共享 Production ID,则返回空数组,无法执行清理 ### Q3: **物料代码会在清理后被自动删除吗?** **A**: - ✅ **干运行模式**:不删除 ERP 数据,但会保留 UI 状态 - ✅ **正式执行**: - ERP 中的物料计划被删除 - `MaterialsToBeDeleted` 表中记录**不会被自动删除**(需要手动清理) ### Q4: **如何清理已删除的物料记录?** **A**: Admin 可以在物料管理界面: - 按 Manager 筛选 - 批量删除已处理的物料记录 - 调用 `materials.delete` IPC 接口 --- ## 总结 Admin 用户执行清理时,被清理物料的来源路径: 1. **主要来源**: `dbo.MaterialsToBeDeleted` 表 2. **过滤条件**: `ManagerName IN (selectedManagers)` 3. **执行流程**: - 从数据库查询物料代码 - 结合订单号列表 - 通过 Playwright 连接 ERP 系统 - 逐个物料执行删除操作 **关键点**:被清理的物料**必须**先在 `MaterialsToBeDeleted` 表中存在记录,并且其 `ManagerName` 与 Admin 选择的负责人匹配。