AidopTenantInitService.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using Admin.NET.Plugin.AiDOP.Entity;
  2. using Furion.Logging;
  3. using Admin.NET.Plugin.AiDOP.Entity.S8;
  4. using Admin.NET.Plugin.AiDOP.Service.S8.OrderFlow;
  5. using Yitter.IdGenerator;
  6. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  7. /// <summary>
  8. /// Ai-DOP 每租户必备基线:S8 七个业务角色 + KPI calc config(从 AIDOP 模板租户复制)。
  9. /// 形态为「每租户复制」,便于各租户独立调整 KPI 公式与角色配置。
  10. /// </summary>
  11. public sealed class AidopTenantInitService : ITransient
  12. {
  13. /// <summary>AIDOP 运行租户:KPI calc config 模板来源。</summary>
  14. public const long KpiTemplateTenantId = 797403760988229L;
  15. /// <summary>AIDOPDemo:S8 七模块场景模板来源,不含规则、数据源或凭据。</summary>
  16. public const long S8SceneTemplateTenantId = 797403760988230L;
  17. // S8-CFG-NOTIFY-NONDEFAULT-ROLE-OPTIONS-FIX-1:改引用 canonical 单一来源。
  18. // 原先此处与 S8SysRoleSeedData 各存一份逐字相同的字面量,靠人工同步;
  19. // role-options 若再复制第三份必然分叉,故统一收口到 S8CanonicalRole。内容未变。
  20. private static (string Code, string Name, int OrderNo)[] S8BusinessRoles
  21. => Admin.NET.Plugin.AiDOP.Infrastructure.S8.S8CanonicalRole.BusinessRoles;
  22. private readonly ISqlSugarClient _db;
  23. public AidopTenantInitService(ISqlSugarClient db) => _db = db;
  24. /// <summary>幂等种入目标租户的 S8 业务角色、KPI 主数据/布局与 calc config;允许重复调用。</summary>
  25. public async Task<AidopTenantBaselineResult> InitTenantBaselineAsync(long tenantId)
  26. {
  27. if (tenantId <= 0)
  28. return new AidopTenantBaselineResult();
  29. var s8Roles = await SeedS8BusinessRolesAsync(tenantId);
  30. var approvalRoles = await CountInsertedAsync(
  31. () => CountRolesAsync(tenantId, SmartOpsImprovementApprovalRules.BaselineRoles.Select(x => x.Code)),
  32. () => AidopImprovementApprovalSeed.EnsureBaselineRoles(_db, tenantId));
  33. var scenes = await SeedS8ScenesFromTemplateAsync(tenantId);
  34. var kpiMasters = await CountInsertedAsync(
  35. () => _db.Queryable<AdoSmartOpsKpiMaster>().ClearFilter<ITenantIdFilter>().CountAsync(x => x.TenantId == tenantId),
  36. () => AidopKpiMasterSeed.EnsureSeed(_db, tenantId, includeDemoValues: false));
  37. var kpiCalc = await SeedKpiCalcConfigFromTemplateAsync(tenantId);
  38. var result = new AidopTenantBaselineResult
  39. {
  40. S8Roles = s8Roles,
  41. ApprovalRoles = approvalRoles,
  42. Scenes = scenes,
  43. KpiMasters = kpiMasters,
  44. KpiCalcConfigs = kpiCalc,
  45. };
  46. Log.Information(
  47. "租户基线 tenant={TenantId} s8Roles={S8Roles} approvalRoles={ApprovalRoles} scenes={Scenes} kpiMasters={KpiMasters} kpiCalc={KpiCalc}",
  48. tenantId, result.S8Roles, result.ApprovalRoles, result.Scenes, result.KpiMasters, result.KpiCalcConfigs);
  49. return result;
  50. }
  51. private async Task<int> CountInsertedAsync(Func<Task<int>> count, Action seed)
  52. {
  53. var before = await count();
  54. seed();
  55. var after = await count();
  56. return Math.Max(0, after - before);
  57. }
  58. private async Task<int> SeedS8ScenesFromTemplateAsync(long tenantId)
  59. {
  60. if (tenantId == S8SceneTemplateTenantId) return 0;
  61. var factoryId = await _db.Queryable<SysTenant>()
  62. .ClearFilter<ITenantIdFilter>()
  63. .Where(x => x.Id == tenantId)
  64. .Select(x => x.OrgId)
  65. .FirstAsync();
  66. if (factoryId <= 0) return 0;
  67. var templates = await _db.Queryable<AdoS8SceneConfig>()
  68. .ClearFilter<ITenantIdFilter>()
  69. .Where(x => x.TenantId == S8SceneTemplateTenantId && x.Enabled)
  70. .OrderBy(x => x.SortNo)
  71. .ToListAsync();
  72. var now = DateTime.Now;
  73. var inserted = 0;
  74. foreach (var template in templates)
  75. {
  76. var exists = await _db.Queryable<AdoS8SceneConfig>()
  77. .ClearFilter<ITenantIdFilter>()
  78. .AnyAsync(x => x.TenantId == tenantId
  79. && x.FactoryId == factoryId
  80. && x.SceneCode == template.SceneCode);
  81. if (exists) continue;
  82. inserted++;
  83. await _db.Insertable(new AdoS8SceneConfig
  84. {
  85. TenantId = tenantId,
  86. FactoryId = factoryId,
  87. SceneCode = template.SceneCode,
  88. SceneName = template.SceneName,
  89. Enabled = true,
  90. SortNo = template.SortNo,
  91. CreatedAt = now,
  92. }).ExecuteCommandAsync();
  93. }
  94. return inserted;
  95. }
  96. private async Task<int> SeedS8BusinessRolesAsync(long tenantId)
  97. {
  98. var ct = DateTime.Now;
  99. var inserted = 0;
  100. foreach (var (code, name, orderNo) in S8BusinessRoles)
  101. {
  102. var exists = await _db.Queryable<SysRole>()
  103. .ClearFilter<ITenantIdFilter>()
  104. .AnyAsync(r => r.TenantId == tenantId && r.Code == code);
  105. if (exists) continue;
  106. inserted++;
  107. await _db.Insertable(new SysRole
  108. {
  109. Id = YitIdHelper.NextId(),
  110. TenantId = tenantId,
  111. Name = name,
  112. Code = code,
  113. OrderNo = orderNo,
  114. DataScope = DataScopeEnum.Self,
  115. Status = StatusEnum.Enable,
  116. Remark = "S8 业务角色(异常协同承接)",
  117. CreateTime = ct,
  118. }).ExecuteCommandAsync();
  119. }
  120. return inserted;
  121. }
  122. private async Task<int> SeedKpiCalcConfigFromTemplateAsync(long tenantId)
  123. {
  124. if (tenantId == KpiTemplateTenantId) return 0;
  125. var templates = await _db.Queryable<AdoSmartOpsKpiCalcConfig>()
  126. .ClearFilter<ITenantIdFilter>()
  127. .Where(c => c.TenantId == KpiTemplateTenantId)
  128. .ToListAsync();
  129. if (templates.Count == 0) return 0;
  130. var now = DateTime.Now;
  131. var inserted = 0;
  132. foreach (var tpl in templates)
  133. {
  134. var exists = await _db.Queryable<AdoSmartOpsKpiCalcConfig>()
  135. .ClearFilter<ITenantIdFilter>()
  136. .AnyAsync(c => c.TenantId == tenantId && c.MetricCode == tpl.MetricCode && c.VersionNo == tpl.VersionNo);
  137. if (exists) continue;
  138. inserted++;
  139. await _db.Insertable(new AdoSmartOpsKpiCalcConfig
  140. {
  141. TenantId = tenantId,
  142. MetricCode = tpl.MetricCode,
  143. ModuleCode = tpl.ModuleCode,
  144. VersionNo = tpl.VersionNo,
  145. CalcEngineType = tpl.CalcEngineType,
  146. DataSourceCode = tpl.DataSourceCode,
  147. SqlScript = tpl.SqlScript,
  148. SqlParametersJson = tpl.SqlParametersJson,
  149. TimeoutSeconds = tpl.TimeoutSeconds,
  150. PublishStatus = tpl.PublishStatus,
  151. IsCurrent = tpl.IsCurrent,
  152. Remark = tpl.Remark,
  153. CreatedBy = "AidopTenantInit",
  154. CreatedAt = now,
  155. }).ExecuteCommandAsync();
  156. }
  157. return inserted;
  158. }
  159. private Task<int> CountRolesAsync(long tenantId, IEnumerable<string> codes)
  160. {
  161. var list = codes.Where(c => !string.IsNullOrWhiteSpace(c)).Distinct().ToList();
  162. if (list.Count == 0) return Task.FromResult(0);
  163. return _db.Queryable<SysRole>()
  164. .ClearFilter<ITenantIdFilter>()
  165. .CountAsync(r => r.TenantId == tenantId && list.Contains(r.Code));
  166. }
  167. }
  168. /// <summary>一次基线执行实际新写入的条数。重复调用时各项为 0。</summary>
  169. public sealed class AidopTenantBaselineResult
  170. {
  171. public int S8Roles { get; init; }
  172. public int ApprovalRoles { get; init; }
  173. public int Scenes { get; init; }
  174. public int KpiMasters { get; init; }
  175. public int KpiCalcConfigs { get; init; }
  176. }