Files
BIPMaterialManager/docs/cleaner-role-based-flow.md
Misaka 3be7959067 feat(cleaner): support selectedManagers filtering for admin cleaner execution
Admin can now pass selectedManagers to getCleanerData so material codes
are queried from MaterialsToBeDeleted by ManagerName IN (selectedManagers).
When no managers are selected, fallback to DiscreteMaterialPlanData by
orderNumbers. User behavior is unchanged. Includes updated tests and
role-based flow documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 19:20:22 +08:00

12 KiB
Raw Blame History

清理器角色差异流程 — 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-52src/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{"优先级3User 覆盖)<br/>自己的类型关键词匹配?"}

    P3 -->|"匹配"| Override["强制覆盖<br/>managerName ← 当前用户"]
    P3 -->|"未匹配"| Keep["保持当前结果"]
    Skip --> Next(["下一条物料"])
    Override --> Next
    Keep --> Next

匹配优先级说明:

优先级 数据源 匹配方式 适用角色
1最高 MaterialsToBeDeleted MaterialCode 精确匹配 全部
2 MaterialsTypeToBeDeleted MaterialName 包含匹配 全部
3User 覆盖) 当前用户的类型关键词 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