Kaynağa Gözat

feat: S5/S6/S7 五个 FULLY KPI 配置化维度筛选下钻闭环 (server 1.0.290)

复用现有维度框架为 S5_L1_001/002/004、S6_L2_002、S7_L1_002 落地配置化筛选下钻:
- 1.0.290.sql:5 个维度配置幂等固化,DIMENSION_SQL 与各自 SUMMARY 同源同 WHERE + GROUP BY 维度键,动态绑定当前生效汇总版本,不写死 Id。
- AidopKanbanController.SmartOpsFilter.cs:纳管集新增 5 码,有筛选时改读维度结果,NOT_CONFIGURED/VERSION_MISMATCH/NO_DATA 均不回落旧 Atomic。
- KpiDimensionDashboardService.cs:Build 增加 material/workOrder 维度筛选(落 DimensionCode)。
- S5/S6/S7 转换服务:注入 KpiDimensionRunService,SUMMARY 成功后触发对应 DIMENSION 跑批(共享 BatchId,SUMMARY FAILED 不触发,维度失败不影响汇总链路)。
无 per-KPI C#,不改业务公式;人效月度 3 KPI 与配置面板本批不做。
YY968XX 1 hafta önce
ebeveyn
işleme
a5ca8ffec9

+ 6 - 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.289</AssemblyVersion>
-    <FileVersion>1.0.289</FileVersion>
-    <Version>1.0.289</Version>
+    <AssemblyVersion>1.0.290</AssemblyVersion>
+    <FileVersion>1.0.290</FileVersion>
+    <Version>1.0.290</Version>
   </PropertyGroup>
 
   <ItemGroup>
@@ -268,6 +268,9 @@
     <None Update="UpdateScripts\1.0.289.sql">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </None>
+    <None Update="UpdateScripts\1.0.290.sql">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
   </ItemGroup>
 
   <ItemGroup>

+ 88 - 0
server/Admin.NET.Web.Entry/UpdateScripts/1.0.290.sql

