namespace Admin.NET.Plugin.AiDOP.Const.S8; /// /// S8-ACTION-PERMISSION-1:异常单「操作」的规范编码。 /// /// 每一条都对应一个真实存在的 API(见 里的 /// ApiHint)。刻意凭空造 EXCEPTION_CLOSE 之类的动作 —— /// S8 没有独立的「关闭」接口,关闭是 approve-verification 通过后的状态机结果, /// 给它一个可配置的权限位只会让管理员配了一个永远不会被检查的开关。 /// public static class S8ExceptionActionCode { /// 查看异常(列表 / 详情 / 时间线 / 决策 / 证据 / 筛选项)。 public const string View = "EXCEPTION_VIEW"; /// /// 查看全部异常(跨规则责任池)。 /// 在 P1-B 建立配置位,由 P1-C 的可见性收敛真正消费 —— /// 届时「非责任池成员」将只能看到自己有责任的规则所产生的异常。 /// public const string ViewAll = "EXCEPTION_VIEW_ALL"; /// 人工提报异常(POST /reports)。 public const string Create = "EXCEPTION_CREATE"; /// 认领(POST {id}/claim)。处理人恒为当前登录账号。 public const string Claim = "EXCEPTION_CLAIM"; /// 转派 / 指派他人(POST {id}/transfer)。与认领分开,权限强于认领。 public const string Transfer = "EXCEPTION_TRANSFER"; /// 开始处理(POST {id}/start-progress)。 public const string Start = "EXCEPTION_START"; /// 提交复核(POST {id}/submit-verification)。 public const string SubmitVerify = "EXCEPTION_SUBMIT_VERIFY"; /// 审核通过 / 退回(POST {id}/approve-verification · {id}/reject-verification)。 public const string Verify = "EXCEPTION_VERIFY"; /// 升级(POST {id}/upgrade)。 public const string Upgrade = "EXCEPTION_UPGRADE"; /// 驳回(POST {id}/reject)。 public const string Reject = "EXCEPTION_REJECT"; /// 补充说明(POST {id}/comment)。 public const string Comment = "EXCEPTION_COMMENT"; } /// 一个可配置动作的元信息。 public sealed record S8ExceptionActionDefinition( string Code, string DisplayName, string ApiHint, /// /// 该动作在旧权限体系中的对应能力码(SysMenu.Permission)。 /// 仅供首次 Provisioning 推导默认值使用 —— 让「本租户原本谁能做」原样延续, /// 而不是拍脑袋给一套新默认。为 null 表示旧体系里没有对应位(如 ViewAll)。 /// string? LegacyPermissionCode, /// /// 该动作还等价于哪些旧能力码(并集推导)。例如「审核」在旧体系里被拆成 /// approve / reject 两个码,任一持有即视为拥有审核能力。 /// string[] LegacyPermissionAliases, int OrderNo); /// /// 动作目录。这是「S8 异常单到底有哪些可授权动作」的唯一出处 —— /// 后端授权、Provisioning 默认值、前端权限矩阵三方共用同一份, /// 避免出现「页面上能配、后端根本不检查」或反之。 /// public static class S8ExceptionActionCatalog { public static readonly IReadOnlyList All = new List { new(S8ExceptionActionCode.View, "查看异常", "GET /exceptions · {id} · timeline · decisions · evidences", S8PermissionCatalog.ExceptionRead, Array.Empty(), 10), new(S8ExceptionActionCode.ViewAll, "查看全部异常", "(P1-C 可见性收敛消费)", null, Array.Empty(), 20), new(S8ExceptionActionCode.Create, "提报异常", "POST /reports", S8PermissionCatalog.ExceptionCreate, Array.Empty(), 30), new(S8ExceptionActionCode.Claim, "认领", "POST /exceptions/{id}/claim", S8PermissionCatalog.ExceptionClaim, Array.Empty(), 40), new(S8ExceptionActionCode.Transfer, "转派", "POST /exceptions/{id}/transfer", S8PermissionCatalog.ExceptionAssign, Array.Empty(), 50), new(S8ExceptionActionCode.Start, "开始处理", "POST /exceptions/{id}/start-progress", S8PermissionCatalog.ExceptionStart, Array.Empty(), 60), new(S8ExceptionActionCode.SubmitVerify, "提交复核", "POST /exceptions/{id}/submit-verification", S8PermissionCatalog.VerificationSubmit, Array.Empty(), 70), new(S8ExceptionActionCode.Verify, "审核(通过 / 退回)", "POST /exceptions/{id}/approve-verification · reject-verification", S8PermissionCatalog.VerificationApprove, new[] { S8PermissionCatalog.VerificationReject }, 80), new(S8ExceptionActionCode.Upgrade, "升级", "POST /exceptions/{id}/upgrade", S8PermissionCatalog.ExceptionUpgrade, Array.Empty(), 90), new(S8ExceptionActionCode.Reject, "驳回", "POST /exceptions/{id}/reject", S8PermissionCatalog.ExceptionReject, Array.Empty(), 100), new(S8ExceptionActionCode.Comment, "补充说明", "POST /exceptions/{id}/comment", S8PermissionCatalog.ExceptionComment, Array.Empty(), 110), }; private static readonly Dictionary ByCode = All.ToDictionary(x => x.Code, StringComparer.OrdinalIgnoreCase); /// 动作码是否在目录中。不在目录 = 未知动作 = 一律拒绝(fail closed)。 public static bool IsKnown(string? code) => !string.IsNullOrWhiteSpace(code) && ByCode.ContainsKey(code!); public static S8ExceptionActionDefinition? Find(string? code) => string.IsNullOrWhiteSpace(code) ? null : ByCode.GetValueOrDefault(code!); }