Просмотр исходного кода

feat(s8): add source table mapping for monitor dictionaries

YY968XX 3 месяцев назад
Родитель
Сommit
e41d5a138a

+ 1 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8MonitorDictionaryDtos.cs

@@ -29,6 +29,7 @@ public class AdoS8MonitorObjectOptionDto
     public string ObjectCode { get; set; } = string.Empty;
     public string ObjectType { get; set; } = string.Empty;
     public string ObjectLabel { get; set; } = string.Empty;
+    public string? SourceTable { get; set; }
     public List<string> Mechanisms { get; set; } = new();
     public List<AdoS8MonitorMetricOptionDto> Metrics { get; set; } = new();
 }

+ 4 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8MonitorObject.cs

@@ -31,6 +31,10 @@ public class AdoS8MonitorObject
     [SugarColumn(ColumnName = "object_name", Length = 128)]
     public string ObjectName { get; set; } = string.Empty;
 
+    /// <summary>规则执行时的物理源表名(demo_test_order 等);为空时表达式生成器走 WHERE 1=0 占位。</summary>
+    [SugarColumn(ColumnName = "source_table", Length = 128, IsNullable = true)]
+    public string? SourceTable { get; set; }
+
     [SugarColumn(ColumnName = "enabled", ColumnDataType = "boolean")]
     public bool Enabled { get; set; } = true;
 

+ 150 - 68
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopMonitorDictionarySeed.cs

@@ -8,6 +8,7 @@ namespace Admin.NET.Plugin.AiDOP.Infrastructure;
 /// CONFIG-MONITOR-DICT-READONLY-SEED-1:S8 监控对象/指标 baseline 种子。
 /// 6 个对象 + 10 条指标,与原前端 BUSINESS_MONITOR_OPTIONS 一一对应。
 /// 幂等:按 (tenant_id=0, factory_id=0, object_code/metric_code) Any() 检查后插入。
+/// 已存在记录会按 SOURCE-MAPPING-1 打补丁:补 source_table / 修正 demo 字段映射。
 /// RATIO 类指标 enabled=false + is_result_kpi=true(S9 结果 KPI 范畴),不进入演示路径。
 /// </summary>
 public static class AidopMonitorDictionarySeed
@@ -16,29 +17,44 @@ public static class AidopMonitorDictionarySeed
     private const string DefaultObjectCodeField = "related_object_code";
     private const string DefaultObjectNameField = "related_object_name";
 