@@ -0,0 +1,88 @@
+-- AIDOP-S567-KPI-DIMENSION-REMAINING-8-CLOSEOUT-1:5 个 FULLY 维度配置生产固化(幂等)。
+-- S5_L1_001 物料 / S5_L1_002 工单 / S5_L1_004 品类 / S6_L2_002 工单 / S7_L1_002 订单。
+-- 各 DIMENSION_SQL 与对应 SUMMARY_SQL 同源同 WHERE,仅加 GROUP BY 维度键 + 标准输出别名;未改业务公式。
+-- 动态绑定当前生效 SUMMARY(不写死 Id);已有当前生效维度配置则跳过(NOT EXISTS IsCurrent=1,杜绝双 Current)。
+-- 幂等:重复执行影响行 0。
+
+-- ───────── S5_L1_001 物料上线周期(日 · 物料 · AVERAGE_OF_SUMS)─────────
+INSERT INTO ado_smart_ops_kpi_dimension_config
+  (`TenantId`,`MetricCode`,`ModuleCode`,`SummaryConfigId`,`SummaryConfigVersion`,`DimensionConfigVersion`,
+   `DataSourceCode`,`SqlScript`,`AggregationType`,`SupportedDimensionsJson`,`TimeoutSeconds`,`PublishStatus`,`IsCurrent`,`BusinessSqlSource`,`ChangeRemark`,`Remark`,`CreatedBy`,`CreatedAt`)
+SELECT s.`TenantId`,'S5_L1_001','S5',s.`Id`,s.`VersionNo`,1,'MDP_LOCAL',
+'SELECT biz_date AS value_date, ''MATERIAL'' AS dimension_type, item_code AS dimension_code, item_code AS dimension_name,
+       AVG(cycle_days) AS metric_value, SUM(cycle_days) AS sum_value, COUNT(*) AS sample_count, item_code AS source_key
+FROM dwd_t8_material_online_cycle
+WHERE tenant_id = @tenant_id AND factory_id = @factory_id AND biz_date = @biz_date AND source_ztid = @ztid AND cycle_days IS NOT NULL
+GROUP BY biz_date, item_code',
+'AVERAGE_OF_SUMS','["value_date","material_code"]',60,'PUBLISHED',1,'SUMMARY S5_L1_001 同源 dwd_t8_material_online_cycle 物料级 cycle_days','生产固化:物料上线周期按物料维度','S5_L1_001 维度配置生产初始化','system',NOW()
+FROM ado_smart_ops_kpi_calc_config s
+WHERE s.`MetricCode`='S5_L1_001' AND s.`IsCurrent`=1 AND s.`PublishStatus`='PUBLISHED'
+  AND NOT EXISTS (SELECT 1 FROM ado_smart_ops_kpi_dimension_config d WHERE d.`MetricCode`='S5_L1_001' AND d.`TenantId`=s.`TenantId` AND d.`IsCurrent`=1)
+LIMIT 1;
+
+-- ───────── S5_L1_002 物料上线满足率(日 · 工单 · AVERAGE_OF_SUMS 复刻均值×100)─────────
+INSERT INTO ado_smart_ops_kpi_dimension_config
+  (`TenantId`,`MetricCode`,`ModuleCode`,`SummaryConfigId`,`SummaryConfigVersion`,`DimensionConfigVersion`,
+   `DataSourceCode`,`SqlScript`,`AggregationType`,`SupportedDimensionsJson`,`TimeoutSeconds`,`PublishStatus`,`IsCurrent`,`BusinessSqlSource`,`ChangeRemark`,`Remark`,`CreatedBy`,`CreatedAt`)
+SELECT s.`TenantId`,'S5_L1_002','S5',s.`Id`,s.`VersionNo`,1,'MDP_LOCAL',
+'SELECT biz_date AS value_date, ''WORK_ORDER'' AS dimension_type, work_order_no AS dimension_code, work_order_no AS dimension_name,
+       ROUND(AVG(fulfillment_rate) * 100, 4) AS metric_value, SUM(fulfillment_rate * 100) AS sum_value, COUNT(*) AS sample_count,
+       SUM(before_kgdate_rows) AS numerator, SUM(total_rows) AS denominator, work_order_no AS source_key
+FROM dwd_t8_material_online_fulfillment
+WHERE tenant_id = @tenant_id AND factory_id = @factory_id AND biz_date = @biz_date AND source_ztid = @ztid AND fulfillment_rate IS NOT NULL
+GROUP BY biz_date, work_order_no',
+'AVERAGE_OF_SUMS','["value_date","work_order_no"]',60,'PUBLISHED',1,'SUMMARY S5_L1_002 同源 dwd_t8_material_online_fulfillment 工单级 fulfillment_rate','生产固化:物料上线满足率按工单维度','S5_L1_002 维度配置生产初始化','system',NOW()
+FROM ado_smart_ops_kpi_calc_config s
+WHERE s.`MetricCode`='S5_L1_002' AND s.`IsCurrent`=1 AND s.`PublishStatus`='PUBLISHED'
+  AND NOT EXISTS (SELECT 1 FROM ado_smart_ops_kpi_dimension_config d WHERE d.`MetricCode`='S5_L1_002' AND d.`TenantId`=s.`TenantId` AND d.`IsCurrent`=1)
+LIMIT 1;
+
+-- ───────── S5_L1_004 品类物料库存周转(月 · 品类 · AVERAGE_OF_SUMS)─────────
+INSERT INTO ado_smart_ops_kpi_dimension_config
+  (`TenantId`,`MetricCode`,`ModuleCode`,`SummaryConfigId`,`SummaryConfigVersion`,`DimensionConfigVersion`,
+   `DataSourceCode`,`SqlScript`,`AggregationType`,`SupportedDimensionsJson`,`TimeoutSeconds`,`PublishStatus`,`IsCurrent`,`BusinessSqlSource`,`ChangeRemark`,`Remark`,`CreatedBy`,`CreatedAt`)
+SELECT s.`TenantId`,'S5_L1_004','S5',s.`Id`,s.`VersionNo`,1,'MDP_LOCAL',
+'SELECT STR_TO_DATE(CONCAT(biz_month, ''-01''), ''%Y-%m-%d'') AS value_date, ''CATEGORY'' AS dimension_type, category_code AS dimension_code, MAX(category_name) AS dimension_name,
+       AVG(turnover_days) AS metric_value, SUM(turnover_days) AS sum_value, COUNT(turnover_days) AS sample_count, category_code AS source_key
+FROM dwd_t8_material_inventory_turnover
+WHERE tenant_id = @tenant_id AND factory_id = @factory_id AND biz_month = DATE_FORMAT(@biz_date, ''%Y-%m'') AND source_ztid = @ztid
+GROUP BY biz_month, category_code',
+'AVERAGE_OF_SUMS','["value_date","category_code"]',60,'PUBLISHED',1,'SUMMARY S5_L1_004 同源 dwd_t8_material_inventory_turnover 品类级 turnover_days','生产固化:品类物料库存周转按品类维度','S5_L1_004 维度配置生产初始化','system',NOW()
+FROM ado_smart_ops_kpi_calc_config s
+WHERE s.`MetricCode`='S5_L1_004' AND s.`IsCurrent`=1 AND s.`PublishStatus`='PUBLISHED'
+  AND NOT EXISTS (SELECT 1 FROM ado_smart_ops_kpi_dimension_config d WHERE d.`MetricCode`='S5_L1_004' AND d.`TenantId`=s.`TenantId` AND d.`IsCurrent`=1)
+LIMIT 1;
+
+-- ───────── S6_L2_002 工单制造满足率(日 · 工单 · AVERAGE_OF_SUMS 复刻均值×100)─────────
+INSERT INTO ado_smart_ops_kpi_dimension_config
+  (`TenantId`,`MetricCode`,`ModuleCode`,`SummaryConfigId`,`SummaryConfigVersion`,`DimensionConfigVersion`,
+   `DataSourceCode`,`SqlScript`,`AggregationType`,`SupportedDimensionsJson`,`TimeoutSeconds`,`PublishStatus`,`IsCurrent`,`BusinessSqlSource`,`ChangeRemark`,`Remark`,`CreatedBy`,`CreatedAt`)
+SELECT s.`TenantId`,'S6_L2_002','S6',s.`Id`,s.`VersionNo`,1,'MDP_LOCAL',
+'SELECT biz_date AS value_date, ''WORK_ORDER'' AS dimension_type, order_no AS dimension_code, order_no AS dimension_name,
+       ROUND(AVG(fulfillment_rate) * 100, 4) AS metric_value, SUM(fulfillment_rate * 100) AS sum_value, COUNT(*) AS sample_count,
+       SUM(done_qty_in_window) AS numerator, SUM(plan_qty) AS denominator, order_no AS source_key
+FROM dwd_t8_work_order_mfg_fulfillment
+WHERE tenant_id = @tenant_id AND factory_id = @factory_id AND biz_date = @biz_date AND source_ztid = @ztid AND fulfillment_rate IS NOT NULL
+GROUP BY biz_date, order_no',
+'AVERAGE_OF_SUMS','["value_date","work_order_no"]',60,'PUBLISHED',1,'SUMMARY S6_L2_002 同源 dwd_t8_work_order_mfg_fulfillment 工单级 fulfillment_rate','生产固化:工单制造满足率按工单维度','S6_L2_002 维度配置生产初始化','system',NOW()
+FROM ado_smart_ops_kpi_calc_config s
+WHERE s.`MetricCode`='S6_L2_002' AND s.`IsCurrent`=1 AND s.`PublishStatus`='PUBLISHED'
+  AND NOT EXISTS (SELECT 1 FROM ado_smart_ops_kpi_dimension_config d WHERE d.`MetricCode`='S6_L2_002' AND d.`TenantId`=s.`TenantId` AND d.`IsCurrent`=1)
+LIMIT 1;
+
+-- ───────── S7_L1_002 订单发货满足率(日 · 订单 · AVERAGE_OF_SUMS)─────────
+INSERT INTO ado_smart_ops_kpi_dimension_config
+  (`TenantId`,`MetricCode`,`ModuleCode`,`SummaryConfigId`,`SummaryConfigVersion`,`DimensionConfigVersion`,
+   `DataSourceCode`,`SqlScript`,`AggregationType`,`SupportedDimensionsJson`,`TimeoutSeconds`,`PublishStatus`,`IsCurrent`,`BusinessSqlSource`,`ChangeRemark`,`Remark`,`CreatedBy`,`CreatedAt`)
+SELECT s.`TenantId`,'S7_L1_002','S7',s.`Id`,s.`VersionNo`,1,'MDP_LOCAL',
+'SELECT biz_date AS value_date, ''ORDER'' AS dimension_type, order_no AS dimension_code, order_no AS dimension_name,
+       ROUND(AVG(fulfillment_rate), 4) AS metric_value, SUM(fulfillment_rate) AS sum_value, COUNT(*) AS sample_count,
+       SUM(in_window_rows) AS numerator, SUM(total_rows) AS denominator, order_no AS source_key
+FROM dwd_t8_order_shipment_fulfillment
+WHERE tenant_id = @tenant_id AND factory_id = @factory_id AND biz_date = @biz_date AND source_ztid = @ztid AND fulfillment_rate IS NOT NULL
+GROUP BY biz_date, order_no',
+'AVERAGE_OF_SUMS','["value_date","order_no"]',60,'PUBLISHED',1,'SUMMARY S7_L1_002 同源 dwd_t8_order_shipment_fulfillment 订单级 fulfillment_rate','生产固化:订单发货满足率按订单维度','S7_L1_002 维度配置生产初始化','system',NOW()
+FROM ado_smart_ops_kpi_calc_config s
+WHERE s.`MetricCode`='S7_L1_002' AND s.`IsCurrent`=1 AND s.`PublishStatus`='PUBLISHED'
+  AND NOT EXISTS (SELECT 1 FROM ado_smart_ops_kpi_dimension_config d WHERE d.`MetricCode`='S7_L1_002' AND d.`TenantId`=s.`TenantId` AND d.`IsCurrent`=1)
+LIMIT 1;

