using Admin.NET.Plugin.AiDOP.Entity.S8; using Admin.NET.Plugin.AiDOP.Infrastructure; namespace Admin.NET.Plugin.AiDOP.Service.S8; public class S8RoleConfigService : ITransient { private readonly SqlSugarRepository _rep; private readonly SqlSugarRepository _sysRoleRep; public S8RoleConfigService( SqlSugarRepository rep, SqlSugarRepository sysRoleRep) { _rep = rep; _sysRoleRep = sysRoleRep; } /// /// 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, /// 即「值显示得出来、编辑时选不到」。 /// public async Task> 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 CreateAsync(AdoS8RolePermissionConfig body, S8TrustedScope scope) { if (string.IsNullOrWhiteSpace(body.RoleCode)) throw new S8BizException("角色编码必填"); body.TenantId = scope.TenantId; body.FactoryId = scope.FactoryId; var exists = await _rep.AsQueryable() .AnyAsync(x => x.TenantId == body.TenantId && x.FactoryId == body.FactoryId && x.RoleCode == body.RoleCode); if (exists) throw new S8BizException("角色编码已存在"); body.Id = 0; body.CreatedAt = DateTime.Now; // 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; } // S8-TENANT-FACTORY-P0-CLOSURE-1:按 Id + 可信作用域绑行;越权 Id 视为不存在,归属不可被 body 改写。 public async Task UpdateAsync(long id, AdoS8RolePermissionConfig body, S8TrustedScope scope) { var e = await LoadScopedAsync(id, scope); if (string.IsNullOrWhiteSpace(body.RoleCode)) throw new S8BizException("角色编码必填"); var exists = await _rep.AsQueryable() .AnyAsync(x => x.Id != id && x.TenantId == e.TenantId && x.FactoryId == e.FactoryId && x.RoleCode == body.RoleCode); if (exists) throw new S8BizException("角色编码已存在"); body.Id = id; body.TenantId = e.TenantId; body.FactoryId = e.FactoryId; body.CreatedAt = e.CreatedAt; body.UpdatedAt = DateTime.Now; await _rep.UpdateAsync(body); return body; } // S8-TENANT-FACTORY-P0-CLOSURE-1:删除必须先按可信作用域绑行,禁止裸 DeleteByIdAsync(id)。 public async Task DeleteAsync(long id, S8TrustedScope scope) { var e = await LoadScopedAsync(id, scope); await _rep.DeleteByIdAsync(e.Id); } private async Task LoadScopedAsync(long id, S8TrustedScope scope) => await _rep.AsQueryable() .Where(x => x.Id == id && x.TenantId == scope.TenantId && x.FactoryId == scope.FactoryId) .FirstAsync() ?? throw new S8NotFoundException(); /// /// 从系统角色表一键导入,跳过已存在的角色编码,返回本次新增条数。 /// public async Task ImportFromSysRolesAsync(long tenantId, long factoryId) { var sysRoles = await _sysRoleRep.AsQueryable() .Where(r => r.TenantId == tenantId && r.Status == StatusEnum.Enable) .Where(r => !string.IsNullOrEmpty(r.Code)) .Select(r => new { r.Code, r.Name }) .ToListAsync(); var existingCodes = (await _rep.AsQueryable() .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId) .Select(x => x.RoleCode) .ToListAsync()).ToHashSet(); var toInsert = sysRoles .Where(r => !existingCodes.Contains(r.Code!)) .Select(r => new AdoS8RolePermissionConfig { TenantId = tenantId, FactoryId = factoryId, RoleCode = r.Code!, PermissionCodes = "[]", CreatedAt = DateTime.Now }).ToList(); if (toInsert.Count > 0) await _rep.InsertRangeAsync(toInsert); return toInsert.Count; } }