AidopMonitorDictionarySeed.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. using System.Diagnostics;
  2. using Admin.NET.Plugin.AiDOP.Entity.S8;
  3. using SqlSugar;
  4. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  5. /// <summary>
  6. /// CONFIG-MONITOR-DICT-READONLY-SEED-1:S8 监控对象/指标 baseline 种子。
  7. /// 6 个对象 + 10 条指标,与原前端 BUSINESS_MONITOR_OPTIONS 一一对应。
  8. /// 幂等:按 (tenant_id=0, factory_id=0, object_code/metric_code) Any() 检查后插入。
  9. /// 已存在记录会按 SOURCE-MAPPING-1 打补丁:补 source_table / 修正 demo 字段映射。
  10. /// RATIO 类指标 enabled=false + is_result_kpi=true(S9 结果 KPI 范畴),不进入演示路径。
  11. /// </summary>
  12. public static class AidopMonitorDictionarySeed
  13. {
  14. private const string DefaultObjectIdField = "source_object_id";
  15. private const string DefaultObjectCodeField = "related_object_code";
  16. private const string DefaultObjectNameField = "related_object_name";
  17. /// <summary>CONFIG-MONITOR-DICT-SOURCE-TABLE-SCHEMA-1:DEMO 演示用单表桥接。</summary>
  18. private const string DemoSourceTable = "demo_test_order";
  19. /// <summary>CONFIG-MONITOR-SOURCE-TABLE-EXTEND-1:库存监控真实库表(5k+ 行 demo 数据)。</summary>
  20. private const string InventoryStockSourceTable = "ic_item_stock";
  21. /// <summary>CONFIG-MONITOR-DATA-EXTEND-2:工单生产真实表(mes_morder MES 制造订单)。</summary>
  22. private const string WorkOrderProductionSourceTable = "mes_morder";
  23. public static void EnsureSeed(ISqlSugarClient db)
  24. {
  25. try
  26. {
  27. var ct = DateTime.Parse("2026-05-09 00:00:00");
  28. // 6 监控对象(仅 ORDER_DELIVERY/IQC_INSPECTION 桥接到 demo_test_order,便于 DATE/VR 真实 SQL 演示)
  29. EnsureObject(db, "ORDER_DELIVERY", "ORDER", "订单交付", DemoSourceTable, 10, ct);
  30. EnsureObject(db, "ORDER_CHANGE", "ORDER", "订单变更", null, 20, ct);
  31. EnsureObject(db, "PURCHASE_DELIVERY", "PURCHASE_ORDER", "供应商交付", null, 30, ct);
  32. EnsureObject(db, "IQC_INSPECTION", "IQC", "IQC 检验", DemoSourceTable, 40, ct);
  33. EnsureObject(db, "WORK_ORDER_PRODUCTION", "WORK_ORDER", "生产工单", WorkOrderProductionSourceTable, 50, ct);
  34. EnsureObject(db, "INVENTORY_STOCK", "INVENTORY", "库存", InventoryStockSourceTable, 60, ct);
  35. // DATE 指标 enabled=true / is_result_kpi=false
  36. // ORDER_DUE_AT 走 demo_test_order:id/order_no 真实列名(覆盖默认 alias 占位)
  37. EnsureMetricDate(db, "ORDER_DUE_AT", "计划交付时间", "ORDER_DELIVERY",
  38. "分钟", "due_at", "status",
  39. objectIdCol: "id", objectCodeCol: "order_no", objectNameCol: "order_no",
  40. sortNo: 110, ct: ct);
  41. EnsureMetricDate(db, "PO_DUE_AT", "计划到货时间", "PURCHASE_DELIVERY",
  42. "分钟", "due_at", "status", null, null, null, 310, ct);
  43. // WO_DUE_AT 走 mes_morder:planner_end_date 计划完工日,morder_state 业务状态;Id/morder_no/moentry_prdname 真实列
  44. EnsureMetricDate(db, "WO_DUE_AT", "计划完工时间", "WORK_ORDER_PRODUCTION",
  45. "分钟", "planner_end_date", "morder_state",
  46. objectIdCol: "Id", objectCodeCol: "morder_no", objectNameCol: "moentry_prdname",
  47. sortNo: 510, ct: ct);
  48. // VALUE_RANGE 指标 enabled=true / is_result_kpi=false
  49. // IQC_VALUE 走 demo_test_order:measured_size 是 demo 实际列;id/order_no 同上
  50. EnsureMetricValueRange(db, "ORDER_CHANGE_COUNT", "订单变更次数", "ORDER_CHANGE",
  51. "次", "measured_value", null, null, null, 210, ct);
  52. EnsureMetricValueRange(db, "IQC_VALUE", "检验值", "IQC_INSPECTION",
  53. null, "measured_size",
  54. objectIdCol: "id", objectCodeCol: "order_no", objectNameCol: "order_no",
  55. sortNo: 410, ct: ct);
  56. // INV_QTY 走 ic_item_stock:sqty 是当前库存量;Id/icitem_number/icitem_name 真实列
  57. EnsureMetricValueRange(db, "INV_QTY", "当前库存量", "INVENTORY_STOCK",
  58. null, "sqty",
  59. objectIdCol: "Id", objectCodeCol: "icitem_number", objectNameCol: "icitem_name",
  60. sortNo: 610, ct: ct);
  61. // RATIO 指标 enabled=false / is_result_kpi=true(S9 结果 KPI 范畴;本轮 seed 入库但默认禁用)
  62. EnsureMetricRatio(db, "ORDER_DELIVERY_RATE", "订单交付满足率", "ORDER_DELIVERY", "%", "measured_value", 120, ct);
  63. EnsureMetricRatio(db, "PO_DELIVERY_RATE", "到货达成率", "PURCHASE_DELIVERY", "%", "measured_value", 320, ct);
  64. EnsureMetricRatio(db, "IQC_PASS_RATE", "检验合格率", "IQC_INSPECTION", "%", "measured_value", 420, ct);
  65. EnsureMetricRatio(db, "WO_COMPLETION_RATE", "工单完工率", "WORK_ORDER_PRODUCTION", "%", "measured_value", 520, ct);
  66. // ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t2d:销售订单链路阶段监控字典 baseline。
  67. // 桥接到 ado_s8_order_flow_stage(t2a-fix 后 PK 显式 / order_id 已与 order 对齐)。
  68. // 三条 metric:DUE(阶段计划完成时间,DATE)/ VARIANCE(阶段偏差天数,VALUE_RANGE)/ CYCLE_RATIO(总周期达成率,RATIO,元数据备用)。
  69. EnsureObject(db, "SALES_ORDER_FLOW_STAGE", "SALES_ORDER", "销售订单链路阶段",
  70. "ado_s8_order_flow_stage", 70, ct);
  71. EnsureMetricDate(db, "ORDER_FLOW_STAGE_DUE", "阶段计划完成时间", "SALES_ORDER_FLOW_STAGE",
  72. null, "target_at", "status",
  73. objectIdCol: "id", objectCodeCol: "order_code", objectNameCol: "order_flow_name",
  74. sortNo: 710, ct: ct);
  75. EnsureMetricValueRange(db, "ORDER_FLOW_STAGE_VARIANCE", "阶段偏差天数", "SALES_ORDER_FLOW_STAGE",
  76. "天", "node_variance_days",
  77. objectIdCol: "id", objectCodeCol: "order_code", objectNameCol: "order_flow_name",
  78. sortNo: 720, ct: ct);
  79. EnsureMetricRatio(db, "ORDER_FLOW_CYCLE_RATIO", "总周期达成率", "SALES_ORDER_FLOW_STAGE",
  80. "%", "cumulative_variance_days", 730, ct,
  81. objectIdCol: "id", objectCodeCol: "order_code", objectNameCol: "order_flow_name",
  82. remark: "[RESERVED] order flow cycle ratio; metadata only, not enabled, no watch_rule");
  83. }
  84. catch (Exception ex)
  85. {
  86. Trace.TraceWarning("AidopMonitorDictionarySeed: " + ex);
  87. }
  88. }
  89. private static void EnsureObject(ISqlSugarClient db, string objectCode, string objectType, string objectName, string? sourceTable, int sortNo, DateTime ct)
  90. {
  91. var existing = db.Queryable<AdoS8MonitorObject>()
  92. .First(x => x.TenantId == 0 && x.FactoryId == 0 && x.ObjectCode == objectCode);
  93. if (existing == null)
  94. {
  95. db.Insertable(new AdoS8MonitorObject
  96. {
  97. TenantId = 0,
  98. FactoryId = 0,
  99. ObjectCode = objectCode,
  100. ObjectType = objectType,
  101. ObjectName = objectName,
  102. SourceTable = sourceTable,
  103. Enabled = true,
  104. SortNo = sortNo,
  105. CreatedAt = ct,
  106. }).ExecuteCommand();
  107. return;
  108. }
  109. // 已存在记录:仅当 source_table 为空时回填,避免覆盖运维已修改的值
  110. if (string.IsNullOrWhiteSpace(existing.SourceTable) && !string.IsNullOrWhiteSpace(sourceTable))
  111. {
  112. db.Updateable<AdoS8MonitorObject>()
  113. .SetColumns(x => x.SourceTable == sourceTable)
  114. .SetColumns(x => x.UpdatedAt == DateTime.Now)
  115. .Where(x => x.Id == existing.Id)
  116. .ExecuteCommand();
  117. }
  118. }
  119. 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)
  120. {
  121. var existing = db.Queryable<AdoS8MonitorMetric>()
  122. .First(x => x.TenantId == 0 && x.FactoryId == 0 && x.MetricCode == metricCode);
  123. if (existing == null)
  124. {
  125. db.Insertable(new AdoS8MonitorMetric
  126. {
  127. TenantId = 0,
  128. FactoryId = 0,
  129. ObjectCode = objectCode,
  130. MetricCode = metricCode,
  131. MetricName = metricName,
  132. Mechanism = "DATE",
  133. Unit = unit,
  134. DueAtField = dueAtField,
  135. StatusField = statusField,
  136. ObjectIdField = objectIdCol ?? DefaultObjectIdField,
  137. ObjectCodeField = objectCodeCol ?? DefaultObjectCodeField,
  138. ObjectNameField = objectNameCol ?? DefaultObjectNameField,
  139. IsResultKpi = false,
  140. Enabled = true,
  141. SortNo = sortNo,
  142. CreatedAt = ct,
  143. }).ExecuteCommand();
  144. return;
  145. }
  146. BackfillMetricColumns(db, existing, objectIdCol, objectCodeCol, objectNameCol, dueAtCol: dueAtField, statusCol: statusField);
  147. }
  148. 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)
  149. {
  150. var existing = db.Queryable<AdoS8MonitorMetric>()
  151. .First(x => x.TenantId == 0 && x.FactoryId == 0 && x.MetricCode == metricCode);
  152. if (existing == null)
  153. {
  154. db.Insertable(new AdoS8MonitorMetric
  155. {
  156. TenantId = 0,
  157. FactoryId = 0,
  158. ObjectCode = objectCode,
  159. MetricCode = metricCode,
  160. MetricName = metricName,
  161. Mechanism = "VALUE_RANGE",
  162. Unit = unit,
  163. MeasuredValueField = measuredValueField,
  164. ObjectIdField = objectIdCol ?? DefaultObjectIdField,
  165. ObjectCodeField = objectCodeCol ?? DefaultObjectCodeField,
  166. ObjectNameField = objectNameCol ?? DefaultObjectNameField,
  167. IsResultKpi = false,
  168. Enabled = true,
  169. SortNo = sortNo,
  170. CreatedAt = ct,
  171. }).ExecuteCommand();
  172. return;
  173. }
  174. BackfillMetricColumns(db, existing, objectIdCol, objectCodeCol, objectNameCol, measuredValueField);
  175. }
  176. private static void EnsureMetricRatio(ISqlSugarClient db, string metricCode, string metricName, string objectCode, string? unit, string measuredValueField, int sortNo, DateTime ct,
  177. string? objectIdCol = null, string? objectCodeCol = null, string? objectNameCol = null, string? remark = null)
  178. {
  179. if (db.Queryable<AdoS8MonitorMetric>()
  180. .Any(x => x.TenantId == 0 && x.FactoryId == 0 && x.MetricCode == metricCode)) return;
  181. db.Insertable(new AdoS8MonitorMetric
  182. {
  183. TenantId = 0,
  184. FactoryId = 0,
  185. ObjectCode = objectCode,
  186. MetricCode = metricCode,
  187. MetricName = metricName,
  188. Mechanism = "RATIO",
  189. Unit = unit,
  190. MeasuredValueField = measuredValueField,
  191. ObjectIdField = objectIdCol ?? DefaultObjectIdField,
  192. ObjectCodeField = objectCodeCol ?? DefaultObjectCodeField,
  193. ObjectNameField = objectNameCol ?? DefaultObjectNameField,
  194. IsResultKpi = true,
  195. Enabled = false,
  196. SortNo = sortNo,
  197. CreatedAt = ct,
  198. Remark = remark ?? "[RESERVED] S9 result KPI; enable explicitly when S8/S9 boundary task lands",
  199. }).ExecuteCommand();
  200. }
  201. /// <summary>已存在指标行:仅在原值仍是默认 alias 占位时回填真实列名,避免覆盖运维改过的字段。</summary>
  202. private static void BackfillMetricColumns(ISqlSugarClient db, AdoS8MonitorMetric existing, string? objectIdCol, string? objectCodeCol, string? objectNameCol, string? measuredValueCol = null, string? dueAtCol = null, string? statusCol = null)
  203. {
  204. var changed = false;
  205. if (!string.IsNullOrWhiteSpace(objectIdCol)
  206. && (string.IsNullOrWhiteSpace(existing.ObjectIdField) || existing.ObjectIdField == DefaultObjectIdField))
  207. {
  208. existing.ObjectIdField = objectIdCol;
  209. changed = true;
  210. }
  211. if (!string.IsNullOrWhiteSpace(objectCodeCol)
  212. && (string.IsNullOrWhiteSpace(existing.ObjectCodeField) || existing.ObjectCodeField == DefaultObjectCodeField))
  213. {
  214. existing.ObjectCodeField = objectCodeCol;
  215. changed = true;
  216. }
  217. if (!string.IsNullOrWhiteSpace(objectNameCol)
  218. && (string.IsNullOrWhiteSpace(existing.ObjectNameField) || existing.ObjectNameField == DefaultObjectNameField))
  219. {
  220. existing.ObjectNameField = objectNameCol;
  221. changed = true;
  222. }
  223. if (!string.IsNullOrWhiteSpace(measuredValueCol)
  224. && (string.IsNullOrWhiteSpace(existing.MeasuredValueField) || existing.MeasuredValueField == "measured_value"))
  225. {
  226. // 仅当原值是默认占位 measured_value 时才覆盖
  227. if (existing.MeasuredValueField != measuredValueCol)
  228. {
  229. existing.MeasuredValueField = measuredValueCol;
  230. changed = true;
  231. }
  232. }
  233. // due_at / status 是 DATE 指标的占位默认值;与 demo_test_order 真实列同名属于巧合。
  234. // 仅当 existing 仍是占位 "due_at"/"status" 时才回填,避免覆盖运维改过的字段。
  235. if (!string.IsNullOrWhiteSpace(dueAtCol)
  236. && existing.DueAtField == "due_at"
  237. && existing.DueAtField != dueAtCol)
  238. {
  239. existing.DueAtField = dueAtCol;
  240. changed = true;
  241. }
  242. if (!string.IsNullOrWhiteSpace(statusCol)
  243. && existing.StatusField == "status"
  244. && existing.StatusField != statusCol)
  245. {
  246. existing.StatusField = statusCol;
  247. changed = true;
  248. }
  249. if (!changed) return;
  250. existing.UpdatedAt = DateTime.Now;
  251. db.Updateable(existing).ExecuteCommand();
  252. }
  253. }