Переглянути джерело

feat(s8): configure exception monitoring categories

YY968XX 1 місяць тому
батько
коміт
c9fd5b7722

+ 11 - 0
Web/src/views/aidop/s8/config/S8ExceptionTypeConfigPage.vue

@@ -8,10 +8,19 @@ const columns = [
 	{ key: 'typeCode', label: '类型编码', width: 180 },
 	{ key: 'typeName', label: '类型名称', width: 180 },
 	{ key: 'sceneCode', label: '场景编码', width: 150 },
+	{ key: 'monitoringCategoryKey', label: '大屏统计类别', width: 160 },
 	{ key: 'enabled', label: '启用', width: 90 },
 	{ key: 'sortNo', label: '排序', width: 90 },
 ];
 
+const MONITORING_CATEGORY_OPTIONS = [
+	{ label: '订单评审', value: 'ORDER_REVIEW' },
+	{ label: '产品设计', value: 'PRODUCT_DESIGN' },
+	{ label: '材料采购', value: 'MATERIAL_PURCHASE' },
+	{ label: '本体生产', value: 'BODY_PRODUCTION' },
+	{ label: '总装发货', value: 'FINAL_ASSEMBLY_DELIVERY' },
+];
+
 // S8-CONFIG-CLEANUP-DEMO-1:以下 7 个字段标记 hidden=true:
 // severityDefault / slaMinutes / ownerRoleCode / escalateRoleCode / statsMode / icon / mobileVisible
 // 渲染层隐藏,但保留在 fields / buildDefault / form / PUT payload,旧值原样回写不丢。
