Prechádzať zdrojové kódy

fix(s8): hydrate exception departments by master data factory

- Hydrate S8 exception list/detail department names using DepartmentMaster master-data factory.
- Reuse S8MasterData.DepartmentFactoryRefId (=1329900200002); ClearFilter + factory_ref_id + dept-id set as hard boundary.
- Replace operational factory_id=1 + tenant filter (which always missed) so names resolve instead of falling back to "部门ID:N".
- Keep factory hidden from frontend and API consumers; no dept-id, rule, params_json, scheduler, or DepartmentMaster data changes.
- Smoke confirms exception 391 and historical dept=10 records display "未归属" and all 38 rows show real names.

chore: bump version server 1.0.192
YY968XX 3 dní pred
rodič
commit
a5c8d06089

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.191</AssemblyVersion>
-    <FileVersion>1.0.191</FileVersion>
-    <Version>1.0.191</Version>
+    <AssemblyVersion>1.0.192</AssemblyVersion>
+    <FileVersion>1.0.192</FileVersion>
+    <Version>1.0.192</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 32 - 3
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ExceptionService.cs

@@ -4,6 +4,7 @@ using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
 using Admin.NET.Plugin.AiDOP.Entity.S8;
 using Admin.NET.Plugin.AiDOP.Infrastructure;
 using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
+using Microsoft.Extensions.Options;
 
 namespace Admin.NET.Plugin.AiDOP.Service.S8;
 
@@ -14,23 +15,30 @@ public class S8ExceptionService : ITransient
     private readonly SqlSugarRepository<AdoS0EmployeeMaster> _empRep;
     private readonly SqlSugarRepository<SysUser> _sysUserRep;
     private readonly S8ImpactMetricsService _impactMetricsService;
+    // S8-EXCEPTION-DEPT-NAME-HYDRATION-MASTERDATA-FACTORY-FIX-1:部门名水合按 DepartmentMaster 主数据 factory 解析。
+    private readonly S8MasterDataOptions _masterDataOptions;
 
     // S8-DEMO-IMPACT-SORT-NOTICE-1:影响排序的候选集合上限。
     // 超过此阈值时降级为 CreatedAt DESC 走 DB 分页,避免全量取 → 内存排序在生产规模下退化。
     private const int ImpactSortCandidateCap = 2000;
 
+    // S8-EXCEPTION-DEPT-NAME-HYDRATION-MASTERDATA-FACTORY-FIX-1:未归属部门 codename(与 S8ManualReportService 同协议常量值)。
+    private const string UnassignedDepartmentCode = "D-UNASSIGNED";
+
     public S8ExceptionService(
         SqlSugarRepository<AdoS8Exception> rep,
         SqlSugarRepository<AdoS0DepartmentMaster> deptRep,
         SqlSugarRepository<AdoS0EmployeeMaster> empRep,
         SqlSugarRepository<SysUser> sysUserRep,
-        S8ImpactMetricsService impactMetricsService)
+        S8ImpactMetricsService impactMetricsService,
+        IOptions<S8MasterDataOptions> masterDataOptions)
     {
         _rep = rep;
         _deptRep = deptRep;
         _empRep = empRep;
         _sysUserRep = sysUserRep;
         _impactMetricsService = impactMetricsService;
+        _masterDataOptions = masterDataOptions.Value;
     }
 
     public async Task<(int total, List<AdoS8ExceptionListItemDto> list)> GetPagedAsync(AdoS8ExceptionQueryDto q)
@@ -405,10 +413,15 @@ public class S8ExceptionService : ITransient
             .Distinct()
             .ToList();
 
+        // S8-EXCEPTION-DEPT-NAME-HYDRATION-MASTERDATA-FACTORY-FIX-1:
+        // 部门主数据位于 S0 域 factory(与 S8 运营 factoryId 不同源),且 DepartmentMaster.tenant_id 属 S0 域租户。
+        // 用主数据 factory + ClearFilter(factory_ref_id + 具体 dept_id 集合做硬边界,等价安全,无跨租户泄漏),
+        // 取代旧的“运营 factoryId + 全局租户过滤”——后者必然查空 → 全部回落“部门ID:N”。
+        var masterDataFactoryId = await ResolveMasterDataDepartmentFactoryRefIdAsync(factoryId);
         var deptMap = deptIds.Count == 0
             ? new Dictionary<long, string>()
-            : (await _deptRep.AsQueryable()
-                .Where(x => deptIds.Contains(x.Id) && x.FactoryRefId == factoryId)
+            : (await _deptRep.AsQueryable().ClearFilter()
+                .Where(x => deptIds.Contains(x.Id) && x.FactoryRefId == masterDataFactoryId)
                 .Select(x => new { x.Id, Name = x.Descr ?? x.Department })
                 .ToListAsync())
                 .ToDictionary(x => x.Id, x => x.Name);
@@ -469,6 +482,22 @@ public class S8ExceptionService : ITransient
             : $"部门ID:{deptId}";
     }
 
+    // S8-EXCEPTION-DEPT-NAME-HYDRATION-MASTERDATA-FACTORY-FIX-1:解析 DepartmentMaster 主数据所在 factory_ref_id。
+    // 优先读配置 S8MasterData.DepartmentFactoryRefId;缺失(<=0)则推导“唯一 active D-UNASSIGNED 的 FactoryRefId”。
+    // 显示路径不抛错:推导 0 条 / 不唯一时退回运营 factoryId(保持旧“部门ID:N”兜底,绝不让列表/详情查询失败)。
+    private async Task<long> ResolveMasterDataDepartmentFactoryRefIdAsync(long fallbackFactoryId)
+    {
+        var configured = _masterDataOptions.DepartmentFactoryRefId;
+        if (configured > 0) return configured;
+
+        var factories = await _deptRep.AsQueryable().ClearFilter()
+            .Where(x => x.Department == UnassignedDepartmentCode && x.IsActive)
+            .Select(x => x.FactoryRefId)
+            .Distinct()
+            .ToListAsync();
+        return factories.Count == 1 ? factories[0] : fallbackFactoryId;
+    }
+
     /// <summary>
     /// S2-EW:debug-only 软删 RelatedObjectCode 以 "TEST_G09_" 开头的异常。
     /// 调用方仅限 <see cref="Controllers.S8.AdoS8WatchDebugController"/>,由其 _debugEndpointEnabled 守门。