+    /// <summary>CONFIG-MONITOR-DICT-SOURCE-TABLE-SCHEMA-1:DEMO 演示用单表桥接。</summary>
+    private const string DemoSourceTable = "demo_test_order";
+
     public static void EnsureSeed(ISqlSugarClient db)
     {
         try
         {
             var ct = DateTime.Parse("2026-05-09 00:00:00");
 
-            // 6 监控对象
-            EnsureObject(db, "ORDER_DELIVERY",         "ORDER",          "订单交付",      10, ct);
-            EnsureObject(db, "ORDER_CHANGE",           "ORDER",          "订单变更",      20, ct);
-            EnsureObject(db, "PURCHASE_DELIVERY",      "PURCHASE_ORDER", "供应商交付",    30, ct);
-            EnsureObject(db, "IQC_INSPECTION",         "IQC",            "IQC 检验",      40, ct);
-            EnsureObject(db, "WORK_ORDER_PRODUCTION",  "WORK_ORDER",     "生产工单",      50, ct);
-            EnsureObject(db, "INVENTORY_STOCK",        "INVENTORY",      "库存",          60, ct);
+            // 6 监控对象(仅 ORDER_DELIVERY/IQC_INSPECTION 桥接到 demo_test_order,便于 DATE/VR 真实 SQL 演示)
+            EnsureObject(db, "ORDER_DELIVERY",         "ORDER",          "订单交付",      DemoSourceTable, 10, ct);
+            EnsureObject(db, "ORDER_CHANGE",           "ORDER",          "订单变更",      null,            20, ct);
+            EnsureObject(db, "PURCHASE_DELIVERY",      "PURCHASE_ORDER", "供应商交付",    null,            30, ct);
+            EnsureObject(db, "IQC_INSPECTION",         "IQC",            "IQC 检验",      DemoSourceTable, 40, ct);
+            EnsureObject(db, "WORK_ORDER_PRODUCTION",  "WORK_ORDER",     "生产工单",      null,            50, ct);
+            EnsureObject(db, "INVENTORY_STOCK",        "INVENTORY",      "库存",          null,            60, ct);
 
             // DATE 指标 enabled=true / is_result_kpi=false
-            EnsureMetricDate(db, "ORDER_DUE_AT", "计划交付时间", "ORDER_DELIVERY",        "分钟", "due_at", "status", 110, ct);
-            EnsureMetricDate(db, "PO_DUE_AT",    "计划到货时间", "PURCHASE_DELIVERY",     "分钟", "due_at", "status", 310, ct);
-            EnsureMetricDate(db, "WO_DUE_AT",    "计划完工时间", "WORK_ORDER_PRODUCTION", "分钟", "due_at", "status", 510, ct);
+            // ORDER_DUE_AT 走 demo_test_order:id/order_no 真实列名(覆盖默认 alias 占位)
+            EnsureMetricDate(db, "ORDER_DUE_AT", "计划交付时间", "ORDER_DELIVERY",
+                "分钟", "due_at", "status",
+                objectIdCol: "id", objectCodeCol: "order_no", objectNameCol: "order_no",
+                sortNo: 110, ct: ct);
+            EnsureMetricDate(db, "PO_DUE_AT",    "计划到货时间", "PURCHASE_DELIVERY",
+                "分钟", "due_at", "status", null, null, null, 310, ct);
+            EnsureMetricDate(db, "WO_DUE_AT",    "计划完工时间", "WORK_ORDER_PRODUCTION",
+                "分钟", "due_at", "status", null, null, null, 510, ct);
 
             // VALUE_RANGE 指标 enabled=true / is_result_kpi=false
-            EnsureMetricValueRange(db, "ORDER_CHANGE_COUNT", "订单变更次数", "ORDER_CHANGE",        "次",  "measured_value", 210, ct);
-            EnsureMetricValueRange(db, "IQC_VALUE",          "检验值",       "IQC_INSPECTION",      null, "measured_value", 410, ct);
-            EnsureMetricValueRange(db, "INV_QTY",            "当前库存量",   "INVENTORY_STOCK",     null, "measured_value", 610, ct);
+            // IQC_VALUE 走 demo_test_order:measured_size 是 demo 实际列;id/order_no 同上
+            EnsureMetricValueRange(db, "ORDER_CHANGE_COUNT", "订单变更次数", "ORDER_CHANGE",
+                "次", "measured_value", null, null, null, 210, ct);
+            EnsureMetricValueRange(db, "IQC_VALUE",          "检验值",       "IQC_INSPECTION",
+                null, "measured_size",
+                objectIdCol: "id", objectCodeCol: "order_no", objectNameCol: "order_no",
+                sortNo: 410, ct: ct);
+            EnsureMetricValueRange(db, "INV_QTY",            "当前库存量",   "INVENTORY_STOCK",
+                null, "measured_value", null, null, null, 610, ct);
 
             // RATIO 指标 enabled=false / is_result_kpi=true(S9 结果 KPI 范畴;本轮 seed 入库但默认禁用)
             EnsureMetricRatio(db, "ORDER_DELIVERY_RATE", "订单交付满足率", "ORDER_DELIVERY",        "%", "measured_value", 120, ct);
@@ -52,74 +68,100 @@ public static class AidopMonitorDictionarySeed
         }
     }
 
