- Create user/ - User guides and configuration documentation - Create features/ - Feature specifications and business flows - Create debugging/ - Debug guides and quick references - Create testing/ - Test infrastructure, reports, and plans - Create internal/ - Internal plans, analyses, and templates - Move cleaner/*.md to cleaner/ directory - Move LOGGING_*.md to developer/guides/ Add docs/README.md as documentation index with category navigation and quick lookup guide. The reorganized structure makes it easier for users and developers to quickly locate relevant documentation.
310 lines
14 KiB
Markdown
310 lines
14 KiB
Markdown
# 清理器角色差异流程 — Admin vs User
|
||
|
||
**文档版本**: 1.1
|
||
**创建日期**: 2026-04-06
|
||
**面向对象**: 开发人员
|
||
|
||
## 概述
|
||
|
||
清理器(Cleaner)在决定"哪些物料需要被清除"时,Admin 和 User 两个角色存在系统性的差异。这些差异贯穿三个阶段:**初始化 → 校验确认 → 执行清理**。
|
||
|
||
本文档使用 Mermaid 图表说明每个阶段的角色分支逻辑。
|
||
|
||
---
|
||
|
||
## 全局流程概览
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph init["阶段一:页面初始化"]
|
||
I1([页面加载]) --> I2{角色判断}
|
||
I2 -->|Admin| I3["管理员列表 ← 全部负责人<br/>默认选中全部"]
|
||
I2 -->|User| I4["管理员列表 ← 空<br/>默认选中仅自己"]
|
||
end
|
||
|
||
subgraph validate["阶段二:校验 → 勾选 → 同步数据库"]
|
||
V1([点击校验]) --> V2["后端查询物料<br/>(不区分角色)"]
|
||
V2 --> V3["物料匹配算法<br/>(User 有覆盖匹配)"]
|
||
V3 --> V4{角色判断}
|
||
V4 -->|Admin| V5["显示全部物料<br/>侧边栏可按负责人筛选"]
|
||
V4 -->|User| V6["仅显示自己的物料<br/>+ 无负责人的物料"]
|
||
V5 --> V7["用户勾选/取消勾选"]
|
||
V6 --> V7
|
||
V7 --> V8{点击同步数据库}
|
||
V8 --> V9{角色判断}
|
||
V9 -->|Admin| V10["处理范围:全部校验结果"]
|
||
V9 -->|User| V11["处理范围:仅筛选后结果"]
|
||
end
|
||
|
||
subgraph execute["阶段三:执行清理(ERP 删除)"]
|
||
E1([点击执行清理]) --> E2["getCleanerData(selectedManagers)<br/>获取物料代码"]
|
||
E2 --> E3{角色判断}
|
||
E3 -->|Admin| E3a{selectedManagers<br/>非空?}
|
||
E3a -->|"是"| E4["SQL WHERE ManagerName IN (选中)<br/>从 MaterialsToBeDeleted 获取"]
|
||
E3a -->|"否"| E4b["从 DiscreteMaterialPlanData<br/>按 orderNumbers 获取"]
|
||
E3 -->|User| E5["SQL WHERE ManagerName = 用户<br/>仅获取自己的物料代码"]
|
||
E4 --> E6["传递给 runCleaner 执行"]
|
||
E4b --> E6
|
||
E5 --> E6
|
||
E6 --> E7([在 ERP 中删除物料])
|
||
end
|
||
|
||
init --> validate --> execute
|
||
```
|
||
|
||
---
|
||
|
||
## 阶段一:页面初始化
|
||
|
||
**源码位置**: `src/renderer/src/hooks/cleaner/api.ts:25-52` 与 `src/renderer/src/hooks/useCleaner.ts:98-112`
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
Start([页面加载]) --> GetAdmin["调用 auth:isAdmin<br/>判断是否管理员"]
|
||
GetAdmin --> GetUser["调用 auth:getCurrentUser<br/>获取当前用户名"]
|
||
GetUser --> RoleCheck{isAdmin?}
|
||
|
||
RoleCheck -->|Admin| GetManagers["调用 materials:getManagers<br/>获取全部负责人列表"]
|
||
GetManagers --> SelectAll["selectedManagers ← 全部负责人<br/>(默认全选)"]
|
||
SelectAll --> RenderSidebar["渲染 CleanerSidebar<br/>显示负责人复选框"]
|
||
|
||
RoleCheck -->|User| SetSelf["selectedManagers ← {currentUsername}<br/>(仅选中自己)"]
|
||
SetSelf --> NoSidebar["不渲染 CleanerSidebar<br/>无侧边栏"]
|
||
|
||
RenderSidebar --> Ready([就绪])
|
||
NoSidebar --> Ready
|
||
```
|
||
|
||
**差异总结**:
|
||
|
||
| 维度 | Admin | User |
|
||
| ---------- | ----------------- | ------ |
|
||
| 侧边栏 | 有 CleanerSidebar | 无 |
|
||
| 管理员列表 | 查询全部负责人 | 不查询 |
|
||
| 默认选中 | 所有负责人 | 仅自己 |
|
||
|
||
---
|
||
|
||
## 阶段二:校验 → 勾选 → 同步数据库
|
||
|
||
### 2.1 物料校验(后端,不区分角色)
|
||
|
||
**源码位置**: `src/main/services/validation/validation-application-service.ts`
|
||
|
||
校验阶段后端查询不区分角色,Admin 和 User 拿到相同的物料数据。区别在于**匹配算法**:
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
Start([遍历每条物料记录]) --> P1{"优先级1<br/>MaterialsToBeDeleted<br/>精确匹配 MaterialCode?"}
|
||
|
||
P1 -->|"匹配"| SetManager["managerName ← 表中记录<br/>isMarkedForDeletion = true"]
|
||
P1 -->|"未匹配"| P2{"优先级2<br/>MaterialsTypeToBeDeleted<br/>MaterialName 包含匹配?"}
|
||
|
||
P2 -->|"匹配"| SetType["managerName ← 类型关键词负责人<br/>matchedTypeKeyword ← 匹配项"]
|
||
P2 -->|"未匹配"| SetNull["managerName = null"]
|
||
|
||
SetManager --> RoleCheck{角色?}
|
||
SetType --> RoleCheck
|
||
SetNull --> RoleCheck
|
||
|
||
RoleCheck -->|"Admin"| Skip["跳过覆盖<br/>使用当前结果"]
|
||
RoleCheck -->|"User"| P3{"优先级3(User 覆盖)<br/>自己的类型关键词匹配?"}
|
||
|
||
P3 -->|"匹配"| Override["强制覆盖<br/>managerName ← 当前用户"]
|
||
P3 -->|"未匹配"| Keep["保持当前结果"]
|
||
Skip --> Next(["下一条物料"])
|
||
Override --> Next
|
||
Keep --> Next
|
||
```
|
||
|
||
**匹配优先级说明**:
|
||
|
||
| 优先级 | 数据源 | 匹配方式 | 适用角色 |
|
||
| -------------- | -------------------------- | --------------------- | -------- |
|
||
| 1(最高) | `MaterialsToBeDeleted` | MaterialCode 精确匹配 | 全部 |
|
||
| 2 | `MaterialsTypeToBeDeleted` | MaterialName 包含匹配 | 全部 |
|
||
| 3(User 覆盖) | 当前用户的类型关键词 | MaterialName 包含匹配 | 仅 User |
|
||
|
||
> **优先级 3 的作用**:当某个物料按优先级 2 被分配给其他负责人,但当前 User 有匹配的类型关键词时,会强制覆盖为自己的。这确保 User 不会为他人操作物料。
|
||
|
||
### 2.2 前端显示过滤
|
||
|
||
**源码位置**: `src/renderer/src/hooks/cleaner/helpers.ts:34-57`
|
||
|
||
校验结果返回前端后,会根据角色进行显示过滤:
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
Input([校验结果 validationResults]) --> RoleCheck{角色判断}
|
||
|
||
RoleCheck -->|Admin| FilterManagers["按侧边栏选中的负责人过滤<br/>selectedManagers.has(managerName)<br/>|| !managerName"]
|
||
RoleCheck -->|User| FilterSelf["仅显示自己的 + 无负责人的<br/>managerName === currentUsername<br/>|| !managerName"]
|
||
|
||
FilterManagers --> FilterHidden["排除已隐藏的物料<br/>!hiddenItems.has(materialCode)"]
|
||
FilterSelf --> FilterHidden
|
||
|
||
FilterHidden --> Output([filteredResults<br/>用于表格显示])
|
||
```
|
||
|
||
### 2.3 确认删除(同步数据库)
|
||
|
||
**源码位置**: `src/renderer/src/hooks/useCleaner.ts:289-344`
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
Start([点击确认删除]) --> RoleScope{角色判断}
|
||
|
||
RoleScope -->|Admin| UseAll["resultsToProcess = validationResults<br/>处理全部校验结果"]
|
||
RoleScope -->|User| UseFiltered["resultsToProcess = filteredResults<br/>仅处理筛选后结果"]
|
||
|
||
UseAll --> BuildPlan["buildDeletionPlan(resultsToProcess, selectedItems)"]
|
||
UseFiltered --> BuildPlan
|
||
|
||
BuildPlan --> Loop["遍历 resultsToProcess"]
|
||
Loop --> Check{物料是否勾选?}
|
||
|
||
Check -->|"已勾选"| HasManager{有负责人?}
|
||
Check -->|"未勾选"| ToDelete["加入 materialsToDelete<br/>从数据库移除标记"]
|
||
|
||
HasManager -->|"有"| ToUpsert["加入 materialsToUpsert<br/>写入/更新到数据库"]
|
||
HasManager -->|"无"| Missing["加入 missingManager<br/>阻止操作"]
|
||
|
||
ToUpsert --> Save["调用 materials:upsertBatch"]
|
||
ToDelete --> Del["调用 materials:delete"]
|
||
Missing --> Warn(["弹窗警告:缺少负责人"])
|
||
Save --> Done([完成])
|
||
Del --> Done
|
||
```
|
||
|
||
**关键代码**:
|
||
|
||
```typescript
|
||
// Admin 处理全部结果,User 只处理筛选后的结果
|
||
const resultsToProcess = isAdmin ? validationResults : filteredResults
|
||
```
|
||
|
||
**差异总结**:
|
||
|
||
| 维度 | Admin | User |
|
||
| ---------------- | --------------------------- | -------------------------------------- |
|
||
| 处理范围 | `validationResults`(全部) | `filteredResults`(自己的+无负责人的) |
|
||
| 可操作物料 | 所有负责人的物料 | 仅自己的 + 无负责人的 |
|
||
| 能否修改他人数据 | 是 | 否 |
|
||
|
||
---
|
||
|
||
## 阶段三:执行清理(ERP 删除)
|
||
|
||
**源码位置**:
|
||
|
||
- 前端调用: `src/renderer/src/hooks/cleaner/api.ts:116-166`
|
||
- 获取数据: `src/main/services/validation/validation-application-service.ts:497-655`
|
||
- 执行删除: `src/main/services/cleaner/cleaner-application-service.ts`
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant UI as 前端 useCleaner
|
||
participant API as api.ts
|
||
participant Main as 主进程
|
||
participant DB as 数据库
|
||
participant ERP as ERP 系统
|
||
|
||
UI->>API: runCleanerExecution({ dryRun, selectedManagers, ... })
|
||
API->>Main: getCleanerData({ selectedManagers })
|
||
|
||
alt Admin + selectedManagers 非空
|
||
Main->>DB: SELECT MaterialCode FROM MaterialsToBeDeleted<br/>WHERE ManagerName IN (@manager0, @manager1, ...)
|
||
Note over Main,DB: 按选中的负责人过滤<br/>从 MaterialsToBeDeleted 获取
|
||
else Admin + selectedManagers 为空
|
||
Main->>DB: SELECT DISTINCT MaterialCode FROM DiscreteMaterialPlanData<br/>WHERE SourceNumber IN (orderNumbers)
|
||
Note over Main,DB: 按订单号查询<br/>从 DiscreteMaterialPlanData 获取
|
||
else User
|
||
Main->>DB: SELECT MaterialCode FROM MaterialsToBeDeleted<br/>WHERE ManagerName = @username
|
||
Note over Main,DB: 按 ManagerName 过滤<br/>仅获取自己的物料代码
|
||
end
|
||
|
||
DB-->>Main: materialCodes[]
|
||
Main-->>API: { orderNumbers, materialCodes }
|
||
|
||
Note over API: 传入角色过滤后的 materialCodes
|
||
API->>Main: cleaner.runCleaner({ orderNumbers, materialCodes, ... })
|
||
|
||
Main->>ERP: 按订单遍历,删除指定物料
|
||
ERP-->>Main: 删除结果
|
||
Main-->>API: CleanerResult
|
||
API-->>UI: 显示执行报告
|
||
```
|
||
|
||
**SQL 差异**:
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph AdminWithMgr["Admin + selectedManagers 非空"]
|
||
A1["SELECT MaterialCode<br/>FROM MaterialsToBeDeleted<br/>WHERE ManagerName IN (@manager0, ...)<br/>AND MaterialCode IS NOT NULL"]
|
||
end
|
||
|
||
subgraph AdminNoMgr["Admin + selectedManagers 为空"]
|
||
A2["SELECT DISTINCT MaterialCode<br/>FROM DiscreteMaterialPlanData<br/>WHERE SourceNumber IN (orderNumbers)"]
|
||
end
|
||
|
||
subgraph User["User 查询"]
|
||
U1["SELECT MaterialCode<br/>FROM MaterialsToBeDeleted<br/>WHERE ManagerName = @username<br/>AND MaterialCode IS NOT NULL"]
|
||
end
|
||
|
||
AdminWithMgr --> |"按选中负责人过滤"| Result([传入 runCleaner])
|
||
AdminNoMgr --> |"按订单号查 DiscreteMaterialPlanData"| Result
|
||
User --> |"仅返回自己的物料代码"| Result
|
||
```
|
||
|
||
**差异总结**:
|
||
|
||
| 维度 | Admin(有 selectedManagers) | Admin(无 selectedManagers) | User |
|
||
| ---------- | ---------------------------- | -------------------------------------- | ------------------------------- |
|
||
| 数据源 | `MaterialsToBeDeleted` | `DiscreteMaterialPlanData` | `MaterialsToBeDeleted` |
|
||
| 查询条件 | `WHERE ManagerName IN (...)` | `WHERE SourceNumber IN (orderNumbers)` | `WHERE ManagerName = @username` |
|
||
| 可删除物料 | 选中负责人的物料 | 订单关联的全部物料 | 仅自己标记的物料 |
|
||
| 无订单号时 | — | 返回空数组 | — |
|
||
|
||
---
|
||
|
||
## 数据安全边界
|
||
|
||
角色隔离在**三个层面**同时生效,形成纵深防御:
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph layer1["第一层:前端过滤"]
|
||
L1["filterValidationResults()<br/>User 仅看到自己的物料"]
|
||
end
|
||
|
||
subgraph layer2["第二层:同步范围"]
|
||
L2["handleConfirmDeletion()<br/>User 仅同步 filteredResults"]
|
||
end
|
||
|
||
subgraph layer3["第三层:后端查询"]
|
||
L3["loadMaterialCodesForCleaner()<br/>Admin: WHERE ManagerName IN (selectedManagers)<br/>User: SQL WHERE ManagerName = user"]
|
||
end
|
||
|
||
L1 -->|"防止误操作"| L2
|
||
L2 -->|"缩小同步范围"| L3
|
||
L3 -->|"最终保证"| Safe([User 无法删除他人物料])
|
||
```
|
||
|
||
> **注意**:`runCleaner()` 本身不做角色过滤,它信任上游传入的 `materialCodes` 已经过角色过滤。安全性由 `getCleanerData()` 的 SQL 查询保证。
|
||
|
||
---
|
||
|
||
## 涉及文件索引
|
||
|
||
| 文件 | 关键函数/逻辑 | 行号 |
|
||
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------- |
|
||
| `src/renderer/src/hooks/cleaner/api.ts` | `initializeCleanerPage()`, `runCleanerExecution()` | 25-52, 116-166 |
|
||
| `src/renderer/src/hooks/useCleaner.ts` | `handleConfirmDeletion()`, 初始化逻辑 | 98-120, 289-345 |
|
||
| `src/renderer/src/hooks/cleaner/helpers.ts` | `filterValidationResults()`, `buildDeletionPlan()` | 34-57, 59-92 |
|
||
| `src/main/services/validation/validation-application-service.ts` | `getCleanerData()`, `loadMaterialCodesForCleaner()`, `queryMaterialCodesByManagers()` | 232-305, 497-604, 606-655 |
|
||
| `src/main/services/cleaner/cleaner-application-service.ts` | `runCleaner()` | 31-168 |
|
||
| `src/main/ipc/cleaner-handler.ts` | `CLEANER_RUN` handler | 16-22 |
|
||
| `src/main/ipc/validation-handler.ts` | `getCleanerData` handler | 194-223 |
|
||
| `src/preload/api/validation.ts` | `getCleanerData()` IPC 桥接 | 11-12 |
|
||
| `src/renderer/src/pages/CleanerPage.tsx` | 页面组件,条件渲染侧边栏 | 74-82 |
|