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

feat(s8): t3j expose order flow fields in exceptions

- AdoS8ExceptionQueryDto: add OrderFlowCode/StageCode/RuleMechanism
- AdoS8ExceptionListItemDto: add RelatedObjectCode/OrderFlowCode/StageCode/RuleMechanism
- AdoS8ExceptionDetailDto: inherit RelatedObjectCode from parent; relies on parent for the other 3 added fields
- S8ExceptionService.GetPagedAsync: add 3 WhereIF + 4 projections; GetDetailAsync: add 3 projections
- s8ExceptionApi S8ExceptionRow: add 5 fields
- orderExecution store: add selectOrderForChainByCode(orderCode)
- S8ExceptionListPage: add order code + order-flow filter and table columns
- S8TaskDetailPage: rule detection card adds 3 fields + "查看订单链路" button (SALES_ORDER + relatedObjectCode gate)
- bump Web 2.4.128 / server 1.0.98
YY968XX 3 месяцев назад
Родитель
Сommit
a61138270b

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
 	"name": "admin.net",
 	"type": "module",
-	"version": "2.4.127",
+	"version": "2.4.128",
 	"packageManager": "pnpm@10.32.1",
 	"lastBuildTime": "2026.03.15",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 10 - 0
Web/src/stores/orderExecution.ts

@@ -345,6 +345,16 @@ export const useOrderExecutionStore = defineStore('orderExecution', {
 				orders: this.filteredOrders,
 			});
 		},