@@ -20,6 +29,7 @@ const fields = [
 	{ key: 'typeCode', label: '类型编码', type: 'input', required: true, placeholder: '如 ORDER_CHANGE' },
 	{ key: 'typeName', label: '类型名称', type: 'input', required: true },
 	{ key: 'sceneCode', label: '场景编码', type: 'select', required: true, optionsKey: 'scenes' },
+	{ key: 'monitoringCategoryKey', label: '大屏统计类别', type: 'select', options: MONITORING_CATEGORY_OPTIONS },
 	{ key: 'severityDefault', label: '默认严重度', type: 'select', optionsKey: 'severities', hidden: true },
 	{ key: 'slaMinutes', label: 'SLA(分钟)', type: 'number', hidden: true },
 	{ key: 'ownerRoleCode', label: '责任角色', type: 'select', optionsKey: 'roles', hidden: true },
@@ -47,6 +57,7 @@ const buildDefault = () => ({
 	typeCode: '',
 	typeName: '',
 	sceneCode: '',
+	monitoringCategoryKey: '',
 	severityDefault: 'MEDIUM',
 	slaMinutes: 60,
 	ownerRoleCode: '',

+ 5 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8ExceptionType.cs

@@ -48,6 +48,11 @@ public class AdoS8ExceptionType
     [SugarColumn(ColumnName = "stats_mode", Length = 16)]
     public string StatsMode { get; set; } = "ALL";
 
+    /// <summary>大屏统计类别 Key(5 选 1:ORDER_REVIEW / PRODUCT_DESIGN / MATERIAL_PURCHASE / BODY_PRODUCTION / FINAL_ASSEMBLY_DELIVERY)。
+    /// S8 Overview 5 张类别卡聚合依据。空值时回落到 S8MonitoringService.CategoryOfLegacy 兼容映射。</summary>
+    [SugarColumn(ColumnName = "monitoring_category_key", Length = 64, IsNullable = true)]
+    public string? MonitoringCategoryKey { get; set; }
+
     [SugarColumn(ColumnName = "mobile_visible", ColumnDataType = "boolean")]
     public bool MobileVisible { get; set; } = true;
 

+ 44 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8MonitoringCategory.cs

@@ -0,0 +1,44 @@
+namespace Admin.NET.Plugin.AiDOP.Infrastructure.S8;
+
+/// <summary>
+/// S8 监控大屏 5 张类别卡稳定 Key 与中文 Label 的双向映射。
+/// Key 写入 ado_s8_exception_type.monitoring_category_key;Label 用于 byCategory.category 输出,
+/// 与前端 CATEGORY_DEFS_FALLBACK / OVERVIEW_CATEGORY_KEY_BY_CELL_CODE 字面 100% 对齐。
+/// </summary>
+public static class S8MonitoringCategory
+{
+    public const string OrderReview = "ORDER_REVIEW";
+    public const string ProductDesign = "PRODUCT_DESIGN";
+    public const string MaterialPurchase = "MATERIAL_PURCHASE";
+    public const string BodyProduction = "BODY_PRODUCTION";
+    public const string FinalAssemblyDelivery = "FINAL_ASSEMBLY_DELIVERY";
+
+    public const string OrderReviewLabel = "订单评审";
+    public const string ProductDesignLabel = "产品设计";
+    public const string MaterialPurchaseLabel = "材料采购";
+    public const string BodyProductionLabel = "本体生产";
+    public const string FinalAssemblyDeliveryLabel = "总装发货";
+
+    public static string? Normalize(string? key)
+    {
+        var normalized = key?.Trim().ToUpperInvariant();
+        return IsValid(normalized) ? normalized : null;
+    }
+
+    public static bool IsValid(string? key) => key is
+        OrderReview or
+        ProductDesign or
+        MaterialPurchase or
+        BodyProduction or
+        FinalAssemblyDelivery;
+
+    public static string? KeyToLabel(string? key) => Normalize(key) switch
+    {
+        OrderReview => OrderReviewLabel,
+        ProductDesign => ProductDesignLabel,
+        MaterialPurchase => MaterialPurchaseLabel,
+        BodyProduction => BodyProductionLabel,
+        FinalAssemblyDelivery => FinalAssemblyDeliveryLabel,
+        _ => null,
+    };
+}

+ 44 - 39
server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/S8ExceptionTypeSeedData.cs

@@ -16,9 +16,13 @@ namespace Admin.NET.Plugin.AiDOP;
 ///   - S8-BASELINE-EXCEPTION-TYPE-RULE-TEMPLATE-UPDATE-1:基于《S1-S7异常类型与三类规则对应表.md》
 ///     扩展 baseline 至 44 条;旧 7 条(DIMENSION_DEVIATION / MATERIAL_SHORTAGE / QUALITY_DEFECT /
 ///     YIELD_DEFICIT / WH_INBOUND_ISSUE / WH_KIT_ISSUE / WH_ISSUE_OUT_ISSUE)暂保留为"待裁剪"。
-///   - S8-DEPRECATED-EXCEPTION-TYPE-MIGRATE-1(本轮):迁移 active 异常 / cell_config / watch_rule.params_json,
+///   - S8-DEPRECATED-EXCEPTION-TYPE-MIGRATE-1:迁移 active 异常 / cell_config / watch_rule.params_json,
 ///     旧 7 条 enabled=false 持久化(不删除,保留历史可查 + remark 标注 migrated to ...)。
 ///     enabled baseline 由 44 → 37。
+///   - S8-EXCEPTION-TYPE-CATEGORY-CONFIG-2(本轮):异常类型新增 monitoring_category_key 列,
+///     用配置驱动 Overview 5 张类别卡聚合,取代 S8MonitoringService.CategoryOf 内的 hardcode。
+///     37 条 enabled 在 SeedData 显式赋 5 选 1(PRODUCT_DESIGN 当前无对应 type_code,先保留 key 选项);
+///     7 条 deprecated 保持 monitoring_category_key=null,命中 CategoryOfLegacy 兜底。
 ///
 /// 字段约定:Id 与 DB 现有 1:1 对齐;type_code 全保留;sort_no 与 DB 一致;enabled=true。
 /// </summary>
@@ -33,13 +37,13 @@ public class S8ExceptionTypeSeedData : ISqlSugarEntitySeedData<Entity.S8.AdoS8Ex
         return new[]
         {
             // ── S1 产销协同 + S7 成品仓储(交付侧) ──
-            T(baseId + 1,  "ORDER_CHANGE",             "订单变更",           S8SceneCode.S1, 60,  "ROLE_ORDER_PLANNER",      "MEDIUM", 100, ct),
-            T(baseId + 2,  "DELIVERY_DELAY",           "交期延迟",           S8SceneCode.S7, 120, "ROLE_ORDER_PLANNER",      "HIGH",   101, ct),
-            T(baseId + 3,  "PENDING_SHIPMENT",         "入库待发",           S8SceneCode.S7, 240, "ROLE_WH_OUTBOUND",        "MEDIUM", 102, ct),
-            T(baseId + 16, "ORDER_REVIEW_DELAY",       "订单评审延迟",       S8SceneCode.S1, 120, "ROLE_ORDER_PLANNER",      "MEDIUM", 400, ct),
+            T(baseId + 1,  "ORDER_CHANGE",             "订单变更",           S8SceneCode.S1, 60,  "ROLE_ORDER_PLANNER",      "MEDIUM", 100, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
+            T(baseId + 2,  "DELIVERY_DELAY",           "交期延迟",           S8SceneCode.S7, 120, "ROLE_ORDER_PLANNER",      "HIGH",   101, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
+            T(baseId + 3,  "PENDING_SHIPMENT",         "入库待发",           S8SceneCode.S7, 240, "ROLE_WH_OUTBOUND",        "MEDIUM", 102, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
+            T(baseId + 16, "ORDER_REVIEW_DELAY",       "订单评审延迟",       S8SceneCode.S1, 120, "ROLE_ORDER_PLANNER",      "MEDIUM", 400, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
 
             // ── S2 制造协同 + S6 生产执行 ──
-            T(baseId + 4,  "EQUIP_FAULT",              "设备异常",           S8SceneCode.S2, 30,  "ROLE_EQUIP_MAINT",        "HIGH",   200, ct),
+            T(baseId + 4,  "EQUIP_FAULT",              "设备异常",           S8SceneCode.S2, 30,  "ROLE_EQUIP_MAINT",        "HIGH",   200, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
             // S8-DEPRECATED-EXCEPTION-TYPE-MIGRATE-1:DIMENSION_DEVIATION/QUALITY_DEFECT 迁 MFG_QUALITY_ABNORMAL
             T(baseId + 14, "DIMENSION_DEVIATION",      "尺寸超差",           S8SceneCode.S2, 60,  "ROLE_QC",                 "HIGH",   200, ct, enabled: false, remark: "[DEPRECATED] migrated to MFG_QUALITY_ABNORMAL"),
             // MATERIAL_SHORTAGE 迁 MFG_MATERIAL_ABNORMAL
@@ -47,62 +51,62 @@ public class S8ExceptionTypeSeedData : ISqlSugarEntitySeedData<Entity.S8.AdoS8Ex
             T(baseId + 6,  "QUALITY_DEFECT",           "质量异常",           S8SceneCode.S2, 60,  "ROLE_QC",                 "HIGH",   202, ct, enabled: false, remark: "[DEPRECATED] migrated to MFG_QUALITY_ABNORMAL"),
             // YIELD_DEFICIT 迁 PRODUCTION_QUALITY_ABNORMAL
             T(baseId + 15, "YIELD_DEFICIT",            "良率不足",           S8SceneCode.S6, 60,  "ROLE_QC",                 "HIGH",   300, ct, enabled: false, remark: "[DEPRECATED] migrated to PRODUCTION_QUALITY_ABNORMAL"),
-            T(baseId + 19, "WORK_ORDER_DELAY",         "工单延期",           S8SceneCode.S6, 60,  "ROLE_PRODUCTION_PLANNER", "HIGH",   403, ct),
+            T(baseId + 19, "WORK_ORDER_DELAY",         "工单延期",           S8SceneCode.S6, 60,  "ROLE_PRODUCTION_PLANNER", "HIGH",   403, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
 
             // ── S3 供应协同 + S4 采购执行 + S5 物料仓储 ──
-            T(baseId + 7,  "SUPPLIER_ETA_ISSUE",       "供应商回复交期异常", S8SceneCode.S3, 240, "ROLE_PURCHASER",          "MEDIUM", 300, ct),
-            T(baseId + 8,  "SUPPLIER_SHIP_ISSUE",      "供应商发货异常",     S8SceneCode.S3, 240, "ROLE_PURCHASER",          "MEDIUM", 301, ct),
+            T(baseId + 7,  "SUPPLIER_ETA_ISSUE",       "供应商回复交期异常", S8SceneCode.S3, 240, "ROLE_PURCHASER",          "MEDIUM", 300, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 8,  "SUPPLIER_SHIP_ISSUE",      "供应商发货异常",     S8SceneCode.S3, 240, "ROLE_PURCHASER",          "MEDIUM", 301, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
             // WH_INBOUND_ISSUE 迁 WAREHOUSE_RECEIPT_ABNORMAL
             T(baseId + 9,  "WH_INBOUND_ISSUE",         "仓库收货异常",       S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "MEDIUM", 302, ct, enabled: false, remark: "[DEPRECATED] migrated to WAREHOUSE_RECEIPT_ABNORMAL"),
-            T(baseId + 10, "IQC_ISSUE",                "IQC 检验异常",       S8SceneCode.S3, 120, "ROLE_QC",                 "MEDIUM", 303, ct),
-            T(baseId + 11, "WH_PUTAWAY_ISSUE",         "仓库上架入库异常",   S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "LOW",    304, ct),
+            T(baseId + 10, "IQC_ISSUE",                "IQC 检验异常",       S8SceneCode.S3, 120, "ROLE_QC",                 "MEDIUM", 303, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 11, "WH_PUTAWAY_ISSUE",         "仓库上架入库异常",   S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "LOW",    304, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
             // WH_KIT_ISSUE 迁 WORK_ORDER_KITTING_ABNORMAL
             T(baseId + 12, "WH_KIT_ISSUE",             "仓库工单备料异常",   S8SceneCode.S5, 60,  "ROLE_WH_OUTBOUND",        "MEDIUM", 305, ct, enabled: false, remark: "[DEPRECATED] migrated to WORK_ORDER_KITTING_ABNORMAL"),
             // WH_ISSUE_OUT_ISSUE 迁 WORK_ORDER_ISSUE_ABNORMAL
             T(baseId + 13, "WH_ISSUE_OUT_ISSUE",       "仓库工单发料异常",   S8SceneCode.S7, 60,  "ROLE_WH_OUTBOUND",        "MEDIUM", 306, ct, enabled: false, remark: "[DEPRECATED] migrated to WORK_ORDER_ISSUE_ABNORMAL"),
-            T(baseId + 17, "PURCHASE_EXECUTION_DELAY", "采购执行延迟",       S8SceneCode.S4, 120, "ROLE_PURCHASER",          "MEDIUM", 401, ct),
-            T(baseId + 18, "PURCHASE_QUALITY_ABNORMAL","采购质量异常",       S8SceneCode.S4, 60,  "ROLE_QC",                 "MEDIUM", 402, ct),
-            T(baseId + 20, "MATERIAL_STOCK_ABNORMAL",  "库存异常",           S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "MEDIUM", 404, ct),
+            T(baseId + 17, "PURCHASE_EXECUTION_DELAY", "采购执行延迟",       S8SceneCode.S4, 120, "ROLE_PURCHASER",          "MEDIUM", 401, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 18, "PURCHASE_QUALITY_ABNORMAL","采购质量异常",       S8SceneCode.S4, 60,  "ROLE_QC",                 "MEDIUM", 402, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 20, "MATERIAL_STOCK_ABNORMAL",  "库存异常",           S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "MEDIUM", 404, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
 
             // ── S8-BASELINE-EXCEPTION-TYPE-RULE-TEMPLATE-UPDATE-1(基于 lwb/S1-S7异常类型与三类规则对应表.md)──
             // S1 产销协同(新增 3)
-            T(baseId + 21, "ORDER_DUE_DATE_DELAY",                         "订单交期延迟",           S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "HIGH",   110, ct),
-            T(baseId + 22, "ORDER_DELIVERY_DELAY_WARNING",                 "订单交付延期预警",       S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "MEDIUM", 111, ct),
-            T(baseId + 23, "DELIVERY_SATISFACTION_RATE_ABNORMAL",          "交付满足率异常",         S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "HIGH",   112, ct),
+            T(baseId + 21, "ORDER_DUE_DATE_DELAY",                         "订单交期延迟",           S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "HIGH",   110, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
+            T(baseId + 22, "ORDER_DELIVERY_DELAY_WARNING",                 "订单交付延期预警",       S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "MEDIUM", 111, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
+            T(baseId + 23, "DELIVERY_SATISFACTION_RATE_ABNORMAL",          "交付满足率异常",         S8SceneCode.S1, 240, "ROLE_ORDER_PLANNER",      "HIGH",   112, ct, monitoringCategoryKey: S8MonitoringCategory.OrderReview),
             // S2 制造协同(新增 4)
-            T(baseId + 24, "WORK_ORDER_COMPLETION_DELAY_WARNING",          "工单完工延期预警",       S8SceneCode.S2, 120, "ROLE_PRODUCTION_PLANNER", "MEDIUM", 210, ct),
-            T(baseId + 25, "WORK_ORDER_COMPLETION_RATE_ABNORMAL",          "工单完工满足率异常",     S8SceneCode.S2, 240, "ROLE_PRODUCTION_PLANNER", "HIGH",   211, ct),
-            T(baseId + 26, "MFG_MATERIAL_ABNORMAL",                        "制造协同物料异常",       S8SceneCode.S2,  60, "ROLE_PRODUCTION_PLANNER", "HIGH",   212, ct),
-            T(baseId + 27, "MFG_QUALITY_ABNORMAL",                         "制造协同质量异常",       S8SceneCode.S2,  60, "ROLE_QC",                 "HIGH",   213, ct),
+            T(baseId + 24, "WORK_ORDER_COMPLETION_DELAY_WARNING",          "工单完工延期预警",       S8SceneCode.S2, 120, "ROLE_PRODUCTION_PLANNER", "MEDIUM", 210, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 25, "WORK_ORDER_COMPLETION_RATE_ABNORMAL",          "工单完工满足率异常",     S8SceneCode.S2, 240, "ROLE_PRODUCTION_PLANNER", "HIGH",   211, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 26, "MFG_MATERIAL_ABNORMAL",                        "制造协同物料异常",       S8SceneCode.S2,  60, "ROLE_PRODUCTION_PLANNER", "HIGH",   212, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 27, "MFG_QUALITY_ABNORMAL",                         "制造协同质量异常",       S8SceneCode.S2,  60, "ROLE_QC",                 "HIGH",   213, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
             // S3 供应协同(新增 1)
-            T(baseId + 28, "SUPPLIER_DELIVERY_DELAY_WARNING",              "供应商交付延期预警",     S8SceneCode.S3, 240, "ROLE_PURCHASER",          "MEDIUM", 310, ct),
+            T(baseId + 28, "SUPPLIER_DELIVERY_DELAY_WARNING",              "供应商交付延期预警",     S8SceneCode.S3, 240, "ROLE_PURCHASER",          "MEDIUM", 310, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
             // S4 采购执行(新增 2)
-            T(baseId + 29, "PURCHASE_DELIVERY_ABNORMAL",                   "采购交期异常",           S8SceneCode.S4, 120, "ROLE_PURCHASER",          "HIGH",   410, ct),
-            T(baseId + 30, "PURCHASE_ARRIVAL_ABNORMAL",                    "采购到货异常",           S8SceneCode.S4, 120, "ROLE_PURCHASER",          "MEDIUM", 411, ct),
+            T(baseId + 29, "PURCHASE_DELIVERY_ABNORMAL",                   "采购交期异常",           S8SceneCode.S4, 120, "ROLE_PURCHASER",          "HIGH",   410, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 30, "PURCHASE_ARRIVAL_ABNORMAL",                    "采购到货异常",           S8SceneCode.S4, 120, "ROLE_PURCHASER",          "MEDIUM", 411, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
             // S5 物料仓储(新增 7)
-            T(baseId + 31, "WAREHOUSE_RECEIPT_ABNORMAL",                   "仓库收货异常",           S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "MEDIUM", 510, ct),
-            T(baseId + 32, "WORK_ORDER_KITTING_ABNORMAL",                  "工单备料异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "MEDIUM", 511, ct),
-            T(baseId + 33, "WORK_ORDER_ISSUE_ABNORMAL",                    "工单发料异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "MEDIUM", 512, ct),
-            T(baseId + 34, "MATERIAL_KITTING_ABNORMAL",                    "物料齐套异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "HIGH",   513, ct),
-            T(baseId + 35, "MATERIAL_KITTING_DELAY_WARNING",               "物料齐套延期预警",       S8SceneCode.S5, 120, "ROLE_WH_OUTBOUND",        "MEDIUM", 514, ct),
-            T(baseId + 36, "INVENTORY_TURNOVER_ABNORMAL",                  "库存周转异常",           S8SceneCode.S5, 240, "ROLE_WH_INBOUND",         "MEDIUM", 515, ct),
-            T(baseId + 37, "INVENTORY_AMOUNT_LEVEL_ABNORMAL",              "库存金额水位异常",       S8SceneCode.S5, 240, "ROLE_WH_INBOUND",         "MEDIUM", 516, ct),
+            T(baseId + 31, "WAREHOUSE_RECEIPT_ABNORMAL",                   "仓库收货异常",           S8SceneCode.S5, 120, "ROLE_WH_INBOUND",         "MEDIUM", 510, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 32, "WORK_ORDER_KITTING_ABNORMAL",                  "工单备料异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "MEDIUM", 511, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 33, "WORK_ORDER_ISSUE_ABNORMAL",                    "工单发料异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "MEDIUM", 512, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 34, "MATERIAL_KITTING_ABNORMAL",                    "物料齐套异常",           S8SceneCode.S5,  60, "ROLE_WH_OUTBOUND",        "HIGH",   513, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 35, "MATERIAL_KITTING_DELAY_WARNING",               "物料齐套延期预警",       S8SceneCode.S5, 120, "ROLE_WH_OUTBOUND",        "MEDIUM", 514, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 36, "INVENTORY_TURNOVER_ABNORMAL",                  "库存周转异常",           S8SceneCode.S5, 240, "ROLE_WH_INBOUND",         "MEDIUM", 515, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
+            T(baseId + 37, "INVENTORY_AMOUNT_LEVEL_ABNORMAL",              "库存金额水位异常",       S8SceneCode.S5, 240, "ROLE_WH_INBOUND",         "MEDIUM", 516, ct, monitoringCategoryKey: S8MonitoringCategory.MaterialPurchase),
             // S6 生产执行(新增 4)
-            T(baseId + 38, "PRODUCTION_MATERIAL_ABNORMAL",                  "生产物料异常",           S8SceneCode.S6,  60, "ROLE_PRODUCTION_PLANNER", "HIGH",   610, ct),
-            T(baseId + 39, "PRODUCTION_QUALITY_ABNORMAL",                   "生产质量异常",           S8SceneCode.S6,  60, "ROLE_QC",                 "HIGH",   611, ct),
-            T(baseId + 40, "PRODUCTION_WORK_ORDER_COMPLETION_DELAY",        "生产工单完工延期",       S8SceneCode.S6,  60, "ROLE_PRODUCTION_PLANNER", "HIGH",   612, ct),
-            T(baseId + 41, "PRODUCTION_WORK_ORDER_COMPLETION_RATE_ABNORMAL","生产工单完工满足率异常", S8SceneCode.S6, 240, "ROLE_PRODUCTION_PLANNER", "HIGH",   613, ct),
+            T(baseId + 38, "PRODUCTION_MATERIAL_ABNORMAL",                  "生产物料异常",           S8SceneCode.S6,  60, "ROLE_PRODUCTION_PLANNER", "HIGH",   610, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 39, "PRODUCTION_QUALITY_ABNORMAL",                   "生产质量异常",           S8SceneCode.S6,  60, "ROLE_QC",                 "HIGH",   611, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 40, "PRODUCTION_WORK_ORDER_COMPLETION_DELAY",        "生产工单完工延期",       S8SceneCode.S6,  60, "ROLE_PRODUCTION_PLANNER", "HIGH",   612, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
+            T(baseId + 41, "PRODUCTION_WORK_ORDER_COMPLETION_RATE_ABNORMAL","生产工单完工满足率异常", S8SceneCode.S6, 240, "ROLE_PRODUCTION_PLANNER", "HIGH",   613, ct, monitoringCategoryKey: S8MonitoringCategory.BodyProduction),
             // S7 成品仓储(新增 3)
-            T(baseId + 42, "FINISHED_GOODS_PENDING_SHIPMENT",              "成品待发异常",           S8SceneCode.S7, 240, "ROLE_WH_OUTBOUND",        "MEDIUM", 710, ct),
-            T(baseId + 43, "SHIPMENT_ABNORMAL",                            "出货异常",               S8SceneCode.S7, 120, "ROLE_WH_OUTBOUND",        "HIGH",   711, ct),
-            T(baseId + 44, "ORDER_DELIVERY_RATE_ABNORMAL",                 "订单交付满足率异常",     S8SceneCode.S7, 240, "ROLE_ORDER_PLANNER",      "HIGH",   712, ct),
+            T(baseId + 42, "FINISHED_GOODS_PENDING_SHIPMENT",              "成品待发异常",           S8SceneCode.S7, 240, "ROLE_WH_OUTBOUND",        "MEDIUM", 710, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
+            T(baseId + 43, "SHIPMENT_ABNORMAL",                            "出货异常",               S8SceneCode.S7, 120, "ROLE_WH_OUTBOUND",        "HIGH",   711, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
+            T(baseId + 44, "ORDER_DELIVERY_RATE_ABNORMAL",                 "订单交付满足率异常",     S8SceneCode.S7, 240, "ROLE_ORDER_PLANNER",      "HIGH",   712, ct, monitoringCategoryKey: S8MonitoringCategory.FinalAssemblyDelivery),
         };
     }
 
     private static Entity.S8.AdoS8ExceptionType T(
         long id, string typeCode, string typeName, string sceneCode,
         int slaMinutes, string ownerRoleCode, string severityDefault, int sortNo, DateTime ct,
-        bool enabled = true, string? remark = null) =>
+        bool enabled = true, string? remark = null, string? monitoringCategoryKey = null) =>
         new()
         {
             Id = id,
@@ -121,6 +125,7 @@ public class S8ExceptionTypeSeedData : ISqlSugarEntitySeedData<Entity.S8.AdoS8Ex
             Enabled = enabled,
             SortNo = sortNo,
             Remark = remark,
+            MonitoringCategoryKey = monitoringCategoryKey,
             CreatedAt = ct,
         };
 }

+ 13 - 3
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8MonitoringService.cs

@@ -138,9 +138,19 @@ public class S8MonitoringService : ITransient
     }
 
     /// <summary>异常类型 → 业务类别(与 Overview 页 5 张类别卡对应)。
-    /// S8-DEPRECATED-EXCEPTION-TYPE-MIGRATE-1:补 7 个迁移目标 type_code 的归类,并对旧 7 条保留兼容
-    /// (历史 active 异常迁移期间 / 重建 DB 期间都不丢类别卡)。</summary>
-    private static string CategoryOf(AdoS8ExceptionType t) => t.TypeCode switch
+    /// 优先级:配置 (AdoS8ExceptionType.MonitoringCategoryKey → KeyToLabel) → legacy hardcode fallback → string.Empty。
+    /// 配置缺失或非法 key 时由 CategoryOfLegacy 兜底,保证演示链路不丢类别卡。</summary>
+    private static string CategoryOf(AdoS8ExceptionType? t)
+    {
+        if (t is null) return string.Empty;
+        var configured = S8MonitoringCategory.KeyToLabel(t.MonitoringCategoryKey);
+        if (!string.IsNullOrEmpty(configured)) return configured;
+        return CategoryOfLegacy(t.TypeCode);
+    }
+
+    /// <summary>Legacy hardcode 映射,保留现有 15 条 enabled + 7 条 deprecated 兼容分支。
+    /// 不在此处补新映射;新 type_code 通过 monitoring_category_key 配置驱动。</summary>
+    private static string CategoryOfLegacy(string? typeCode) => typeCode switch
     {
         // 订单评审
         "ORDER_CHANGE"                              => "订单评审",