+ 6 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/AidopKanbanController.SmartOpsFilter.cs

@@ -10,7 +10,12 @@ public partial class AidopKanbanController
     /// 绝不再走旧 Atomic 旁路。本批只纳管 S7_L1_001,其余 8 码保持现状。
     /// </summary>
     private static readonly HashSet<string> DimensionManagedMetricCodes =
-        new(StringComparer.OrdinalIgnoreCase) { "S7_L1_001" };
+        new(StringComparer.OrdinalIgnoreCase)
+        {
+            "S7_L1_001",
+            // 剩余 8 KPI 第一批 5 个 FULLY(日期/月 + 真实业务维度)
+            "S5_L1_001", "S5_L1_002", "S5_L1_004", "S6_L2_002", "S7_L1_002",
+        };
 
     private sealed record DashboardValuesBundle(
         Dictionary<string, DashboardMetricValueModel> Values,

+ 14 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/FinishedWarehouse/S7MdpSyncTransformService.cs

@@ -264,6 +264,20 @@ ON DUPLICATE KEY UPDATE
             ? await UpsertKpiValueAsync("S7_L1_002", option.BizDate, dispatch.MetricValue, now, option)
             : 0;
         sub.DenominatorStatus = dispatch.DenominatorStatus;
+
+        // 调度:SUMMARY 成功/NO_DATA 后触发对应 DIMENSION 跑批(共享 BatchId;SUMMARY FAILED 不触发)。
+        if (dispatch.ShouldUpsert)
+        {
+            try
+            {
+                await _dimensionRun.RunDimensionAsync(
+                    "S7_L1_002", ModuleCode, option.TargetTenantId, option.BizDate, batchId, triggerType, ct);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogWarning(ex, "S7_L1_002 维度跑批异常(不影响汇总链路)");
+            }
+        }
         return sub;
     }
 

