Kaynağa Gözat

fix(s8): map task operators to bound system users

S8 task-flow auth previously compared assignee/verifier (employee.Id) to
the JWT user id (sysUser.Id), which live in different namespaces and
made submit/approve/reject-verification reject every legitimate caller.

Add EmployeeMaster.SysUserId column and resolve assignee/verifier through
that mapping before comparing against UserManager.UserId. Error message
includes "或当前账号未绑定员工主数据" so unbound employees fail visibly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
YY968XX 5 ay önce
ebeveyn
işleme
afca5fece8

+ 3 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Warehouse/AdoS0EmployeeMaster.cs

@@ -79,6 +79,9 @@ public class AdoS0EmployeeMaster
     [SugarColumn(ColumnName = "UpdateTime", ColumnDescription = "更新时间", IsNullable = true)]
     [SugarColumn(ColumnName = "UpdateTime", ColumnDescription = "更新时间", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
     public DateTime? UpdateTime { get; set; }
 
 
+    [SugarColumn(ColumnName = "sys_user_id", ColumnDescription = "关联系统账号ID(SysUser.Id;为空表示未绑定)", ColumnDataType = "bigint", IsNullable = true)]
+    public long? SysUserId { get; set; }
+
     /// <summary>列表展示:部门说明(不落库,来自 DepartmentMaster 关联)</summary>
     /// <summary>列表展示:部门说明(不落库,来自 DepartmentMaster 关联)</summary>
     [SugarColumn(IsIgnore = true)]
     [SugarColumn(IsIgnore = true)]
     public string? DepartDescr { get; set; }
     public string? DepartDescr { get; set; }

+ 26 - 6
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8TaskFlowService.cs

@@ -1,3 +1,4 @@
+using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
 using Admin.NET.Plugin.AiDOP.Entity.S8;
 using Admin.NET.Plugin.AiDOP.Entity.S8;
 using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
 using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
 using Admin.NET.Plugin.ApprovalFlow.Service;
 using Admin.NET.Plugin.ApprovalFlow.Service;
@@ -8,17 +9,20 @@ public class S8TaskFlowService : ITransient
 {
 {
     private readonly SqlSugarRepository<AdoS8Exception> _rep;
     private readonly SqlSugarRepository<AdoS8Exception> _rep;
     private readonly SqlSugarRepository<AdoS8ExceptionTimeline> _timelineRep;
     private readonly SqlSugarRepository<AdoS8ExceptionTimeline> _timelineRep;
+    private readonly SqlSugarRepository<AdoS0EmployeeMaster> _employeeRep;
     private readonly FlowEngineService _flowEngine;
     private readonly FlowEngineService _flowEngine;
     private readonly UserManager _userManager;
     private readonly UserManager _userManager;
 
 
     public S8TaskFlowService(
     public S8TaskFlowService(
         SqlSugarRepository<AdoS8Exception> rep,
         SqlSugarRepository<AdoS8Exception> rep,
         SqlSugarRepository<AdoS8ExceptionTimeline> timelineRep,
         SqlSugarRepository<AdoS8ExceptionTimeline> timelineRep,
+        SqlSugarRepository<AdoS0EmployeeMaster> employeeRep,
         FlowEngineService flowEngine,
         FlowEngineService flowEngine,
         UserManager userManager)
         UserManager userManager)
     {
     {
         _rep = rep;
         _rep = rep;
         _timelineRep = timelineRep;
         _timelineRep = timelineRep;
+        _employeeRep = employeeRep;
         _flowEngine = flowEngine;
         _flowEngine = flowEngine;
         _userManager = userManager;
         _userManager = userManager;
     }
     }
@@ -170,8 +174,8 @@ public class S8TaskFlowService : ITransient
     {
     {
         var currentUserId = GetCurrentUserId();
         var currentUserId = GetCurrentUserId();
         var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
         var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
-        if (e.AssigneeId != currentUserId)
-            throw new S8BizException("只有当前处理人才能提交复检");
+        await EnsureCurrentUserIsOperatorAsync(e.AssigneeId, currentUserId,
+            "只有当前处理人才能提交复检(或当前账号未绑定员工主数据)");
         if (verifierId <= 0)
         if (verifierId <= 0)
             throw new S8BizException("请选择检验人");
             throw new S8BizException("请选择检验人");
         if (!S8StatusRules.IsAllowedTransition(e.Status, "PENDING_VERIFICATION"))
         if (!S8StatusRules.IsAllowedTransition(e.Status, "PENDING_VERIFICATION"))
@@ -199,8 +203,8 @@ public class S8TaskFlowService : ITransient
     {
     {
         var currentUserId = GetCurrentUserId();
         var currentUserId = GetCurrentUserId();
         var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
         var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
-        if (e.VerifierId != currentUserId)
-            throw new S8BizException("只有指定检验人才能检验通过");
+        await EnsureCurrentUserIsOperatorAsync(e.VerifierId, currentUserId,
+            "只有指定检验人才能检验通过(或当前账号未绑定员工主数据)");
         if (!S8StatusRules.IsAllowedTransition(e.Status, "RESOLVED"))
         if (!S8StatusRules.IsAllowedTransition(e.Status, "RESOLVED"))
             throw new S8BizException($"状态 {e.Status} 不可检验通过");
             throw new S8BizException($"状态 {e.Status} 不可检验通过");
 
 
@@ -227,8 +231,8 @@ public class S8TaskFlowService : ITransient
     {
     {
         var currentUserId = GetCurrentUserId();
         var currentUserId = GetCurrentUserId();
         var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
         var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
-        if (e.VerifierId != currentUserId)
-            throw new S8BizException("只有指定检验人才能检验退回");
+        await EnsureCurrentUserIsOperatorAsync(e.VerifierId, currentUserId,
+            "只有指定检验人才能检验退回(或当前账号未绑定员工主数据)");
         if (!S8StatusRules.IsAllowedTransition(e.Status, "IN_PROGRESS"))
         if (!S8StatusRules.IsAllowedTransition(e.Status, "IN_PROGRESS"))
             throw new S8BizException($"状态 {e.Status} 不可检验退回");
             throw new S8BizException($"状态 {e.Status} 不可检验退回");
         if (string.IsNullOrWhiteSpace(remark))
         if (string.IsNullOrWhiteSpace(remark))
@@ -271,6 +275,22 @@ public class S8TaskFlowService : ITransient
         return currentUserId;
         return currentUserId;
     }
     }
 
 
+    // 把异常上的处理人/检验人(employeeId) 经 EmployeeMaster.SysUserId 解析到系统账号 ID。
+    private async Task<long?> GetEmployeeSysUserIdAsync(long? employeeId)
+    {
+        if (!employeeId.HasValue || employeeId.Value <= 0) return null;
+        var emp = await _employeeRep.GetFirstAsync(x => x.Id == employeeId.Value);
+        return emp?.SysUserId;
+    }
+
+    // 鉴权统一入口:要求当前登录用户必须是 employeeId 解析后的 SysUserId。
+    private async Task EnsureCurrentUserIsOperatorAsync(long? employeeId, long currentUserId, string failMessage)
+    {
+        var ownerSysUserId = await GetEmployeeSysUserIdAsync(employeeId);
+        if (ownerSysUserId != currentUserId)
+            throw new S8BizException(failMessage);
+    }
+
     private async Task InsertTimelineAsync(long exceptionId, string code, string label, string? from, string? to,
     private async Task InsertTimelineAsync(long exceptionId, string code, string label, string? from, string? to,
         long? operatorId, string? operatorName, string? remark) =>
         long? operatorId, string? operatorName, string? remark) =>
         await _timelineRep.InsertAsync(new AdoS8ExceptionTimeline
         await _timelineRep.InsertAsync(new AdoS8ExceptionTimeline