Sfoglia il codice sorgente

feat(kpi): pilot configurable dimension results for S7_L1_001

Add manual run trigger + dimension-detail read-only query on the dimension
config controller, completing the pilot loop for S7_L1_001 (order-level
发货周期). Runtime-verified end to end (tables created, lifecycle,
run/idempotency/version-mismatch/retire, detail, SUMMARY==DIMENSION recon).
Dashboard NOT switched, frontend NOT built, legacy Atomic NOT closed.

- POST run/{metricCode}: manual RunDimensionAsync trigger (MANUAL)
- GET detail/{metricCode}: paged dimension detail + AggregationType-recomputed
  aggregate value (server-side), current-version only, no atomic fallback
- QueryDetailAsync in service; detail DTOs
- bump version server 1.0.285
YY968XX 1 settimana fa
parent
commit
b3acd2f608

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET ͨ��Ȩ�޿���ƽ̨</Description>
-    <AssemblyVersion>1.0.284</AssemblyVersion>
-    <FileVersion>1.0.284</FileVersion>
-    <Version>1.0.284</Version>
+    <AssemblyVersion>1.0.285</AssemblyVersion>
+    <FileVersion>1.0.285</FileVersion>
+    <Version>1.0.285</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 31 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/AdoSmartOpsKpiDimensionConfigController.cs

@@ -19,11 +19,14 @@ namespace Admin.NET.Plugin.AiDOP.Controllers;
 public class AdoSmartOpsKpiDimensionConfigController : ControllerBase
 {
     private readonly AdoSmartOpsKpiDimensionConfigService _service;
+    private readonly KpiDimensionRunService _run;
     private readonly ISqlSugarClient _db;
 
-    public AdoSmartOpsKpiDimensionConfigController(AdoSmartOpsKpiDimensionConfigService service, ISqlSugarClient db)
+    public AdoSmartOpsKpiDimensionConfigController(
+        AdoSmartOpsKpiDimensionConfigService service, KpiDimensionRunService run, ISqlSugarClient db)
     {
         _service = service;
+        _run = run;
         _db = db;
     }
 
@@ -114,6 +117,33 @@ public class AdoSmartOpsKpiDimensionConfigController : ControllerBase
         return Ok(new { ok = true });
     }
 
+    /// <summary>手动触发一次维度执行(通用入口 RunDimensionAsync,TriggerType=MANUAL)。</summary>
+    [HttpPost("run/{metricCode}")]
+    public async Task<IActionResult> Run(
+        string metricCode, [FromQuery] string moduleCode, [FromQuery] DateTime? valueDate, CancellationToken ct)
+    {
+        if (string.IsNullOrWhiteSpace(metricCode) || string.IsNullOrWhiteSpace(moduleCode))
+            return BadRequest(new { message = "metricCode / moduleCode 必填" });
+        var tenantId = AdoSmartOpsKpiCalcConfigService.ResolveKpiTenantId(moduleCode);
+        var vd = (valueDate ?? DateTime.Today.AddDays(-1)).Date;
+        var batchId = $"DIM_MANUAL_{metricCode}_{vd:yyyyMMdd}_{DateTime.Now:HHmmssfff}";
+        var res = await _run.RunDimensionAsync(metricCode, moduleCode, tenantId, vd, batchId, "MANUAL", ct);
+        return Ok(res);
+    }
+
+    /// <summary>维度明细只读查询(当前生效维度版本;含按 AggregationType 复算的聚合值 + 分页明细)。</summary>
+    [HttpGet("detail/{metricCode}")]
+    public async Task<IActionResult> Detail(
+        string metricCode, [FromQuery] string moduleCode,
+        [FromQuery] DateTime? startDate, [FromQuery] DateTime? endDate,
+        [FromQuery] string? dimensionType, [FromQuery] string? dimensionCode,
+        [FromQuery] int page = 1, [FromQuery] int pageSize = 20)
+    {
+        if (string.IsNullOrWhiteSpace(metricCode) || string.IsNullOrWhiteSpace(moduleCode))
+            return BadRequest(new { message = "metricCode / moduleCode 必填" });
+        return Ok(await _service.QueryDetailAsync(metricCode, moduleCode, startDate, endDate, dimensionType, dimensionCode, page, pageSize));
+    }
+
     /// <summary>某 KPI 的维度运行日志(分页,脱敏,租户后端解析)。</summary>
     [HttpGet("run-log/{metricCode}")]
     public async Task<IActionResult> RunLog(

+ 31 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/SmartOps/KpiDimensionConfigDtos.cs

@@ -92,3 +92,34 @@ public sealed class KpiDimensionCapabilityDto
     public string? LastRunStatus { get; set; }
     public DateTime? LastRunAt { get; set; }
 }
