فهرست منبع

fix(s8): align reporter idspace with employee master

YY968XX 2 هفته پیش
والد
کامیت
143eb6c9d1

+ 5 - 4
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ExceptionService.cs

@@ -234,9 +234,9 @@ public class S8ExceptionService : ITransient
                 .ToListAsync())
                 .ToDictionary(x => x.Id, x => x.Name);
 
-        // ReporterId 现在记录 sysUser.Id(OBS-S8-REPORT-REPORTER-NULL-001 修复后由服务端 JWT 兜底),
-        // 与 employee.Id 不在同一命名空间,需要单独查 SysUser 取 RealName/Account;
-        // 同时保留对历史 reporter=employee.Id 数据的兼容(empMap fallback)
+        // S8-REPORTER-IDSPACE-FIX-1(P0-B-1):ReporterId 新协议 = EmployeeMaster.RecID(与 assignee/verifier 同 idspace)。
+        // 水合优先级:EmployeeMaster.Name → fallback SysUser.RealName/Account(兼容旧数据 reporter=SysUser.Id)。
+        // 旧注释 “ReporterId 现在记录 sysUser.Id” 已失效,由本批协议替代
         var reporterUserIds = list.OfType<AdoS8ExceptionDetailDto>()
             .Select(x => x.ReporterId ?? 0L)
             .Where(x => x > 0)
@@ -260,8 +260,9 @@ public class S8ExceptionService : ITransient
 
             if (row is AdoS8ExceptionDetailDto detail)
             {
+                // S8-REPORTER-IDSPACE-FIX-1:empMap(EmployeeMaster.RecID)优先,reporterUserMap(SysUser.Id)兜底兼容旧数据。
                 detail.ReporterName = detail.ReporterId.HasValue
-                    ? (reporterUserMap.GetValueOrDefault(detail.ReporterId.Value) ?? empMap.GetValueOrDefault(detail.ReporterId.Value))
+                    ? (empMap.GetValueOrDefault(detail.ReporterId.Value) ?? reporterUserMap.GetValueOrDefault(detail.ReporterId.Value))
                     : null;
                 detail.VerifierName = detail.VerifierId.HasValue ? empMap.GetValueOrDefault(detail.VerifierId.Value) : null;
             }

+ 33 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ManualReportService.cs

@@ -74,6 +74,7 @@ public class S8ManualReportService : ITransient
     private readonly SqlSugarRepository<AdoS0DepartmentMaster> _deptRep;
     private readonly SqlSugarRepository<AdoS0LineMaster> _lineRep;
     private readonly SqlSugarRepository<AdoS8ExceptionType> _typeRep;
+    private readonly SqlSugarRepository<AdoS0EmployeeMaster> _empRep;
     private readonly UserManager _userManager;
     private readonly FlowEngineService _flowEngine;
     private readonly ILogger<S8ManualReportService> _logger;
@@ -86,6 +87,7 @@ public class S8ManualReportService : ITransient
         SqlSugarRepository<AdoS0DepartmentMaster> deptRep,
         SqlSugarRepository<AdoS0LineMaster> lineRep,
         SqlSugarRepository<AdoS8ExceptionType> typeRep,
+        SqlSugarRepository<AdoS0EmployeeMaster> empRep,
         UserManager userManager,
         FlowEngineService flowEngine,
         ILogger<S8ManualReportService> logger)
@@ -97,11 +99,27 @@ public class S8ManualReportService : ITransient
         _deptRep = deptRep;
         _lineRep = lineRep;
         _typeRep = typeRep;
+        _empRep = empRep;
         _userManager = userManager;
         _flowEngine = flowEngine;
         _logger = logger;
     }
 
+    // S8-REPORTER-IDSPACE-FIX-1(P0-B-1):把当前登录 SysUser.Id 反查 EmployeeMaster.RecID。
+    // 用于 reporter_id idspace 与 assignee_id / verifier_id 对齐。
+    // 协议:sys_user_id + factory_ref_id 双键命中;EmployeeMaster.tenant_id 与 SysUser.TenantId 历史错位 →
+    // ClearFilter,由 factoryRefId 做硬边界(与 S8MasterDataAdapter / S8TaskFlowService 同口径)。
+    // 未绑定时返回 null,调用方负责诊断日志。
+    private async Task<long?> ResolveCurrentEmployeeIdAsync(long factoryId, long sysUserId)
+    {
+        if (sysUserId <= 0 || factoryId <= 0) return null;
+        var emp = await _empRep.AsQueryable().ClearFilter()
+            .Where(x => x.SysUserId == sysUserId && x.FactoryRefId == factoryId && x.IsActive)
+            .Select(x => new { x.Id })
+            .FirstAsync();
+        return emp?.Id;
+    }
+
     /// <summary>
     /// 主动提报推断 ExceptionTypeCode:场景下取启用且 SortNo 最小的一条。
     /// baseline 异常类型当前 tenant_id=0/factory_id=0(全局基线),所以匹配条件为
@@ -194,7 +212,20 @@ public class S8ManualReportService : ITransient
         var severity = S8SeverityCode.Normalize(rawSeverity);
 
         // 提报人以服务端登录上下文为准,忽略前端传入;未登录上下文落 null。
+        // currentUserId 仍是 SysUser.Id,仅用作 timeline.OperatorId(与 S8TaskFlowService 同口径);
+        // S8-REPORTER-IDSPACE-FIX-1:ReporterId 写入改用 EmployeeMaster.RecID(与 assignee/verifier 同 idspace)。
         var currentUserId = _userManager.UserId > 0 ? _userManager.UserId : (long?)null;
+        long? currentEmployeeId = null;
+        if (currentUserId.HasValue)
+        {
+            currentEmployeeId = await ResolveCurrentEmployeeIdAsync(dto.FactoryId, currentUserId.Value);
+            if (currentEmployeeId == null)
+            {
+                _logger.LogWarning(
+                    "manual_report_reporter_unbound sysUserId={SysUserId} factoryId={FactoryId} title={Title}",
+                    currentUserId.Value, dto.FactoryId, dto.Title);
+            }
+        }
 
         // 主动提报无前端 type 字段,按场景兜底推断;保证不进"未分类"桶。
         var inferredType = await InferExceptionTypeCodeAsync(dto.TenantId, dto.FactoryId, dto.SceneCode.Trim());
@@ -229,7 +260,8 @@ public class S8ManualReportService : ITransient
             PriorityLevel = "P3",
             OccurrenceDeptId = dto.OccurrenceDeptId,
             ResponsibleDeptId = dto.ResponsibleDeptId,
-            ReporterId = currentUserId,
+            // S8-REPORTER-IDSPACE-FIX-1:reporter_id idspace = EmployeeMaster.RecID(与 assignee/verifier 同)。
+            ReporterId = currentEmployeeId,
             ExceptionTypeCode = inferredType,
             ModuleCode = resolvedModule,
             // S8-PROCESS-NODE-MODULE-CODE-ALIGNMENT-EXEC-1:当前阶段 process_node_code 留空,