| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182 |
- using Admin.NET.Plugin.AiDOP.Const.S8;
- using Admin.NET.Plugin.AiDOP.Entity.S8;
- using Admin.NET.Plugin.AiDOP.Service.S8;
- namespace Admin.NET.Plugin.AiDOP.Infrastructure.S8;
- /// <summary>认领资格判定结果。拒绝时带原因码,让「没权限」与「不负责这条规则」可分辨。</summary>
- public sealed record S8ClaimEligibilityResult(bool Allowed, string? ReasonCode, string? Message)
- {
- public static S8ClaimEligibilityResult Pass() => new(true, null, null);
- public static S8ClaimEligibilityResult Deny(string reason, string message) => new(false, reason, message);
- }
- /// <summary>认领被拒的原因码。</summary>
- public static class S8ClaimDenyReason
- {
- /// <summary>账号没有「认领」这个动作的权限(角色维度)。</summary>
- public const string NoActionPermission = "claim_no_action_permission";
- /// <summary>账号不在这条规则的处理账号池里(责任维度)。</summary>
- public const string NotInHandlerPool = "claim_not_in_handler_pool";
- }
- /// <summary>
- /// S8-HANDLER-POOL-1:「这个账号现在能不能认领这条异常」的<b>唯一</b>判定实现。
- ///
- /// <para><b>三个正交维度,缺一不可</b>:
- /// <list type="number">
- /// <item><b>动作权限</b>:角色是否被授予 <c>EXCEPTION_CLAIM</c>(由 <see cref="IS8ExceptionActionAuthorizer"/> 回答);</item>
- /// <item><b>规则责任</b>:账号是否在该异常来源规则的处理池里(本类回答);</item>
- /// <item><b>状态机</b>:单据当前状态是否允许认领 + 是否已被人抢走
- /// (由 <c>S8TaskFlowService</c> 的原子 UPDATE 回答,本类不重复实现)。</item>
- /// </list>
- /// 把三者混在一处判断是本仓已经踩过的坑:一旦某一维度被"顺手"放宽,
- /// 另外两维看起来仍然正常,缺陷只在特定组合下出现。</para>
- ///
- /// <para><b>人工提报的异常没有来源规则</b>(<c>SourceRuleCode</c> 为空)。
- /// 这类单据不受责任池约束 —— 池是"谁负责这条规则",而它根本不来自任何规则。
- /// 若在这里一刀切要求入池,人工提报的异常将永远无人可认领。</para>
- /// </summary>
- public interface IS8ClaimEligibility
- {
- Task<S8ClaimEligibilityResult> CheckAsync(long tenantId, long userId, AdoS8Exception exception);
- }
- /// <summary>正式实现。生产代码里只有这一份;接口只为测试留 seam。</summary>
- public sealed class S8ClaimEligibility : IS8ClaimEligibility, ITransient
- {
- private readonly IS8ExceptionActionAuthorizer _authorizer;
- private readonly IS8RuleHandlerPoolReader _handlerPool;
- public S8ClaimEligibility(
- IS8ExceptionActionAuthorizer authorizer,
- IS8RuleHandlerPoolReader handlerPool)
- {
- _authorizer = authorizer;
- _handlerPool = handlerPool;
- }
- public async Task<S8ClaimEligibilityResult> CheckAsync(long tenantId, long userId, AdoS8Exception exception)
- {
- // ① 动作权限。Controller 上的 [S8ExceptionAction] 已经拦过一次,
- // 这里再判一次是因为本方法也被"我能不能认领"的前端探测接口复用 ——
- // 两处都必须给出同一个答案,否则按钮显示可点、点了却 403。
- var auth = await _authorizer.AuthorizeAsync(tenantId, userId, S8ExceptionActionCode.Claim);
- if (!auth.Allowed)
- return S8ClaimEligibilityResult.Deny(
- S8ClaimDenyReason.NoActionPermission, "没有权限执行该操作");
- // ② 规则责任。人工提报无来源规则,不受池约束(见接口注释)。
- var ruleCode = exception.SourceRuleCode;
- if (string.IsNullOrWhiteSpace(ruleCode))
- return S8ClaimEligibilityResult.Pass();
- if (!await _handlerPool.IsMemberAsync(tenantId, ruleCode!, userId))
- return S8ClaimEligibilityResult.Deny(
- S8ClaimDenyReason.NotInHandlerPool,
- "你不是该规则的处理人员,无法认领。如需处理请联系管理员将你加入该规则的处理账号池。");
- return S8ClaimEligibilityResult.Pass();
- }
- }
|