-    private static void EnsureObject(ISqlSugarClient db, string objectCode, string objectType, string objectName, int sortNo, DateTime ct)
+    private static void EnsureObject(ISqlSugarClient db, string objectCode, string objectType, string objectName, string? sourceTable, int sortNo, DateTime ct)
     {
-        var exists = db.Queryable<AdoS8MonitorObject>()
-            .Any(x => x.TenantId == 0 && x.FactoryId == 0 && x.ObjectCode == objectCode);
-        if (exists) return;
+        var existing = db.Queryable<AdoS8MonitorObject>()
+            .First(x => x.TenantId == 0 && x.FactoryId == 0 && x.ObjectCode == objectCode);
 
-        db.Insertable(new AdoS8MonitorObject
+        if (existing == null)
         {
-            TenantId = 0,
-            FactoryId = 0,
-            ObjectCode = objectCode,
-            ObjectType = objectType,
-            ObjectName = objectName,
-            Enabled = true,
-            SortNo = sortNo,
-            CreatedAt = ct,
-        }).ExecuteCommand();
+            db.Insertable(new AdoS8MonitorObject
+            {
+                TenantId = 0,
+                FactoryId = 0,
+                ObjectCode = objectCode,
+                ObjectType = objectType,
+                ObjectName = objectName,
+                SourceTable = sourceTable,
+                Enabled = true,
+                SortNo = sortNo,
+                CreatedAt = ct,
+            }).ExecuteCommand();
+            return;
+        }
+
+        // 已存在记录:仅当 source_table 为空时回填,避免覆盖运维已修改的值
+        if (string.IsNullOrWhiteSpace(existing.SourceTable) && !string.IsNullOrWhiteSpace(sourceTable))
+        {
+            db.Updateable<AdoS8MonitorObject>()
+                .SetColumns(x => x.SourceTable == sourceTable)
+                .SetColumns(x => x.UpdatedAt == DateTime.Now)
+                .Where(x => x.Id == existing.Id)
+                .ExecuteCommand();
+        }
     }
 
-    private static void EnsureMetricDate(ISqlSugarClient db, string metricCode, string metricName, string objectCode, string? unit, string dueAtField, string statusField, int sortNo, DateTime ct)
+    private static void EnsureMetricDate(ISqlSugarClient db, string metricCode, string metricName, string objectCode, string? unit, string dueAtField, string statusField, string? objectIdCol, string? objectCodeCol, string? objectNameCol, int sortNo, DateTime ct)
     {
-        if (db.Queryable<AdoS8MonitorMetric>()
-            .Any(x => x.TenantId == 0 && x.FactoryId == 0 && x.MetricCode == metricCode)) return;
+        var existing = db.Queryable<AdoS8MonitorMetric>()
+            .First(x => x.TenantId == 0 && x.FactoryId == 0 && x.MetricCode == metricCode);
 
-        db.Insertable(new AdoS8MonitorMetric
+        if (existing == null)
         {
-            TenantId = 0,
-            FactoryId = 0,
-            ObjectCode = objectCode,
-            MetricCode = metricCode,
-            MetricName = metricName,
-            Mechanism = "DATE",
-            Unit = unit,
-            DueAtField = dueAtField,
-            StatusField = statusField,
-            ObjectIdField = DefaultObjectIdField,
-            ObjectCodeField = DefaultObjectCodeField,
-            ObjectNameField = DefaultObjectNameField,
-            IsResultKpi = false,
-            Enabled = true,
-            SortNo = sortNo,
-            CreatedAt = ct,
-        }).ExecuteCommand();
+            db.Insertable(new AdoS8MonitorMetric
+            {
+                TenantId = 0,
+                FactoryId = 0,
+                ObjectCode = objectCode,
+                MetricCode = metricCode,
+                MetricName = metricName,
+                Mechanism = "DATE",
+                Unit = unit,
+                DueAtField = dueAtField,
+                StatusField = statusField,
+                ObjectIdField = objectIdCol ?? DefaultObjectIdField,
+                ObjectCodeField = objectCodeCol ?? DefaultObjectCodeField,
+                ObjectNameField = objectNameCol ?? DefaultObjectNameField,
+                IsResultKpi = false,
+                Enabled = true,
+                SortNo = sortNo,
+                CreatedAt = ct,
+            }).ExecuteCommand();
+            return;
+        }
+
+        BackfillMetricColumns(db, existing, objectIdCol, objectCodeCol, objectNameCol);
     }
 