+		// t3j:从异常详情等"只有 orderCode 字符串"的入口进入 Chain 页时,
+		// 写入最小导航状态;Chain 页 onMounted 会 loadFromDomain + loadChainFromDomain(focused)。
+		selectOrderForChainByCode(orderCode: string) {
+			if (!orderCode) return;
+			this.selectedOrderNo = orderCode;
+			Session.set(NAV_STATE_KEY, {
+				selectedOrderNo: orderCode,
+				orders: [],
+			});
+		},
 		restoreChainSelection() {
 			const snap = Session.get(NAV_STATE_KEY) as
 				| { selectedOrderNo?: string; orders?: SalesOrderExecution[] }

+ 6 - 0
Web/src/views/aidop/s8/api/s8ExceptionApi.ts

@@ -54,6 +54,12 @@ export interface S8ExceptionRow {
 	dedupKey?: string | null;
 	lastDetectedAt?: string | null;
 	ruleType?: string | null;
+	// ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:异常列表/详情携带订单链路字段。
+	relatedObjectCode?: string | null;
+	orderFlowCode?: string | null;
+	orderFlowName?: string | null;
+	stageCode?: string | null;
+	ruleMechanism?: string | null;
 }
 
 export interface S8DecisionRow {

+ 36 - 0
Web/src/views/aidop/s8/exceptions/S8ExceptionListPage.vue

@@ -19,6 +19,15 @@
 					<el-option v-for="m in filterOpts.modules" :key="m.value" :label="m.label" :value="m.value" />
 				</el-select>
 			</el-form-item>
+			<!-- ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j -->
+			<el-form-item label="订单号">
+				<el-input v-model="query.relatedObjectCode" placeholder="订单号 / 关联对象" clearable style="width: 180px" />
+			</el-form-item>
+			<el-form-item label="链路阶段">
+				<el-select v-model="query.orderFlowCode" clearable placeholder="全部" style="width: 160px">
+					<el-option v-for="o in ORDER_FLOW_OPTIONS" :key="o.value" :label="o.label" :value="o.value" />
+				</el-select>
+			</el-form-item>
 			<el-form-item label="部门">
 				<el-select v-model="query.deptId" clearable placeholder="全部" style="width: 180px" filterable>
 					<el-option v-for="s in filterOpts.departments" :key="s.value" :label="s.label" :value="s.value" />
@@ -70,6 +79,13 @@
 			<el-table-column label="模块" width="120" show-overflow-tooltip>
 				<template #default="{ row }">{{ row.moduleName || row.moduleCode || row.sceneName || row.sceneCode || '-' }}</template>
 			</el-table-column>
+			<!-- ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:订单链路列 -->
+			<el-table-column label="订单号" width="140" show-overflow-tooltip>
+				<template #default="{ row }">{{ row.relatedObjectCode || '-' }}</template>
+			</el-table-column>
+			<el-table-column label="链路阶段" width="130" show-overflow-tooltip>
+				<template #default="{ row }">{{ row.orderFlowName || orderFlowLabel(row.orderFlowCode) || '-' }}</template>
+			</el-table-column>
 			<el-table-column prop="statusLabel" label="状态" width="100" />
 			<el-table-column prop="severityLabel" label="严重度" width="90" />
 			<el-table-column prop="priorityLevel" label="优先级" width="90" />
@@ -163,12 +179,28 @@ const query = reactive({
 	includeUnclassified: false,
 	beginTime: '' as string,
 	endTime: '' as string,
+	// t3j:订单链路筛选
+	relatedObjectCode: '' as string,
+	orderFlowCode: '' as string,
 	page: 1,
 	pageSize: 20,
 	tenantId: 1,
 	factoryId: 1,
 });
 
+// t3j:ORDER_FLOW 协议枚举 → 中文 label;与 OrderFlowConstants 同义,仅供 UI 展示。
+const ORDER_FLOW_OPTIONS: { value: string; label: string }[] = [
+	{ value: 'ORDER_REVIEW_PLAN_CALC', label: '订单评审' },
+	{ value: 'PRODUCT_DESIGN', label: '产品设计' },
+	{ value: 'MATERIAL_PURCHASE', label: '材料采购' },
+	{ value: 'BODY_PRODUCTION', label: '本体生产' },
+	{ value: 'FINAL_ASSEMBLY_DELIVERY', label: '总装发货' },
+];
+function orderFlowLabel(code: string | null | undefined): string {
+	if (!code) return '';
+	return ORDER_FLOW_OPTIONS.find((o) => o.value === code)?.label || code;
+}
+
 // S8-DEPT-DISPLAY-CONSISTENCY-1(P0-A-2):部门展示 fallback —— 名→部门ID:{id}→未归属。后端已按 factory_ref_id 水合。
 function deptDisplay(name?: string | null, id?: number | null) {
 	if (name && name.trim()) return name;
@@ -233,6 +265,8 @@ async function loadList() {
 			includeUnclassified: query.includeUnclassified || undefined,
 			beginTime: beginStr || undefined,
 			endTime: endStr || undefined,
+			relatedObjectCode: query.relatedObjectCode || undefined,
+			orderFlowCode: query.orderFlowCode || undefined,
 		});
 		rows.value = res.list;
 		total.value = res.total;
@@ -255,6 +289,8 @@ function resetQuery() {
 	query.includeUnclassified = false;
 	query.beginTime = '';
 	query.endTime = '';
+	query.relatedObjectCode = '';
+	query.orderFlowCode = '';
 	dateStart.value = null;
 	dateEnd.value = null;
 	query.page = 1;

+ 40 - 1
Web/src/views/aidop/s8/exceptions/S8TaskDetailPage.vue

@@ -6,8 +6,33 @@ import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import ApprovalPanel from '/@/views/approvalFlow/component/ApprovalPanel.vue';
 import { s8ExceptionApi, type S8DecisionRow, type S8EvidenceRow } from '../api/s8ExceptionApi';
 import { useUserInfo } from '/@/stores/userInfo';
+import { useOrderExecutionStore } from '/@/stores/orderExecution';
 
 const userStore = useUserInfo();
+const orderExecutionStore = useOrderExecutionStore();
+
+// ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:ORDER_FLOW 协议 code → 中文 label。
+const ORDER_FLOW_NAME_MAP: Record<string, string> = {
+	ORDER_REVIEW_PLAN_CALC: '订单评审',
+	PRODUCT_DESIGN: '产品设计',
+	MATERIAL_PURCHASE: '材料采购',
+	BODY_PRODUCTION: '本体生产',
+	FINAL_ASSEMBLY_DELIVERY: '总装发货',
+};
+function orderFlowLabel(code: string | null | undefined): string {
+	if (!code) return '';
+	return ORDER_FLOW_NAME_MAP[code] || code;
+}
+const canViewOrderChain = computed(() => {
+	const d = detail.value;
+	return !!d && d.sourceObjectType === 'SALES_ORDER' && !!d.relatedObjectCode;
+});
+function onViewOrderChain() {
+	const code = String(detail.value?.relatedObjectCode || '');
+	if (!code) return;
+	orderExecutionStore.selectOrderForChainByCode(code);
+	router.push('/aidop/s8/monitoring/order-execution/chain');
+}
 
 const route = useRoute();
 const router = useRouter();
@@ -273,7 +298,17 @@ onMounted(async () => {
 			<el-row :gutter="16" class="mt16">
 				<el-col :span="24">
 					<el-card shadow="never">
-						<template #header>规则检测信息</template>
+						<template #header>
+							<span>规则检测信息</span>
+							<!-- ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:仅在 SALES_ORDER + relatedObjectCode 非空时显示 -->
+							<el-button
+								v-if="canViewOrderChain"
+								type="primary"
+								link
+								style="margin-left: 12px"
+								@click="onViewOrderChain"
+							>查看订单链路 ›</el-button>
+						</template>
 						<el-descriptions :column="3" border>
 							<el-descriptions-item label="规则类型">
 								<el-tag v-if="detail.ruleType" :type="ruleTypeTagType(detail.ruleType)" size="small">{{ ruleTypeLabel(detail.ruleType) }}</el-tag>
@@ -282,6 +317,10 @@ onMounted(async () => {
 							<el-descriptions-item label="规则编码">{{ detail.sourceRuleCode || '—' }}</el-descriptions-item>
 							<el-descriptions-item label="来源对象类型">{{ detail.sourceObjectType || '—' }}</el-descriptions-item>
 							<el-descriptions-item label="来源对象 ID">{{ detail.sourceObjectId || '—' }}</el-descriptions-item>
+							<!-- t3j:订单链路字段 -->
+							<el-descriptions-item label="订单号">{{ detail.relatedObjectCode || '—' }}</el-descriptions-item>
+							<el-descriptions-item label="链路阶段">{{ detail.orderFlowName || orderFlowLabel(detail.orderFlowCode) || '—' }}</el-descriptions-item>
+							<el-descriptions-item label="规则机制">{{ detail.ruleMechanism || '—' }}</el-descriptions-item>
 							<el-descriptions-item label="最近检测时间">{{ detail.lastDetectedAt || '—' }}</el-descriptions-item>
 							<el-descriptions-item label="恢复时间">
 								<el-tag v-if="detail.recoveredAt" type="success" size="small">{{ detail.recoveredAt }}</el-tag>

+ 3 - 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.97</AssemblyVersion>
-    <FileVersion>1.0.97</FileVersion>
-    <Version>1.0.97</Version>
+    <AssemblyVersion>1.0.98</AssemblyVersion>
+    <FileVersion>1.0.98</FileVersion>
+    <Version>1.0.98</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 14 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8Dtos.cs

@@ -26,6 +26,12 @@ public class AdoS8ExceptionQueryDto
     /// <summary>S8-DASHBOARD-DATA-ALIGN-S1S7-1:仅返回 module_code IN S1-S7 的异常;看板明细表传 true 与 KPI 口径对齐。
     /// 默认 false 以保证异常列表页(/aidop/s8/exceptions)行为不变。</summary>
     public bool? OnlyS1S7Modules { get; set; }
+    /// <summary>ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:按订单链路阶段(大写 ORDER_FLOW code)筛选。</summary>
+    public string? OrderFlowCode { get; set; }
+    /// <summary>ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:按 S_STAGE 阶段筛选。</summary>
+    public string? StageCode { get; set; }
+    /// <summary>ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:按规则机制筛选。</summary>
+    public string? RuleMechanism { get; set; }
     public int Page { get; set; } = 1;
     public int PageSize { get; set; } = 20;
 }
@@ -65,6 +71,14 @@ public class AdoS8ExceptionListItemDto
     public string? DedupKey { get; set; }
     public DateTime? LastDetectedAt { get; set; }
     public string? RuleType { get; set; }
+    /// <summary>ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:关联对象编码(订单链路场景下为 order_code)。</summary>
+    public string? RelatedObjectCode { get; set; }
+    /// <summary>ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:订单链路大写 code(5 阶段协议枚举)。</summary>
+    public string? OrderFlowCode { get; set; }
+    /// <summary>ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:S_STAGE 阶段。</summary>
+    public string? StageCode { get; set; }
+    /// <summary>ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3j:规则机制。</summary>
+    public string? RuleMechanism { get; set; }
 }
 
 public class AdoS8ExceptionDetailDto : AdoS8ExceptionListItemDto
@@ -85,7 +99,6 @@ public class AdoS8ExceptionDetailDto : AdoS8ExceptionListItemDto
     public string? VerificationResult { get; set; }
     public string? VerificationRemark { get; set; }
     public long? SourceRuleId { get; set; }
-    public string? RelatedObjectCode { get; set; }
 }
 
 public class AdoS8ManualReportCreateDto

+ 11 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8ExceptionService.cs

@@ -63,6 +63,9 @@ public class S8ExceptionService : ITransient
             .WhereIF(q.EndTime.HasValue, (e, sc, wr) => e.CreatedAt <= q.EndTime!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.ProcessNodeCode), (e, sc, wr) => e.ProcessNodeCode == q.ProcessNodeCode)
             .WhereIF(!string.IsNullOrWhiteSpace(q.RelatedObjectCode), (e, sc, wr) => e.RelatedObjectCode == q.RelatedObjectCode)
+            .WhereIF(!string.IsNullOrWhiteSpace(q.OrderFlowCode), (e, sc, wr) => e.OrderFlowCode == q.OrderFlowCode)
+            .WhereIF(!string.IsNullOrWhiteSpace(q.StageCode), (e, sc, wr) => e.StageCode == q.StageCode)
+            .WhereIF(!string.IsNullOrWhiteSpace(q.RuleMechanism), (e, sc, wr) => e.RuleMechanism == q.RuleMechanism)
             .WhereIF(q.RecoveredStatus == "RECOVERED", (e, sc, wr) => e.RecoveredAt != null)
             .WhereIF(q.RecoveredStatus == "ACTIVE", (e, sc, wr) => e.RecoveredAt == null)
             .WhereIF(!string.IsNullOrWhiteSpace(q.RuleType), (e, sc, wr) => wr.RuleType == q.RuleType)
@@ -100,7 +103,11 @@ public class S8ExceptionService : ITransient
                 SourceObjectId = e.SourceObjectId,
                 DedupKey = e.DedupKey,
                 LastDetectedAt = e.LastDetectedAt,
-                RuleType = wr.RuleType
+                RuleType = wr.RuleType,
+                RelatedObjectCode = e.RelatedObjectCode,
+                OrderFlowCode = e.OrderFlowCode,
+                StageCode = e.StageCode,
+                RuleMechanism = e.RuleMechanism,
             })
             .ToPageListAsync(q.Page, q.PageSize);
 
@@ -199,6 +206,9 @@ public class S8ExceptionService : ITransient
                 SourceObjectId = e.SourceObjectId,
                 ExceptionTypeCode = e.ExceptionTypeCode,
                 RuleType = wr.RuleType,
+                OrderFlowCode = e.OrderFlowCode,
+                StageCode = e.StageCode,
+                RuleMechanism = e.RuleMechanism,
             })
             .Take(1)
             .ToListAsync();