Просмотр исходного кода

fix(s8): align role configuration with global scope | server 1.0.421

- role list: read the platform defaults at (0,0) together with the current trusted
  tenant/factory row and merge them by role_code, factory override winning, matching the
  S8ConfigScope pattern already used by exception types, dashboard cells and monitor
  metrics. The list previously matched the trusted scope exactly while the seven business
  roles are seeded as platform defaults, so it returned nothing in any real tenant
- as a result the exception type form could not offer a responsible or escalation role at
  all, even though 48 of the 53 configured types already reference one
- entity: expose non-persisted Scope and HasGlobalDefault so a caller can tell a platform
  default from a factory override, and so a platform default is never presented as editable
- create: return the generated identity instead of 0, so the caller can immediately read,
  update or delete the new row

Note: permission_codes remains unused at runtime. S8 authorization continues to run on
SysUserRole and SysRoleMenu; this change does not introduce permission enforcement.
YY968XX 1 месяц назад
Родитель
Сommit
36c71dfc91

+ 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.420</AssemblyVersion>
-    <FileVersion>1.0.420</FileVersion>
-    <Version>1.0.420</Version>
+    <AssemblyVersion>1.0.421</AssemblyVersion>
+    <FileVersion>1.0.421</FileVersion>
+    <Version>1.0.421</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 16 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8RolePermissionConfig.cs

@@ -26,4 +26,20 @@ public class AdoS8RolePermissionConfig
 
     [SugarColumn(ColumnName = "updated_at", IsNullable = true)]
     public DateTime? UpdatedAt { get; set; }
+
+    /// <summary>
+    /// S8-STEP6B-CFG-ROLES-CLOSURE-1:作用域标识(不落库),与 AdoS8ExceptionType / AdoS8DashboardCellConfig 同构。
+    /// GLOBAL = 平台默认(租户侧只读);FACTORY = 当前工厂覆盖。
+    /// 前端据此渲染「配置来源」并禁用平台默认行的编辑 / 删除——按 S8ConfigScope 契约,
+    /// 「不允许在页面上给出最终只会 404 的编辑动作」。
+    /// </summary>
+    [SugarColumn(IsIgnore = true)]
+    public string Scope => Admin.NET.Plugin.AiDOP.Infrastructure.S8ConfigScope.Resolve(TenantId, FactoryId);
+
+    /// <summary>
+    /// S8-STEP6B-CFG-ROLES-CLOSURE-1:本行 role_code 是否存在平台默认行(不落库,由 ListAsync 赋值)。
+    /// true = 平台默认的工厂覆盖;false = 本工厂自建。
+    /// </summary>
+    [SugarColumn(IsIgnore = true)]
+    public bool HasGlobalDefault { get; set; }
 }

+ 38 - 4
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8RoleConfigService.cs

@@ -16,11 +16,42 @@ public class S8RoleConfigService : ITransient
         _sysRoleRep = sysRoleRep;
     }
 
-    public async Task<List<AdoS8RolePermissionConfig>> ListAsync(long tenantId, long factoryId) =>
-        await _rep.AsQueryable()
-            .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId)
+    /// <summary>
+    /// S8-STEP6B-CFG-ROLES-CLOSURE-1:改为「平台默认 (0,0) + 当前工厂覆盖」合并读取,
+    /// 与 S8ExceptionTypeService / S8DashboardCellConfigService / S8MonitoringService 的
+    /// S8-CONFIG-GLOBAL-ROW-SEMANTICS-AND-KPI-TARGET-1 范式一致(同 role_code 取工厂覆盖优先的唯一有效行)。
+    ///
+    /// 原实现只精确匹配 (tenantId, factoryId),而 S8RolePermissionSeedData 的 7 个业务角色种在 (0,0)
+    /// 且其类注释自述「tenant_id=0 / factory_id=0 表示全局默认」「作为异常类型配置的责任角色来源」——
+    /// 两者口径相反,导致任何真实租户下本接口恒返回 0 行。
+    /// 实测后果(UAT 租户 838257186181189):异常类型配置页把本接口当作「责任角色 / 升级角色」下拉词表
+    /// (S8ExceptionTypeConfigPage.vue:159),下拉恒为空;而同页 53 条异常类型中已有 48 条带 owner_role_code,
+    /// 即「值显示得出来、编辑时选不到」。
+    /// </summary>
+    public async Task<List<AdoS8RolePermissionConfig>> ListAsync(long tenantId, long factoryId)
+    {
+        var all = await _rep.AsQueryable()
+            .Where(x => (x.TenantId == S8ConfigScope.GlobalTenantId && x.FactoryId == S8ConfigScope.GlobalFactoryId)
+                     || (x.TenantId == tenantId && x.FactoryId == factoryId))
             .ToListAsync();
 
+        var globalCodes = all
+            .Where(x => S8ConfigScope.IsGlobal(x.TenantId, x.FactoryId))
+            .Select(x => x.RoleCode)
+            .ToHashSet();
+
+        var effective = all
+            .GroupBy(x => x.RoleCode)
+            .Select(g => g.OrderByDescending(x => x.FactoryId).First())
+            .OrderBy(x => x.RoleCode)
+            .ToList();
+
+        foreach (var row in effective)
+            row.HasGlobalDefault = globalCodes.Contains(row.RoleCode);
+
+        return effective;
+    }
+
     // S8-TENANT-FACTORY-P0-CLOSURE-1:归属一律由服务端可信作用域盖章,忽略 body.TenantId / body.FactoryId。
     public async Task<AdoS8RolePermissionConfig> CreateAsync(AdoS8RolePermissionConfig body, S8TrustedScope scope)
     {
@@ -32,7 +63,10 @@ public class S8RoleConfigService : ITransient
         if (exists) throw new S8BizException("角色编码已存在");
         body.Id = 0;
         body.CreatedAt = DateTime.Now;
-        await _rep.InsertAsync(body);
+        // S8-STEP6B-CFG-ROLES-CLOSURE-1:回填自增主键(与 CFG_DATASRC D-3 同源缺陷)。
+        // 原 InsertAsync 只返回 bool,body.Id 保持 0,调用方随后 GET/PUT/DELETE 一律 404。
+        // 采用仓内既有写法(同 S8ExceptionTypeService / S8DataSourceService)。
+        body.Id = await _rep.AsInsertable(body).ExecuteReturnBigIdentityAsync();
         return body;
     }