+ 18 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Manufacturing/S6MdpSyncTransformService.cs

@@ -20,6 +20,7 @@ public class S6MdpSyncTransformService : ITransient
     private readonly SysNoticeService _sysNoticeService;
     private readonly ILogger<S6MdpSyncTransformService> _logger;
     private readonly SmartOps.KpiCalcDispatcher _kpiCalcDispatcher;
+    private readonly SmartOps.KpiDimensionRunService _dimensionRun;
     private const string L2ValueTable = "ado_s9_kpi_value_l2_day";
     private const string JobCode = "S6_MDP_SYNC_TRANSFORM";
     private const string JobName = "S6 生产执行 MDP 同步与转换";
@@ -32,12 +33,14 @@ public class S6MdpSyncTransformService : ITransient
         ISqlSugarClient db,
         SysNoticeService sysNoticeService,
         ILogger<S6MdpSyncTransformService> logger,
-        SmartOps.KpiCalcDispatcher kpiCalcDispatcher)
+        SmartOps.KpiCalcDispatcher kpiCalcDispatcher,
+        SmartOps.KpiDimensionRunService dimensionRun)
     {
         _db = db;
         _sysNoticeService = sysNoticeService;
         _logger = logger;
         _kpiCalcDispatcher = kpiCalcDispatcher;
+        _dimensionRun = dimensionRun;
     }
 
     public async Task<S6MdpSyncTransformResult> RunFullAsync(
@@ -166,6 +169,20 @@ ON DUPLICATE KEY UPDATE
             ? await UpsertKpiValueAsync("S6_L2_002", option.BizDate, dispatch.MetricValue, now, option, L2ValueTable)
             : 0;
         sub.DenominatorStatus = dispatch.DenominatorStatus;
+
+        // 调度:SUMMARY 成功/NO_DATA 后触发对应 DIMENSION 跑批(共享 BatchId;SUMMARY FAILED 不触发)。
+        if (dispatch.ShouldUpsert)
+        {
+            try
+            {
+                await _dimensionRun.RunDimensionAsync(
+                    "S6_L2_002", ModuleCode, option.TargetTenantId, option.BizDate, batchId, triggerType, ct);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogWarning(ex, "S6_L2_002 维度跑批异常(不影响汇总链路)");
+            }
+        }
         return sub;
     }
 

