S8RoleConfigService.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. using Admin.NET.Plugin.AiDOP.Entity.S8;
  2. using Admin.NET.Plugin.AiDOP.Infrastructure;
  3. namespace Admin.NET.Plugin.AiDOP.Service.S8;
  4. public class S8RoleConfigService : ITransient
  5. {
  6. private readonly SqlSugarRepository<AdoS8RolePermissionConfig> _rep;
  7. private readonly SqlSugarRepository<SysRole> _sysRoleRep;
  8. public S8RoleConfigService(
  9. SqlSugarRepository<AdoS8RolePermissionConfig> rep,
  10. SqlSugarRepository<SysRole> sysRoleRep)
  11. {
  12. _rep = rep;
  13. _sysRoleRep = sysRoleRep;
  14. }
  15. /// <summary>
  16. /// S8-STEP6B-CFG-ROLES-CLOSURE-1:改为「平台默认 (0,0) + 当前工厂覆盖」合并读取,
  17. /// 与 S8ExceptionTypeService / S8DashboardCellConfigService / S8MonitoringService 的
  18. /// S8-CONFIG-GLOBAL-ROW-SEMANTICS-AND-KPI-TARGET-1 范式一致(同 role_code 取工厂覆盖优先的唯一有效行)。
  19. ///
  20. /// 原实现只精确匹配 (tenantId, factoryId),而 S8RolePermissionSeedData 的 7 个业务角色种在 (0,0)
  21. /// 且其类注释自述「tenant_id=0 / factory_id=0 表示全局默认」「作为异常类型配置的责任角色来源」——
  22. /// 两者口径相反,导致任何真实租户下本接口恒返回 0 行。
  23. /// 实测后果(UAT 租户 838257186181189):异常类型配置页把本接口当作「责任角色 / 升级角色」下拉词表
  24. /// (S8ExceptionTypeConfigPage.vue:159),下拉恒为空;而同页 53 条异常类型中已有 48 条带 owner_role_code,
  25. /// 即「值显示得出来、编辑时选不到」。
  26. /// </summary>
  27. public async Task<List<AdoS8RolePermissionConfig>> ListAsync(long tenantId, long factoryId)
  28. {
  29. var all = await _rep.AsQueryable()
  30. .Where(x => (x.TenantId == S8ConfigScope.GlobalTenantId && x.FactoryId == S8ConfigScope.GlobalFactoryId)
  31. || (x.TenantId == tenantId && x.FactoryId == factoryId))
  32. .ToListAsync();
  33. var globalCodes = all
  34. .Where(x => S8ConfigScope.IsGlobal(x.TenantId, x.FactoryId))
  35. .Select(x => x.RoleCode)
  36. .ToHashSet();
  37. var effective = all
  38. .GroupBy(x => x.RoleCode)
  39. .Select(g => g.OrderByDescending(x => x.FactoryId).First())
  40. .OrderBy(x => x.RoleCode)
  41. .ToList();
  42. foreach (var row in effective)
  43. row.HasGlobalDefault = globalCodes.Contains(row.RoleCode);
  44. return effective;
  45. }
  46. // S8-TENANT-FACTORY-P0-CLOSURE-1:归属一律由服务端可信作用域盖章,忽略 body.TenantId / body.FactoryId。
  47. public async Task<AdoS8RolePermissionConfig> CreateAsync(AdoS8RolePermissionConfig body, S8TrustedScope scope)
  48. {
  49. if (string.IsNullOrWhiteSpace(body.RoleCode)) throw new S8BizException("角色编码必填");
  50. body.TenantId = scope.TenantId;
  51. body.FactoryId = scope.FactoryId;
  52. var exists = await _rep.AsQueryable()
  53. .AnyAsync(x => x.TenantId == body.TenantId && x.FactoryId == body.FactoryId && x.RoleCode == body.RoleCode);
  54. if (exists) throw new S8BizException("角色编码已存在");
  55. body.Id = 0;
  56. body.CreatedAt = DateTime.Now;
  57. // S8-STEP6B-CFG-ROLES-CLOSURE-1:回填自增主键(与 CFG_DATASRC D-3 同源缺陷)。
  58. // 原 InsertAsync 只返回 bool,body.Id 保持 0,调用方随后 GET/PUT/DELETE 一律 404。
  59. // 采用仓内既有写法(同 S8ExceptionTypeService / S8DataSourceService)。
  60. body.Id = await _rep.AsInsertable(body).ExecuteReturnBigIdentityAsync();
  61. return body;
  62. }
  63. // S8-TENANT-FACTORY-P0-CLOSURE-1:按 Id + 可信作用域绑行;越权 Id 视为不存在,归属不可被 body 改写。
  64. public async Task<AdoS8RolePermissionConfig> UpdateAsync(long id, AdoS8RolePermissionConfig body, S8TrustedScope scope)
  65. {
  66. var e = await LoadScopedAsync(id, scope);
  67. if (string.IsNullOrWhiteSpace(body.RoleCode)) throw new S8BizException("角色编码必填");
  68. var exists = await _rep.AsQueryable()
  69. .AnyAsync(x => x.Id != id && x.TenantId == e.TenantId && x.FactoryId == e.FactoryId && x.RoleCode == body.RoleCode);
  70. if (exists) throw new S8BizException("角色编码已存在");
  71. body.Id = id;
  72. body.TenantId = e.TenantId;
  73. body.FactoryId = e.FactoryId;
  74. body.CreatedAt = e.CreatedAt;
  75. body.UpdatedAt = DateTime.Now;
  76. await _rep.UpdateAsync(body);
  77. return body;
  78. }
  79. // S8-TENANT-FACTORY-P0-CLOSURE-1:删除必须先按可信作用域绑行,禁止裸 DeleteByIdAsync(id)。
  80. public async Task DeleteAsync(long id, S8TrustedScope scope)
  81. {
  82. var e = await LoadScopedAsync(id, scope);
  83. await _rep.DeleteByIdAsync(e.Id);
  84. }
  85. private async Task<AdoS8RolePermissionConfig> LoadScopedAsync(long id, S8TrustedScope scope) =>
  86. await _rep.AsQueryable()
  87. .Where(x => x.Id == id && x.TenantId == scope.TenantId && x.FactoryId == scope.FactoryId)
  88. .FirstAsync() ?? throw new S8NotFoundException();
  89. /// <summary>
  90. /// 从系统角色表一键导入,跳过已存在的角色编码,返回本次新增条数。
  91. /// </summary>
  92. public async Task<int> ImportFromSysRolesAsync(long tenantId, long factoryId)
  93. {
  94. var sysRoles = await _sysRoleRep.AsQueryable()
  95. .Where(r => r.TenantId == tenantId && r.Status == StatusEnum.Enable)
  96. .Where(r => !string.IsNullOrEmpty(r.Code))
  97. .Select(r => new { r.Code, r.Name })
  98. .ToListAsync();
  99. var existingCodes = (await _rep.AsQueryable()
  100. .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId)
  101. .Select(x => x.RoleCode)
  102. .ToListAsync()).ToHashSet();
  103. var toInsert = sysRoles
  104. .Where(r => !existingCodes.Contains(r.Code!))
  105. .Select(r => new AdoS8RolePermissionConfig
  106. {
  107. TenantId = tenantId,
  108. FactoryId = factoryId,
  109. RoleCode = r.Code!,
  110. PermissionCodes = "[]",
  111. CreatedAt = DateTime.Now
  112. }).ToList();
  113. if (toInsert.Count > 0)
  114. await _rep.InsertRangeAsync(toInsert);
  115. return toInsert.Count;
  116. }
  117. }