AdoSmartOpsKpiBusinessInputService.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. using System.Text.RegularExpressions;
  2. using Admin.NET.Core;
  3. using Admin.NET.Plugin.AiDOP.Dto.SmartOps;
  4. using Admin.NET.Plugin.AiDOP.Entity;
  5. using SqlSugar;
  6. namespace Admin.NET.Plugin.AiDOP.SmartOps;
  7. /// <summary>
  8. /// KPI 业务来源与准备状态服务(KPI 级)。缺行时返回 IsPersisted=false 的默认 PENDING_BUSINESS_FIELDS,
  9. /// 不隐式建行;置 READY_FOR_CONFIG 时服务端强校验(来源系统/表/字段/SQL来源 + 对应 CONFIG_SQL 草稿含 SqlText/数据源)。
  10. /// ModuleCode / TenantId 一律由 MetricCode 解析,不信任前端(避免错误 ModuleCode 造成重复口径)。
  11. /// </summary>
  12. public sealed class AdoSmartOpsKpiBusinessInputService : ITransient
  13. {
  14. public const string StatusPendingFields = "PENDING_BUSINESS_FIELDS";
  15. public const string StatusPendingSql = "PENDING_BUSINESS_SQL";
  16. public const string StatusReady = "READY_FOR_CONFIG";
  17. private const string EngineConfigSql = "CONFIG_SQL";
  18. private static readonly string[] AllStatuses = { StatusPendingFields, StatusPendingSql, StatusReady };
  19. private static readonly Regex ModuleRe = new(@"^S[1-9]$", RegexOptions.Compiled);
  20. private readonly ISqlSugarClient _db;
  21. public AdoSmartOpsKpiBusinessInputService(ISqlSugarClient db)
  22. {
  23. _db = db;
  24. }
  25. /// <summary>从 MetricCode 解析模块(Sx_Ly_nnn → Sx);非法前缀回落 metricCode 首段大写。</summary>
  26. public static string ResolveModuleCode(string metricCode)
  27. {
  28. var prefix = (metricCode ?? string.Empty).Split('_')[0].Trim().ToUpperInvariant();
  29. return ModuleRe.IsMatch(prefix) ? prefix : prefix;
  30. }
  31. private ISugarQueryable<AdoSmartOpsKpiBusinessInput> Query() =>
  32. _db.Queryable<AdoSmartOpsKpiBusinessInput>().ClearFilter<ITenantIdFilter>();
  33. /// <summary>取业务来源登记;库中无记录时返回默认 PENDING_BUSINESS_FIELDS(IsPersisted=false),不建行。</summary>
  34. public async Task<KpiBusinessInputDto> GetAsync(string metricCode)
  35. {
  36. var moduleCode = ResolveModuleCode(metricCode);
  37. var tenantId = AdoSmartOpsKpiCalcConfigService.ResolveKpiTenantId(moduleCode);
  38. var e = await Query().Where(x => x.MetricCode == metricCode && x.TenantId == tenantId).FirstAsync();
  39. if (e == null)
  40. return new KpiBusinessInputDto
  41. {
  42. IsPersisted = false,
  43. TenantId = tenantId,
  44. MetricCode = metricCode,
  45. ModuleCode = moduleCode,
  46. BusinessInputStatus = StatusPendingFields,
  47. };
  48. return ToDto(e);
  49. }
  50. /// <summary>门禁用:取业务准备状态;无记录返回 PENDING_BUSINESS_FIELDS。</summary>
  51. public async Task<string> GetStatusAsync(long tenantId, string metricCode)
  52. {
  53. var e = await Query().Where(x => x.MetricCode == metricCode && x.TenantId == tenantId).FirstAsync();
  54. return string.IsNullOrWhiteSpace(e?.BusinessInputStatus) ? StatusPendingFields : e!.BusinessInputStatus;
  55. }
  56. /// <summary>保存业务来源登记(upsert)。置 READY 前强校验;ModuleCode/TenantId 后端解析。</summary>
  57. public async Task<KpiBusinessInputDto> UpsertAsync(KpiBusinessInputUpsertDto dto, string operatorName)
  58. {
  59. if (string.IsNullOrWhiteSpace(dto.MetricCode))
  60. throw Oops.Bah("metricCode 必填");
  61. var status = (dto.BusinessInputStatus ?? string.Empty).Trim().ToUpperInvariant();
  62. if (!AllStatuses.Contains(status))
  63. throw Oops.Bah($"非法业务准备状态:{dto.BusinessInputStatus}(PENDING_BUSINESS_FIELDS/PENDING_BUSINESS_SQL/READY_FOR_CONFIG)");
  64. var moduleCode = ResolveModuleCode(dto.MetricCode);
  65. var tenantId = AdoSmartOpsKpiCalcConfigService.ResolveKpiTenantId(moduleCode);
  66. if (status == StatusReady)
  67. await EnsureReadyRequirementsAsync(tenantId, dto);
  68. var existing = await Query().Where(x => x.MetricCode == dto.MetricCode && x.TenantId == tenantId).FirstAsync();
  69. var now = DateTime.Now;
  70. if (existing == null)
  71. {
  72. var entity = new AdoSmartOpsKpiBusinessInput
  73. {
  74. TenantId = tenantId,
  75. MetricCode = dto.MetricCode,
  76. ModuleCode = moduleCode,
  77. BusinessInputStatus = status,
  78. SourceSystem = Trim(dto.SourceSystem),
  79. SourceTables = Trim(dto.SourceTables),
  80. SourceFields = Trim(dto.SourceFields),
  81. BusinessSqlSource = Trim(dto.BusinessSqlSource),
  82. BusinessSqlRemark = Trim(dto.BusinessSqlRemark),
  83. BusinessOwner = Trim(dto.BusinessOwner),
  84. FieldOwner = Trim(dto.FieldOwner),
  85. Remark = Trim(dto.Remark),
  86. CreatedBy = operatorName,
  87. CreatedAt = now,
  88. };
  89. entity.Id = await _db.Insertable(entity).ExecuteReturnBigIdentityAsync();
  90. return ToDto(entity);
  91. }
  92. existing.ModuleCode = moduleCode;
  93. existing.BusinessInputStatus = status;
  94. existing.SourceSystem = Trim(dto.SourceSystem);
  95. existing.SourceTables = Trim(dto.SourceTables);
  96. existing.SourceFields = Trim(dto.SourceFields);
  97. existing.BusinessSqlSource = Trim(dto.BusinessSqlSource);
  98. existing.BusinessSqlRemark = Trim(dto.BusinessSqlRemark);
  99. existing.BusinessOwner = Trim(dto.BusinessOwner);
  100. existing.FieldOwner = Trim(dto.FieldOwner);
  101. existing.Remark = Trim(dto.Remark);
  102. existing.UpdatedBy = operatorName;
  103. existing.UpdatedAt = now;
  104. await _db.Updateable(existing).ExecuteCommandAsync();
  105. return ToDto(existing);
  106. }
  107. /// <summary>READY_FOR_CONFIG 前置:业务字段齐 + 存在含 SqlText/数据源的 CONFIG_SQL 版本。缺项明确报出。</summary>
  108. private async Task EnsureReadyRequirementsAsync(long tenantId, KpiBusinessInputUpsertDto dto)
  109. {
  110. var missing = new List<string>();
  111. if (string.IsNullOrWhiteSpace(dto.SourceSystem)) missing.Add("来源系统");
  112. if (string.IsNullOrWhiteSpace(dto.SourceTables)) missing.Add("来源表");
  113. if (string.IsNullOrWhiteSpace(dto.SourceFields)) missing.Add("来源字段");
  114. if (string.IsNullOrWhiteSpace(dto.BusinessSqlSource)) missing.Add("SQL 来源");
  115. var hasConfigSql = await _db.Queryable<AdoSmartOpsKpiCalcConfig>().ClearFilter<ITenantIdFilter>()
  116. .Where(x => x.MetricCode == dto.MetricCode && x.TenantId == tenantId
  117. && x.CalcEngineType == EngineConfigSql
  118. && x.SqlScript != null && x.SqlScript != ""
  119. && x.DataSourceCode != null && x.DataSourceCode != "")
  120. .AnyAsync();
  121. if (!hasConfigSql) missing.Add("对应 CONFIG_SQL 配置(含 SqlText + 数据源)");
  122. if (missing.Count > 0)
  123. throw Oops.Bah($"不能置为 READY_FOR_CONFIG,尚缺:{string.Join("、", missing)}");
  124. }
  125. private static string? Trim(string? s) => string.IsNullOrWhiteSpace(s) ? null : s.Trim();
  126. private static KpiBusinessInputDto ToDto(AdoSmartOpsKpiBusinessInput e) => new()
  127. {
  128. IsPersisted = true,
  129. TenantId = e.TenantId,
  130. MetricCode = e.MetricCode,
  131. ModuleCode = e.ModuleCode,
  132. BusinessInputStatus = e.BusinessInputStatus,
  133. SourceSystem = e.SourceSystem,
  134. SourceTables = e.SourceTables,
  135. SourceFields = e.SourceFields,
  136. BusinessSqlSource = e.BusinessSqlSource,
  137. BusinessSqlRemark = e.BusinessSqlRemark,
  138. BusinessOwner = e.BusinessOwner,
  139. FieldOwner = e.FieldOwner,
  140. Remark = e.Remark,
  141. UpdatedBy = e.UpdatedBy,
  142. UpdatedAt = e.UpdatedAt,
  143. };
  144. }