# 清理器角色差异流程 — Admin vs User **文档版本**: 1.1 **创建日期**: 2026-04-06 **面向对象**: 开发人员 ## 概述 清理器(Cleaner)在决定"哪些物料需要被清除"时,Admin 和 User 两个角色存在系统性的差异。这些差异贯穿三个阶段:**初始化 → 校验确认 → 执行清理**。 本文档使用 Mermaid 图表说明每个阶段的角色分支逻辑。 --- ## 全局流程概览 ```mermaid flowchart TB subgraph init["阶段一:页面初始化"] I1([页面加载]) --> I2{角色判断} I2 -->|Admin| I3["管理员列表 ← 全部负责人
默认选中全部"] I2 -->|User| I4["管理员列表 ← 空
默认选中仅自己"] end subgraph validate["阶段二:校验 → 勾选 → 同步数据库"] V1([点击校验]) --> V2["后端查询物料
(不区分角色)"] V2 --> V3["物料匹配算法
(User 有覆盖匹配)"] V3 --> V4{角色判断} V4 -->|Admin| V5["显示全部物料
侧边栏可按负责人筛选"] V4 -->|User| V6["仅显示自己的物料
+ 无负责人的物料"] V5 --> V7["用户勾选/取消勾选"] V6 --> V7 V7 --> V8{点击同步数据库} V8 --> V9{角色判断} V9 -->|Admin| V10["处理范围:全部校验结果"] V9 -->|User| V11["处理范围:仅筛选后结果"] end subgraph execute["阶段三:执行清理(ERP 删除)"] E1([点击执行清理]) --> E2["getCleanerData(selectedManagers)
获取物料代码"] E2 --> E3{角色判断} E3 -->|Admin| E3a{selectedManagers
非空?} E3a -->|"是"| E4["SQL WHERE ManagerName IN (选中)
从 MaterialsToBeDeleted 获取"] E3a -->|"否"| E4b["从 DiscreteMaterialPlanData
按 orderNumbers 获取"] E3 -->|User| E5["SQL WHERE ManagerName = 用户
仅获取自己的物料代码"] 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
判断是否管理员"] GetAdmin --> GetUser["调用 auth:getCurrentUser
获取当前用户名"] GetUser --> RoleCheck{isAdmin?} RoleCheck -->|Admin| GetManagers["调用 materials:getManagers
获取全部负责人列表"] GetManagers --> SelectAll["selectedManagers ← 全部负责人
(默认全选)"] SelectAll --> RenderSidebar["渲染 CleanerSidebar
显示负责人复选框"] RoleCheck -->|User| SetSelf["selectedManagers ← {currentUsername}
(仅选中自己)"] SetSelf --> NoSidebar["不渲染 CleanerSidebar
无侧边栏"] 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
MaterialsToBeDeleted
精确匹配 MaterialCode?"} P1 -->|"匹配"| SetManager["managerName ← 表中记录
isMarkedForDeletion = true"] P1 -->|"未匹配"| P2{"优先级2
MaterialsTypeToBeDeleted
MaterialName 包含匹配?"} P2 -->|"匹配"| SetType["managerName ← 类型关键词负责人
matchedTypeKeyword ← 匹配项"] P2 -->|"未匹配"| SetNull["managerName = null"] SetManager --> RoleCheck{角色?} SetType --> RoleCheck SetNull --> RoleCheck RoleCheck -->|"Admin"| Skip["跳过覆盖
使用当前结果"] RoleCheck -->|"User"| P3{"优先级3(User 覆盖)
自己的类型关键词匹配?"} P3 -->|"匹配"| Override["强制覆盖
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["按侧边栏选中的负责人过滤
selectedManagers.has(managerName)
|| !managerName"] RoleCheck -->|User| FilterSelf["仅显示自己的 + 无负责人的
managerName === currentUsername
|| !managerName"] FilterManagers --> FilterHidden["排除已隐藏的物料
!hiddenItems.has(materialCode)"] FilterSelf --> FilterHidden FilterHidden --> Output([filteredResults
用于表格显示]) ``` ### 2.3 确认删除(同步数据库) **源码位置**: `src/renderer/src/hooks/useCleaner.ts:289-344` ```mermaid flowchart TB Start([点击确认删除]) --> RoleScope{角色判断} RoleScope -->|Admin| UseAll["resultsToProcess = validationResults
处理全部校验结果"] RoleScope -->|User| UseFiltered["resultsToProcess = filteredResults
仅处理筛选后结果"] UseAll --> BuildPlan["buildDeletionPlan(resultsToProcess, selectedItems)"] UseFiltered --> BuildPlan BuildPlan --> Loop["遍历 resultsToProcess"] Loop --> Check{物料是否勾选?} Check -->|"已勾选"| HasManager{有负责人?} Check -->|"未勾选"| ToDelete["加入 materialsToDelete
从数据库移除标记"] HasManager -->|"有"| ToUpsert["加入 materialsToUpsert
写入/更新到数据库"] HasManager -->|"无"| Missing["加入 missingManager
阻止操作"] 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
WHERE ManagerName IN (@manager0, @manager1, ...) Note over Main,DB: 按选中的负责人过滤
从 MaterialsToBeDeleted 获取 else Admin + selectedManagers 为空 Main->>DB: SELECT DISTINCT MaterialCode FROM DiscreteMaterialPlanData
WHERE SourceNumber IN (orderNumbers) Note over Main,DB: 按订单号查询
从 DiscreteMaterialPlanData 获取 else User Main->>DB: SELECT MaterialCode FROM MaterialsToBeDeleted
WHERE ManagerName = @username Note over Main,DB: 按 ManagerName 过滤
仅获取自己的物料代码 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
FROM MaterialsToBeDeleted
WHERE ManagerName IN (@manager0, ...)
AND MaterialCode IS NOT NULL"] end subgraph AdminNoMgr["Admin + selectedManagers 为空"] A2["SELECT DISTINCT MaterialCode
FROM DiscreteMaterialPlanData
WHERE SourceNumber IN (orderNumbers)"] end subgraph User["User 查询"] U1["SELECT MaterialCode
FROM MaterialsToBeDeleted
WHERE ManagerName = @username
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()
User 仅看到自己的物料"] end subgraph layer2["第二层:同步范围"] L2["handleConfirmDeletion()
User 仅同步 filteredResults"] end subgraph layer3["第三层:后端查询"] L3["loadMaterialCodesForCleaner()
Admin: WHERE ManagerName IN (selectedManagers)
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 |