- Add skill that uses check-mermaid.js to validate Mermaid diagrams - Skill guides Claude to parse error reports and apply intelligent fixes - Fixes common Mermaid parser bugs (parentheses, brackets, braces in labels) - Include demo scripts with test Markdown file for validation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
15 KiB
15 KiB
Admin 用户物料清理数据流说明文档
概述
本文档详细说明 ERPAuto 系统中,Admin 用户执行物料清理操作时,被清理物料的完整获取流程、数据来源和处理链路。
核心结论
Admin 用户清理的物料来源:被清理的物料代码从数据库表 dbo.MaterialsToBeDeleted 中获取,根据 Admin 用户在 UI 界面选择的负责人(Manager)进行过滤。
数据流总览
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<br/>WHERE ManagerName IN (selectedManagers)
DB-->>Service: materialCodes[]
Service-->>IPC: {orderNumbers[], materialCodes[]}
else Regular User
Service->>DB: SELECT MaterialCode FROM dbo.MaterialsToBeDeleted<br/>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 删除)
架构分层
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 查询:
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)
graph LR
subgraph "步骤 1: 用户触发的数据来源"
A1[Admin 点击'数据校验']
end
subgraph "步骤 2: 数据获取模式"
A2{校验模式?}
A21[Full Mode<br/>全量数据]
A22[Filtered Mode<br/>筛选数据]
end
subgraph "步骤 3: 数据库查询"
A3[DiscreteMaterialPlanDAO]
A31["queryAllDistinctByMaterialCode()"]
A32["queryBySourceNumbersDistinct(orderNumbers)"]
end
subgraph "步骤 4: 数据增强"
A4[加载负责人信息]
A41[加载 MaterialsTypeToBeDeleted<br/>关键词匹配]
A42[加载 MaterialsToBeDeleted<br/>已标记记录]
end
subgraph "结果"
A5["ValidationResult[]<br/>包含 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 表:
sequenceDiagram
participant UI as 用户界面
participant DAO as MaterialsToBeDeletedDAO
participant DB as Database
UI->>DAO: upsertBatch(materials[])
loop 对每个物料
DAO->>DB: MERGE INTO dbo.MaterialsToBeDeleted<br/>ON MaterialCode<br/>WHEN MATCHED UPDATE<br/>WHEN NOT MATCHED INSERT
end
DB-->>DAO: 成功/失败统计
DAO-->>UI: { total, success, failed }
SQL 逻辑(MERGE UPSERT):
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 用户执行实际删除操作的核心流程:
graph TD
subgraph "Renderer 层"
S1["handleExecuteDeletion()"]
S1-->S2["runCleanerExecution {selectedManagers}"]
end
subgraph "获取清理数据 (主进程)"
S2-->S3[ValidationApplicationService.getCleanerData]
S3-->S4{用户类型?}
S4-->|Admin + selectedManagers| S5[loadMaterialCodesForCleaner<br/>Admin With Managers]
S5-->S6[SELECT FROM MaterialsToBeDeleted<br/>WHERE ManagerName IN selectedManagers]
S4-->|Admin no managers| S7[Admin Without Managers]
S7-->S8[SELECT FROM DiscreteMaterialPlanData<br/>WHERE SourceNo IN orderNumbers]
S4-->|普通用户 | S9[SELECT FROM MaterialsToBeDeleted<br/>WHERE ManagerName = username]
end
subgraph "执行清理"
S6-->S10["返回 materialCodes[]"]
S8-->S10
S9-->S10
S10-->S11[CleanerApplicationService.runCleaner]
S11-->S12[CleanerService.clean<br/>连接 ERP 执行删除]
end
S12-->S13[清理完成报告]
style S5 fill:#ff9,stroke:#333
style S6 fill:#f96,stroke:#333
style S12 fill:#f99,stroke:#333
关键代码路径:
// 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 删除执行
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 用户获取物料的完整路径:
graph TB
Start[Admin 点击执行清理] --> Check{有 selectedManagers?}
Check -->|Yes| Path1[路径 1: MaterialsToBeDeleted 表]
Path1 --> Q1["SELECT MaterialCode FROM MaterialsToBeDeleted<br/>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<br/>WHERE SourceNo IN (orderNumbers)"]
Q2 --> Merge
NeedProd -->|No| Empty[返回空数组<br/>无法执行清理]
Merge --> Execute[传递给 CleanerService<br/>执行 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 |
物料计划数据查询 |
数据流状态图
stateDiagram-v2
[*] --> 物料录入:用户在 UI 输入物料
物料录入 --> 待校验:保存到 MaterialsToBeDeleted 表
待校验 --> 校验中:点击"数据校验"
校验中 --> 已标记:通过关键词匹配负责人
已标记 --> 待清理:用户勾选物料
待清理 --> 清理执行中:点击"执行清理"
清理执行中 --> 清理完成:ERP 删除成功
清理执行中 --> 部分失败:部分物料删除失败
清理完成 --> [*]
部分失败 --> [*]
note right of 待清理
Admin 可以查看和选择
所有负责人的物料
end note
note right of 清理执行中
从 MaterialsToBeDeleted 表
根据 selectedManagers 过滤
获取要删除的物料代码
end note
数据流对比:Admin vs 普通用户
graph TB
subgraph "Admin 用户"
A1[勾选多个 Manager]
A2[SELECT FROM MaterialsToBeDeleted<br/>WHERE ManagerName IN selectedManagers]
A3[获取所有选中负责人的物料]
end
subgraph "普通用户"
B1[只能看到自己的物料]
B2[SELECT FROM MaterialsToBeDeleted<br/>WHERE ManagerName = username]
B3[只能删除自己的物料]
end
A1 --> A2 --> A3 --> Exec[执行清理]
B1 --> B2 --> B3 --> Exec
常见问题解答
Q1: 物料代码是如何被录入到 MaterialsToBeDeleted 表的?
A: 有三种方式:
- 用户手动勾选 → 在 CleanerPage 勾选物料 → 点击"确认删除" →
upsertBatch - 关键词自动匹配 → 校验时根据
MaterialsTypeToBeDeleted配置自动分配负责人和标记 - API 直接写入 → 其他服务调用
materials.upsertBatchIPC
Q2: Admin 如果不选 Manager 会怎样?
A: Admin 可以不选 Manager,此时:
- 系统会尝试从"共享 Production ID"解析订单号
- 然后从
DiscreteMaterialPlanData表查询所有物料代码(不经过MaterialsToBeDeleted过滤) - 如果没有共享 Production ID,则返回空数组,无法执行清理
Q3: 物料代码会在清理后被自动删除吗?
A:
- ✅ 干运行模式:不删除 ERP 数据,但会保留 UI 状态
- ✅ 正式执行:
- ERP 中的物料计划被删除
MaterialsToBeDeleted表中记录不会被自动删除(需要手动清理)
Q4: 如何清理已删除的物料记录?
A: Admin 可以在物料管理界面:
- 按 Manager 筛选
- 批量删除已处理的物料记录
- 调用
materials.deleteIPC 接口
总结
Admin 用户执行清理时,被清理物料的来源路径:
- 主要来源:
dbo.MaterialsToBeDeleted表 - 过滤条件:
ManagerName IN (selectedManagers) - 执行流程:
- 从数据库查询物料代码
- 结合订单号列表
- 通过 Playwright 连接 ERP 系统
- 逐个物料执行删除操作
关键点:被清理的物料必须先在 MaterialsToBeDeleted 表中存在记录,并且其 ManagerName 与 Admin 选择的负责人匹配。