+
+/// <summary>维度明细单行(只读查询)。</summary>
+public sealed class KpiDimensionDetailRowDto
+{
+    public string ValueDate { get; set; } = string.Empty;
+    public string DimensionType { get; set; } = string.Empty;
+    public string DimensionCode { get; set; } = string.Empty;
+    public string? DimensionName { get; set; }
+    public decimal? MetricValue { get; set; }
+    public decimal? Numerator { get; set; }
+    public decimal? Denominator { get; set; }
+    public decimal? SumValue { get; set; }
+    public int? SampleCount { get; set; }
+    public string? SourceKey { get; set; }
+    public string? BatchId { get; set; }
+}
+
+/// <summary>维度明细只读查询结果(含按 AggregationType 复算的聚合值 + 分页明细)。</summary>
+public sealed class KpiDimensionDetailResultDto
+{
+    public string MetricCode { get; set; } = string.Empty;
+    /// <summary>OK / NOT_CONFIGURED。</summary>
+    public string Status { get; set; } = "OK";
+    public int? SummaryConfigVersion { get; set; }
+    public int? DimensionConfigVersion { get; set; }
+    public string? AggregationType { get; set; }
+    /// <summary>当前筛选范围按 AggregationType 复算的聚合值(后端算,前端只展示)。</summary>
+    public decimal? AggregateValue { get; set; }
+    public int Total { get; set; }
+    public List<KpiDimensionDetailRowDto> List { get; set; } = new();
+}

+ 63 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/SmartOps/AdoSmartOpsKpiDimensionConfigService.cs

@@ -295,6 +295,69 @@ public sealed class AdoSmartOpsKpiDimensionConfigService : ITransient
         return cap;
     }
 
+    /// <summary>
+    /// 维度明细只读查询(当前生效维度版本):按 AggregationType 复算聚合值 + 分页明细。
+    /// 只读当前激活配置产生的维度结果,绝不读旧 Atomic。无激活配置 → NOT_CONFIGURED。
+    /// </summary>
+    public async Task<KpiDimensionDetailResultDto> QueryDetailAsync(
+        string metricCode, string moduleCode, DateTime? startDate, DateTime? endDate,
+        string? dimensionType, string? dimensionCode, int page, int pageSize)
+    {
+        var tenantId = AdoSmartOpsKpiCalcConfigService.ResolveKpiTenantId(moduleCode);
+        var active = await GetActiveAsync(tenantId, metricCode);
+        var result = new KpiDimensionDetailResultDto { MetricCode = metricCode };
+        if (active == null)
+        {
+            result.Status = "NOT_CONFIGURED";
+            return result;
+        }
+        result.SummaryConfigVersion = active.SummaryConfigVersion;
+        result.DimensionConfigVersion = active.DimensionConfigVersion;
+        result.AggregationType = active.AggregationType;
+
+        var sd = startDate?.Date;
+        var ed = endDate?.Date;
+        ISugarQueryable<AdoSmartOpsKpiDimensionValueDay> Build() =>
+            _db.Queryable<AdoSmartOpsKpiDimensionValueDay>().ClearFilter<ITenantIdFilter>()
+                .Where(x => x.TenantId == tenantId && x.MetricCode == metricCode
+                            && x.DimensionConfigVersion == active.DimensionConfigVersion)
+                .WhereIF(sd.HasValue, x => x.ValueDate >= sd!.Value)
+                .WhereIF(ed.HasValue, x => x.ValueDate <= ed!.Value)
+                .WhereIF(!string.IsNullOrWhiteSpace(dimensionType), x => x.DimensionType == dimensionType)
+                .WhereIF(!string.IsNullOrWhiteSpace(dimensionCode), x => x.DimensionCode == dimensionCode);
+
+        result.AggregateValue = active.AggregationType switch
+        {
+            "AVERAGE_OF_SUMS" => Div(await Build().SumAsync(x => x.SumValue), (decimal?)await Build().SumAsync(x => x.SampleCount)),
+            "RATIO_OF_SUMS" => Div(await Build().SumAsync(x => x.Numerator), await Build().SumAsync(x => x.Denominator)),
+            "DIRECT_VALUE" => await Build().AvgAsync(x => x.MetricValue),
+            _ => null,
+        };
+
+        RefAsync<int> total = 0;
+        var list = await Build().OrderBy(x => x.ValueDate, OrderByType.Desc).OrderBy(x => x.DimensionCode)
+            .ToPageListAsync(page <= 0 ? 1 : page, pageSize <= 0 ? 20 : pageSize, total);
+        result.Total = total.Value;
+        result.List = list.Select(x => new KpiDimensionDetailRowDto
+        {
+            ValueDate = x.ValueDate.ToString("yyyy-MM-dd"),
+            DimensionType = x.DimensionType,
+            DimensionCode = x.DimensionCode,
+            DimensionName = x.DimensionName,
+            MetricValue = x.MetricValue,
+            Numerator = x.Numerator,
+            Denominator = x.Denominator,
+            SumValue = x.SumValue,
+            SampleCount = x.SampleCount,
+            SourceKey = x.SourceKey,
+            BatchId = x.BatchId,
+        }).ToList();
+        return result;
+    }
+
+    private static decimal? Div(decimal? a, decimal? b) =>
+        b.HasValue && b.Value != 0 ? (a ?? 0m) / b.Value : null;
+
     /// <summary>已登记数据源列表(第一版仅本地中台库)。</summary>
     public List<object> ListDataSources() => new()
     {