+ 46 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/MaterialWarehouse/S5MdpSyncTransformService.cs

@@ -31,17 +31,20 @@ public class S5MdpSyncTransformService : ITransient
     private const string NoticeReceiverUserName = "超级管理员";
 
     private readonly SmartOps.KpiCalcDispatcher _kpiCalcDispatcher;
+    private readonly SmartOps.KpiDimensionRunService _dimensionRun;
 
     public S5MdpSyncTransformService(
         ISqlSugarClient db,
         SysNoticeService sysNoticeService,
         ILogger<S5MdpSyncTransformService> logger,
-        SmartOps.KpiCalcDispatcher kpiCalcDispatcher)
+        SmartOps.KpiCalcDispatcher kpiCalcDispatcher,
+        SmartOps.KpiDimensionRunService dimensionRun)
     {
         _db = db;
         _sysNoticeService = sysNoticeService;
         _logger = logger;
         _kpiCalcDispatcher = kpiCalcDispatcher;
+        _dimensionRun = dimensionRun;
     }
 
     public async Task<S5MdpSyncTransformResult> RunFullAsync(
@@ -188,6 +191,20 @@ ON DUPLICATE KEY UPDATE
             ? await UpsertKpiValueAsync("S5_L1_001", option.BizDate, dispatch.MetricValue, now, option)
             : 0;
         sub.DenominatorStatus = dispatch.DenominatorStatus;
+
+        // 调度:SUMMARY 成功/NO_DATA 后触发对应 DIMENSION 跑批(共享 BatchId;SUMMARY FAILED 不触发)。
+        if (dispatch.ShouldUpsert)
+        {
+            try
+            {
+                await _dimensionRun.RunDimensionAsync(
+                    "S5_L1_001", ModuleCode, option.TargetTenantId, option.BizDate, batchId, triggerType, ct);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogWarning(ex, "S5_L1_001 维度跑批异常(不影响汇总链路)");
+            }
+        }
         return sub;
     }
 
