- 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>
483 lines
15 KiB
Markdown
483 lines
15 KiB
Markdown
# 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<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 删除)
|
||
```
|
||
|
||
---
|
||
|
||
## 架构分层
|
||
|
||
```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<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` 表:
|
||
|
||
```mermaid
|
||
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):
|
||
|
||
```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<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
|
||
```
|
||
|
||
**关键代码路径**:
|
||
|
||
```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<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` | 物料计划数据查询 |
|
||
|
||
---
|
||
|
||
## 数据流状态图
|
||
|
||
```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<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**: 有三种方式:
|
||
|
||
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 选择的负责人匹配。
|