- 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.
14 KiB
14 KiB
清理器角色差异流程 — Admin vs User
文档版本: 1.1 创建日期: 2026-04-06 面向对象: 开发人员
概述
清理器(Cleaner)在决定"哪些物料需要被清除"时,Admin 和 User 两个角色存在系统性的差异。这些差异贯穿三个阶段:初始化 → 校验确认 → 执行清理。
本文档使用 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
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 拿到相同的物料数据。区别在于匹配算法:
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
校验结果返回前端后,会根据角色进行显示过滤:
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
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
关键代码:
// 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
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 差异:
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 |
| 可删除物料 | 选中负责人的物料 | 订单关联的全部物料 | 仅自己标记的物料 |
| 无订单号时 | — | 返回空数组 | — |
数据安全边界
角色隔离在三个层面同时生效,形成纵深防御:
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 |