namespace Admin.NET.Plugin.AiDOP.Infrastructure.S8; /// /// S8-CFG-NOTIFY-NONDEFAULT-ROLE-OPTIONS-FIX-1:S8 业务角色的 **canonical 定义(唯一来源)**。 /// /// 身份模型(本批锁定): /// SysRole.Code = **逻辑身份**,跨租户稳定; /// SysRole.Id = **租户本地物理身份**,同一个 Code 在每个租户都有各自不同的 Id。 /// 因此任何「按角色找行」的逻辑都必须**按 Code 在可信租户作用域内查**, /// 绝不能按写死的物理 Id 去找——那等于假设所有租户共用默认租户那一份角色行。 /// /// 为什么需要这个文件:这 7 个角色此前在仓内被**逐字复制了两份** /// (S8SysRoleSeedData 种默认租户、AidopTenantInitService 按租户复制), /// 内容恰好一致纯属人工同步。再在第三处(role-options)复制一份必然分叉, /// 所以本文件把两份合并为一份,两个既有定义改为引用它。 /// /// ⚠️ 不含 sys_admin:那是平台系统管理员角色(默认租户 `系统管理员`), /// 既不在上述任一 S8 角色定义里,也不被任何一条生效通知层引用 /// (14 条 GLOBAL 层只引用下列 Code 中的 5 个)。仓内唯一出现 sys_admin 的通知层 /// 属于另一个租户的 legacy demo 行,且其 scene 是已被禁止新建的非规范场景。 /// 故它**不是** S8 业务角色,不应出现在租户的 CFG_NOTIFY 角色候选里。 /// public static class S8CanonicalRole { /// /// S8 七个业务角色(异常协同承接)。 /// OrderNo 同时是**产品展示顺序**——产品顺序不得由数据库物理 Id 决定, /// 否则同一份配置在不同租户会呈现不同排序。 /// public static readonly (string Code, string Name, int OrderNo)[] BusinessRoles = { ("ROLE_ORDER_PLANNER", "订单计划员", 800), ("ROLE_PRODUCTION_PLANNER", "生产计划员", 801), ("ROLE_PURCHASER", "采购员", 802), ("ROLE_WH_INBOUND", "仓库收货员", 803), ("ROLE_WH_OUTBOUND", "仓库发货员", 804), ("ROLE_QC", "质检员", 805), ("ROLE_EQUIP_MAINT", "设备维修员", 806), }; /// canonical 业务角色 Code 白名单(候选集合的**唯一**筛选依据)。 public static readonly string[] BusinessRoleCodes = Array.ConvertAll(BusinessRoles, x => x.Code); /// /// canonical 展示序。未知 Code 排到末尾而不是抛错—— /// 排序不是校验入口,不该因为出现一个陌生 Code 就让整个下拉挂掉。 /// public static int OrderOf(string? code) { foreach (var r in BusinessRoles) if (string.Equals(r.Code, code, StringComparison.Ordinal)) return r.OrderNo; return int.MaxValue; } }