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;
/// 认领资格判定结果。拒绝时带原因码,让「没权限」与「不负责这条规则」可分辨。
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);
}
/// 认领被拒的原因码。
public static class S8ClaimDenyReason
{
/// 账号没有「认领」这个动作的权限(角色维度)。
public const string NoActionPermission = "claim_no_action_permission";
/// 账号不在这条规则的处理账号池里(责任维度)。
public const string NotInHandlerPool = "claim_not_in_handler_pool";
}
///
/// S8-HANDLER-POOL-1:「这个账号现在能不能认领这条异常」的唯一判定实现。
///
/// 三个正交维度,缺一不可:
///
/// - 动作权限:角色是否被授予 EXCEPTION_CLAIM(由 回答);
/// - 规则责任:账号是否在该异常来源规则的处理池里(本类回答);
/// - 状态机:单据当前状态是否允许认领 + 是否已被人抢走
/// (由 S8TaskFlowService 的原子 UPDATE 回答,本类不重复实现)。
///
/// 把三者混在一处判断是本仓已经踩过的坑:一旦某一维度被"顺手"放宽,
/// 另外两维看起来仍然正常,缺陷只在特定组合下出现。
///
/// 人工提报的异常没有来源规则(SourceRuleCode 为空)。
/// 这类单据不受责任池约束 —— 池是"谁负责这条规则",而它根本不来自任何规则。
/// 若在这里一刀切要求入池,人工提报的异常将永远无人可认领。
///
public interface IS8ClaimEligibility
{
Task CheckAsync(long tenantId, long userId, AdoS8Exception exception);
}
/// 正式实现。生产代码里只有这一份;接口只为测试留 seam。
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 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();
}
}