@@ -280,6 +297,20 @@ ON DUPLICATE KEY UPDATE
             ? await UpsertKpiValueAsync("S5_L1_002", option.BizDate, dispatch.MetricValue, now, option)
             : 0;
         sub.DenominatorStatus = dispatch.DenominatorStatus;
+
+        // 调度:SUMMARY 成功/NO_DATA 后触发对应 DIMENSION 跑批(共享 BatchId;SUMMARY FAILED 不触发)。
+        if (dispatch.ShouldUpsert)
+        {
+            try
+            {
+                await _dimensionRun.RunDimensionAsync(
+                    "S5_L1_002", ModuleCode, option.TargetTenantId, option.BizDate, batchId, triggerType, ct);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogWarning(ex, "S5_L1_002 维度跑批异常(不影响汇总链路)");
+            }
+        }
         return sub;
     }
 
@@ -461,6 +492,20 @@ ON DUPLICATE KEY UPDATE
             ? await UpsertKpiValueAsync("S5_L1_004", option.MonthlyPeriodEnd, dispatch.MetricValue, now, option)
             : 0;
         sub.DenominatorStatus = dispatch.DenominatorStatus;
+
+        // 调度:SUMMARY 成功/NO_DATA 后触发对应 DIMENSION 跑批(月度:DIMENSION_SQL 用 @biz_date 派生 biz_month,与 SUMMARY 同月)。
+        if (dispatch.ShouldUpsert)
+        {
+            try
+            {
+                await _dimensionRun.RunDimensionAsync(
+                    "S5_L1_004", ModuleCode, option.TargetTenantId, option.MonthlyPeriodEnd, batchId, triggerType, ct);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogWarning(ex, "S5_L1_004 维度跑批异常(不影响汇总链路)");
+            }
+        }
         return sub;
     }
 

+ 7 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/SmartOps/KpiDimensionDashboardService.cs

@@ -66,14 +66,20 @@ public sealed class KpiDimensionDashboardService : ITransient
 
         var ds = filter.DateStart?.Date;
         var de = filter.DateEnd?.Date;
+        // 业务维度值统一落在 DimensionCode(各 KPI 声明维度不同:物料/工单/订单/品类),
+        // 同时兼容标准列匹配。前端只发该 KPI SupportedDimensions 内的维度。
         var orderNo = string.IsNullOrWhiteSpace(filter.OrderNo) ? null : filter.OrderNo!.Trim();
+        var material = string.IsNullOrWhiteSpace(filter.Material) ? null : filter.Material!.Trim();
+        var workOrder = string.IsNullOrWhiteSpace(filter.WorkOrder) ? null : filter.WorkOrder!.Trim();
         ISugarQueryable<AdoSmartOpsKpiDimensionValueDay> Build() =>
             _db.Queryable<AdoSmartOpsKpiDimensionValueDay>().ClearFilter<ITenantIdFilter>()
                 .Where(x => x.TenantId == kpiTenantId && x.MetricCode == metricCode
                             && x.DimensionConfigVersion == active.DimensionConfigVersion)
                 .WhereIF(ds.HasValue, x => x.ValueDate >= ds!.Value)
                 .WhereIF(de.HasValue, x => x.ValueDate <= de!.Value)
-                .WhereIF(orderNo != null, x => x.OrderNo == orderNo || x.DimensionCode == orderNo);
+                .WhereIF(orderNo != null, x => x.OrderNo == orderNo || x.DimensionCode == orderNo)
+                .WhereIF(material != null, x => x.MaterialCode == material || x.DimensionCode == material)
+                .WhereIF(workOrder != null, x => x.WorkOrderNo == workOrder || x.DimensionCode == workOrder);
 
         result.MetricValue = await AggregateAsync(active.AggregationType, Build);
         // 逐日趋势(同 AggregationType 复算)