-    private static void EnsureMetricValueRange(ISqlSugarClient db, string metricCode, string metricName, string objectCode, string? unit, string measuredValueField, int sortNo, DateTime ct)
+    private static void EnsureMetricValueRange(ISqlSugarClient db, string metricCode, string metricName, string objectCode, string? unit, string measuredValueField, string? objectIdCol, string? objectCodeCol, string? objectNameCol, int sortNo, DateTime ct)
     {
-        if (db.Queryable<AdoS8MonitorMetric>()
-            .Any(x => x.TenantId == 0 && x.FactoryId == 0 && x.MetricCode == metricCode)) return;
+        var existing = db.Queryable<AdoS8MonitorMetric>()
+            .First(x => x.TenantId == 0 && x.FactoryId == 0 && x.MetricCode == metricCode);
 
-        db.Insertable(new AdoS8MonitorMetric
+        if (existing == null)
         {
-            TenantId = 0,
-            FactoryId = 0,
-            ObjectCode = objectCode,
-            MetricCode = metricCode,
-            MetricName = metricName,
-            Mechanism = "VALUE_RANGE",
-            Unit = unit,
-            MeasuredValueField = measuredValueField,
-            ObjectIdField = DefaultObjectIdField,
-            ObjectCodeField = DefaultObjectCodeField,
-            ObjectNameField = DefaultObjectNameField,
-            IsResultKpi = false,
-            Enabled = true,
-            SortNo = sortNo,
-            CreatedAt = ct,
-        }).ExecuteCommand();
+            db.Insertable(new AdoS8MonitorMetric
+            {
+                TenantId = 0,
+                FactoryId = 0,
+                ObjectCode = objectCode,
+                MetricCode = metricCode,
+                MetricName = metricName,
+                Mechanism = "VALUE_RANGE",
+                Unit = unit,
+                MeasuredValueField = measuredValueField,
+                ObjectIdField = objectIdCol ?? DefaultObjectIdField,
+                ObjectCodeField = objectCodeCol ?? DefaultObjectCodeField,
+                ObjectNameField = objectNameCol ?? DefaultObjectNameField,
+                IsResultKpi = false,
+                Enabled = true,
+                SortNo = sortNo,
+                CreatedAt = ct,
+            }).ExecuteCommand();
+            return;
+        }
+
+        BackfillMetricColumns(db, existing, objectIdCol, objectCodeCol, objectNameCol, measuredValueField);
     }
 
     private static void EnsureMetricRatio(ISqlSugarClient db, string metricCode, string metricName, string objectCode, string? unit, string measuredValueField, int sortNo, DateTime ct)
@@ -147,4 +189,44 @@ public static class AidopMonitorDictionarySeed
             Remark = "[RESERVED] S9 result KPI; enable explicitly when S8/S9 boundary task lands",
         }).ExecuteCommand();
     }
+
+    /// <summary>已存在指标行:仅在原值仍是默认 alias 占位时回填真实列名,避免覆盖运维改过的字段。</summary>
+    private static void BackfillMetricColumns(ISqlSugarClient db, AdoS8MonitorMetric existing, string? objectIdCol, string? objectCodeCol, string? objectNameCol, string? measuredValueCol = null)
+    {
+        var changed = false;
+
+        if (!string.IsNullOrWhiteSpace(objectIdCol)
+            && (string.IsNullOrWhiteSpace(existing.ObjectIdField) || existing.ObjectIdField == DefaultObjectIdField))
+        {
+            existing.ObjectIdField = objectIdCol;
+            changed = true;
+        }
+        if (!string.IsNullOrWhiteSpace(objectCodeCol)
+            && (string.IsNullOrWhiteSpace(existing.ObjectCodeField) || existing.ObjectCodeField == DefaultObjectCodeField))
+        {
+            existing.ObjectCodeField = objectCodeCol;
+            changed = true;
+        }
+        if (!string.IsNullOrWhiteSpace(objectNameCol)
+            && (string.IsNullOrWhiteSpace(existing.ObjectNameField) || existing.ObjectNameField == DefaultObjectNameField))
+        {
+            existing.ObjectNameField = objectNameCol;
+            changed = true;
+        }
+        if (!string.IsNullOrWhiteSpace(measuredValueCol)
+            && (string.IsNullOrWhiteSpace(existing.MeasuredValueField) || existing.MeasuredValueField == "measured_value"))
+        {
+            // 仅当原值是默认占位 measured_value 时才覆盖
+            if (existing.MeasuredValueField != measuredValueCol)
+            {
+                existing.MeasuredValueField = measuredValueCol;
+                changed = true;
+            }
+        }
+
+        if (!changed) return;
+
+        existing.UpdatedAt = DateTime.Now;
+        db.Updateable(existing).ExecuteCommand();
+    }
 }

+ 1 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8MonitorDictionaryService.cs

@@ -86,6 +86,7 @@ public class S8MonitorDictionaryService : ITransient
                 ObjectCode = obj.ObjectCode,
                 ObjectType = obj.ObjectType,
                 ObjectLabel = obj.ObjectName,
+                SourceTable = obj.SourceTable,
                 Mechanisms = mechanisms,
                 Metrics = metrics,
             });