| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- using System.Text.RegularExpressions;
- using Admin.NET.Core;
- using Admin.NET.Plugin.AiDOP.Dto.SmartOps;
- using Admin.NET.Plugin.AiDOP.Entity;
- using SqlSugar;
- namespace Admin.NET.Plugin.AiDOP.SmartOps;
- /// <summary>
- /// KPI 业务来源与准备状态服务(KPI 级)。缺行时返回 IsPersisted=false 的默认 PENDING_BUSINESS_FIELDS,
- /// 不隐式建行;置 READY_FOR_CONFIG 时服务端强校验(来源系统/表/字段/SQL来源 + 对应 CONFIG_SQL 草稿含 SqlText/数据源)。
- /// ModuleCode / TenantId 一律由 MetricCode 解析,不信任前端(避免错误 ModuleCode 造成重复口径)。
- /// </summary>
- public sealed class AdoSmartOpsKpiBusinessInputService : ITransient
- {
- public const string StatusPendingFields = "PENDING_BUSINESS_FIELDS";
- public const string StatusPendingSql = "PENDING_BUSINESS_SQL";
- public const string StatusReady = "READY_FOR_CONFIG";
- private const string EngineConfigSql = "CONFIG_SQL";
- private static readonly string[] AllStatuses = { StatusPendingFields, StatusPendingSql, StatusReady };
- private static readonly Regex ModuleRe = new(@"^S[1-9]$", RegexOptions.Compiled);
- private readonly ISqlSugarClient _db;
- public AdoSmartOpsKpiBusinessInputService(ISqlSugarClient db)
- {
- _db = db;
- }
- /// <summary>从 MetricCode 解析模块(Sx_Ly_nnn → Sx);非法前缀回落 metricCode 首段大写。</summary>
- public static string ResolveModuleCode(string metricCode)
- {
- var prefix = (metricCode ?? string.Empty).Split('_')[0].Trim().ToUpperInvariant();
- return ModuleRe.IsMatch(prefix) ? prefix : prefix;
- }
- private ISugarQueryable<AdoSmartOpsKpiBusinessInput> Query() =>
- _db.Queryable<AdoSmartOpsKpiBusinessInput>().ClearFilter<ITenantIdFilter>();
- /// <summary>取业务来源登记;库中无记录时返回默认 PENDING_BUSINESS_FIELDS(IsPersisted=false),不建行。</summary>
- public async Task<KpiBusinessInputDto> GetAsync(string metricCode)
- {
- var moduleCode = ResolveModuleCode(metricCode);
- var tenantId = AdoSmartOpsKpiCalcConfigService.ResolveKpiTenantId(moduleCode);
- var e = await Query().Where(x => x.MetricCode == metricCode && x.TenantId == tenantId).FirstAsync();
- if (e == null)
- return new KpiBusinessInputDto
- {
- IsPersisted = false,
- TenantId = tenantId,
- MetricCode = metricCode,
- ModuleCode = moduleCode,
- BusinessInputStatus = StatusPendingFields,
- };
- return ToDto(e);
- }
- /// <summary>门禁用:取业务准备状态;无记录返回 PENDING_BUSINESS_FIELDS。</summary>
- public async Task<string> GetStatusAsync(long tenantId, string metricCode)
- {
- var e = await Query().Where(x => x.MetricCode == metricCode && x.TenantId == tenantId).FirstAsync();
- return string.IsNullOrWhiteSpace(e?.BusinessInputStatus) ? StatusPendingFields : e!.BusinessInputStatus;
- }
- /// <summary>保存业务来源登记(upsert)。置 READY 前强校验;ModuleCode/TenantId 后端解析。</summary>
- public async Task<KpiBusinessInputDto> UpsertAsync(KpiBusinessInputUpsertDto dto, string operatorName)
- {
- if (string.IsNullOrWhiteSpace(dto.MetricCode))
- throw Oops.Bah("metricCode 必填");
- var status = (dto.BusinessInputStatus ?? string.Empty).Trim().ToUpperInvariant();
- if (!AllStatuses.Contains(status))
- throw Oops.Bah($"非法业务准备状态:{dto.BusinessInputStatus}(PENDING_BUSINESS_FIELDS/PENDING_BUSINESS_SQL/READY_FOR_CONFIG)");
- var moduleCode = ResolveModuleCode(dto.MetricCode);
- var tenantId = AdoSmartOpsKpiCalcConfigService.ResolveKpiTenantId(moduleCode);
- if (status == StatusReady)
- await EnsureReadyRequirementsAsync(tenantId, dto);
- var existing = await Query().Where(x => x.MetricCode == dto.MetricCode && x.TenantId == tenantId).FirstAsync();
- var now = DateTime.Now;
- if (existing == null)
- {
- var entity = new AdoSmartOpsKpiBusinessInput
- {
- TenantId = tenantId,
- MetricCode = dto.MetricCode,
- ModuleCode = moduleCode,
- BusinessInputStatus = status,
- SourceSystem = Trim(dto.SourceSystem),
- SourceTables = Trim(dto.SourceTables),
- SourceFields = Trim(dto.SourceFields),
- BusinessSqlSource = Trim(dto.BusinessSqlSource),
- BusinessSqlRemark = Trim(dto.BusinessSqlRemark),
- BusinessOwner = Trim(dto.BusinessOwner),
- FieldOwner = Trim(dto.FieldOwner),
- Remark = Trim(dto.Remark),
- CreatedBy = operatorName,
- CreatedAt = now,
- };
- entity.Id = await _db.Insertable(entity).ExecuteReturnBigIdentityAsync();
- return ToDto(entity);
- }
- existing.ModuleCode = moduleCode;
- existing.BusinessInputStatus = status;
- existing.SourceSystem = Trim(dto.SourceSystem);
- existing.SourceTables = Trim(dto.SourceTables);
- existing.SourceFields = Trim(dto.SourceFields);
- existing.BusinessSqlSource = Trim(dto.BusinessSqlSource);
- existing.BusinessSqlRemark = Trim(dto.BusinessSqlRemark);
- existing.BusinessOwner = Trim(dto.BusinessOwner);
- existing.FieldOwner = Trim(dto.FieldOwner);
- existing.Remark = Trim(dto.Remark);
- existing.UpdatedBy = operatorName;
- existing.UpdatedAt = now;
- await _db.Updateable(existing).ExecuteCommandAsync();
- return ToDto(existing);
- }
- /// <summary>READY_FOR_CONFIG 前置:业务字段齐 + 存在含 SqlText/数据源的 CONFIG_SQL 版本。缺项明确报出。</summary>
- private async Task EnsureReadyRequirementsAsync(long tenantId, KpiBusinessInputUpsertDto dto)
- {
- var missing = new List<string>();
- if (string.IsNullOrWhiteSpace(dto.SourceSystem)) missing.Add("来源系统");
- if (string.IsNullOrWhiteSpace(dto.SourceTables)) missing.Add("来源表");
- if (string.IsNullOrWhiteSpace(dto.SourceFields)) missing.Add("来源字段");
- if (string.IsNullOrWhiteSpace(dto.BusinessSqlSource)) missing.Add("SQL 来源");
- var hasConfigSql = await _db.Queryable<AdoSmartOpsKpiCalcConfig>().ClearFilter<ITenantIdFilter>()
- .Where(x => x.MetricCode == dto.MetricCode && x.TenantId == tenantId
- && x.CalcEngineType == EngineConfigSql
- && x.SqlScript != null && x.SqlScript != ""
- && x.DataSourceCode != null && x.DataSourceCode != "")
- .AnyAsync();
- if (!hasConfigSql) missing.Add("对应 CONFIG_SQL 配置(含 SqlText + 数据源)");
- if (missing.Count > 0)
- throw Oops.Bah($"不能置为 READY_FOR_CONFIG,尚缺:{string.Join("、", missing)}");
- }
- private static string? Trim(string? s) => string.IsNullOrWhiteSpace(s) ? null : s.Trim();
- private static KpiBusinessInputDto ToDto(AdoSmartOpsKpiBusinessInput e) => new()
- {
- IsPersisted = true,
- TenantId = e.TenantId,
- MetricCode = e.MetricCode,
- ModuleCode = e.ModuleCode,
- BusinessInputStatus = e.BusinessInputStatus,
- SourceSystem = e.SourceSystem,
- SourceTables = e.SourceTables,
- SourceFields = e.SourceFields,
- BusinessSqlSource = e.BusinessSqlSource,
- BusinessSqlRemark = e.BusinessSqlRemark,
- BusinessOwner = e.BusinessOwner,
- FieldOwner = e.FieldOwner,
- Remark = e.Remark,
- UpdatedBy = e.UpdatedBy,
- UpdatedAt = e.UpdatedAt,
- };
- }
|