瀏覽代碼

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

YY968XX 5 月之前
父節點
當前提交
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 ObjectCode { get; set; } = string.Empty;
     public string ObjectType { get; set; } = string.Empty;
     public string ObjectType { get; set; } = string.Empty;
     public string ObjectLabel { 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<string> Mechanisms { get; set; } = new();
     public List<AdoS8MonitorMetricOptionDto> Metrics { 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)]
     [SugarColumn(ColumnName = "object_name", Length = 128)]
     public string ObjectName { get; set; } = string.Empty;
     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")]
     [SugarColumn(ColumnName = "enabled", ColumnDataType = "boolean")]
     public bool Enabled { get; set; } = true;
     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 种子。
 /// CONFIG-MONITOR-DICT-READONLY-SEED-1:S8 监控对象/指标 baseline 种子。
 /// 6 个对象 + 10 条指标,与原前端 BUSINESS_MONITOR_OPTIONS 一一对应。
 /// 6 个对象 + 10 条指标,与原前端 BUSINESS_MONITOR_OPTIONS 一一对应。
 /// 幂等:按 (tenant_id=0, factory_id=0, object_code/metric_code) Any() 检查后插入。
 /// 幂等:按 (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 范畴),不进入演示路径。
 /// RATIO 类指标 enabled=false + is_result_kpi=true(S9 结果 KPI 范畴),不进入演示路径。
 /// </summary>
 /// </summary>
 public static class AidopMonitorDictionarySeed
 public static class AidopMonitorDictionarySeed
@@ -16,29 +17,44 @@ public static class AidopMonitorDictionarySeed
     private const string DefaultObjectCodeField = "related_object_code";
     private const string DefaultObjectCodeField = "related_object_code";
     private const string DefaultObjectNameField = "related_object_name";
     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)
     public static void EnsureSeed(ISqlSugarClient db)
     {
     {
         try
         try
         {
         {
             var ct = DateTime.Parse("2026-05-09 00:00:00");
             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
             // 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
             // 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 入库但默认禁用)
             // RATIO 指标 enabled=false / is_result_kpi=true(S9 结果 KPI 范畴;本轮 seed 入库但默认禁用)
             EnsureMetricRatio(db, "ORDER_DELIVERY_RATE", "订单交付满足率", "ORDER_DELIVERY",        "%", "measured_value", 120, ct);
             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)
     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",
             Remark = "[RESERVED] S9 result KPI; enable explicitly when S8/S9 boundary task lands",
         }).ExecuteCommand();
         }).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,
                 ObjectCode = obj.ObjectCode,
                 ObjectType = obj.ObjectType,
                 ObjectType = obj.ObjectType,
                 ObjectLabel = obj.ObjectName,
                 ObjectLabel = obj.ObjectName,
+                SourceTable = obj.SourceTable,
                 Mechanisms = mechanisms,
                 Mechanisms = mechanisms,
                 Metrics = metrics,
                 Metrics = metrics,
             });
             });