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

feat(s8): t3i wire order chain overview to domain api

- domainMapper: add substep/unit/lifecycle-with-substeps + aggregate→legacy baseline mappers (ownerDept='--' fallback)
- orderExecution store: add loadAggregateFromDomain(scope) + loadChainFromDomain(orderCode) with per-order subSteps cache
- OrderChainOverviewPage: onMounted/watch/onRetry switch to domain flow; legacy demo fallback preserved
- bump Web 2.4.127
YY968XX 3 недель назад
Родитель
Сommit
8773aa79a1

+ 1 - 1
Web/package.json

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

+ 37 - 1
Web/src/stores/orderExecution.ts

@@ -28,9 +28,17 @@ import {
 } from '/@/views/aidop/s8/api/s8OrderFlowApi';
 // ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3g:正式 API client + mapper,loadFromDomain 旁路旧 demo 走正式端点。
 // t3h:增订单详情按需加载,展开订单时调 getOrderFlowOrder 注入 lifecycle。
-import { getOrderFlowOrder, getOrderFlowOrders } from '/@/views/aidop/s8/api/s8OrderFlowDomainApi';
+// t3i:Chain 页 baseline + 单订单 chain(含 substeps/units)切正式 API。
 import {
+	getOrderFlowAggregate,
+	getOrderFlowChain,
+	getOrderFlowOrder,
+	getOrderFlowOrders,
+} from '/@/views/aidop/s8/api/s8OrderFlowDomainApi';
+import {
+	mapDomainAggregateToLegacyBaseline,
 	mapDomainOrderDetailLifecycle,
+	mapDomainOrderDetailLifecycleWithSubsteps,
 	mapDomainOrderListItem,
 } from '/@/views/aidop/s8/monitoring/data/order-execution/domainMapper';
 
@@ -296,6 +304,34 @@ export const useOrderExecutionStore = defineStore('orderExecution', {
 				this.loadError = err instanceof Error ? err.message : String(err);
 			}
 		},
+		// t3i:Chain 页 baseline 矩阵;mapper 输出兼容旧 S8OrderFlowAggregateSnapshotDto,ChainBaselineMatrix 无须改。
+		async loadAggregateFromDomain(scope: string = 'BASELINE_PPT') {
+			try {
+				const agg = await getOrderFlowAggregate({ scope });
+				this.aggregateSnapshot = mapDomainAggregateToLegacyBaseline(agg);
+			} catch (err: unknown) {
+				this.loadError = err instanceof Error ? err.message : String(err);
+			}
+		},
+		// t3i:单订单 chain(含 substeps/units)按需加载;该订单 lifecycle 已有 subSteps 则跳过。
+		async loadChainFromDomain(orderCode: string) {
+			if (!orderCode) return;
+			const index = this.orders.findIndex((o) => o.soNo === orderCode);
+			if (index < 0) return;
+			const existing = this.orders[index].lifecycle;
+			const hasSubSteps = existing.some((s) => Array.isArray(s.subSteps) && s.subSteps.length > 0);
+			if (hasSubSteps) return;
+			try {
+				const chain = await getOrderFlowChain(orderCode);
+				if (!chain?.order) return;
+				const lifecycle = mapDomainOrderDetailLifecycleWithSubsteps(chain.order);
+				const next = this.orders.slice();
+				next[index] = { ...next[index], lifecycle };
+				this.orders = next;
+			} catch (err: unknown) {
+				this.loadError = err instanceof Error ? err.message : String(err);
+			}
+		},
 		setFilters(partial: Partial<OrderExecutionFilters>) {
 			this.filters = { ...this.filters, ...partial };
 		},

+ 22 - 8
Web/src/views/aidop/s8/monitoring/OrderChainOverviewPage.vue

@@ -41,22 +41,30 @@ const ALL_STAGE_KEYS: OrderNodeKey[] = [
 const store = useOrderExecutionStore();
 const router = useRouter();
 
-onMounted(() => {
-	store.loadFixture();
+onMounted(async () => {
+	// ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3i:Chain 页改走正式 API;旧 loadFixture/loadFromBackend 保留在 store 内作 fallback。
+	await store.loadFromDomain();
+	void store.loadAggregateFromDomain('BASELINE_PPT');
 	store.restoreChainSelection();
-	// ORDER-FLOW-CHAIN-ENTRY-FOCUS-1:从订单行进入时默认收敛到该订单视角,避免一进来就是 105/105 聚合。
 	const focused = store.selectedOrderNo;
-	if (focused && !selectedOrderNos.value.includes(focused)) {
-		selectedOrderNos.value = [focused];
+	if (focused) {
+		if (!selectedOrderNos.value.includes(focused)) {
+			selectedOrderNos.value = [focused];
+		}
+		void store.loadChainFromDomain(focused);
 	}
 });
 
 // ORDER-FLOW-CHAIN-ENTRY-AND-CLEANUP-1:路由复用场景下 onMounted 不会重跑,需 watch 同步入口订单。
+// t3i:watch 中追加 chain 详情按需加载,确保切换订单时 subSteps/units 也跟上。
 watch(
 	() => store.selectedOrderNo,
 	(newVal) => {
-		if (newVal && !selectedOrderNos.value.includes(newVal)) {
-			selectedOrderNos.value = [newVal];
+		if (newVal) {
+			if (!selectedOrderNos.value.includes(newVal)) {
+				selectedOrderNos.value = [newVal];
+			}
+			void store.loadChainFromDomain(newVal);
 		}
 	},
 );
@@ -233,7 +241,13 @@ const loadError = computed(() => store.loadError);
 const showBaseline = computed(() => isUnfiltered.value && !!aggregateSnapshot.value);
 
 function onRetry() {
-	void store.loadFromBackend();
+	// t3i:重试也走 domain 流;旧 loadFromBackend 保留作 fallback,本路径不再触发。
+	void (async () => {
+		await store.loadFromDomain();
+		void store.loadAggregateFromDomain('BASELINE_PPT');
+		const focused = store.selectedOrderNo;
+		if (focused) void store.loadChainFromDomain(focused);
+	})();
 }
 </script>
 

+ 79 - 1
Web/src/views/aidop/s8/monitoring/data/order-execution/domainMapper.ts

@@ -1,17 +1,25 @@
 // ORDER-FLOW-S8-INTEGRATED-DOMAIN-RESET-1 t3g:正式 API DTO → 旧 SalesOrderExecution 字段映射。
 // t3h:补 lifecycle mapper + OrderFlowCode 大写到 OrderNodeKey 小写的协议翻译表。
-// Chain 页面暂不切换;总览页展开后五阶段对照表通过 loadDetailFromDomain 注入 lifecycle
+// t3i:补 substep / unit mapper + chain detail(含 subSteps)lifecycle 映射 + aggregate → 旧 baseline snapshot 映射
 import { EXECUTION_OBSERVED_AT } from '/@/views/aidop/s8/monitoring/data/order-execution/customers';
 import type {
 	OrderNodeKey,
 	SalesOrderExecution,
 	StageSnapshot,
+	SubStepDetail,
 } from '/@/views/aidop/s8/monitoring/data/order-execution/types';
 import type {
+	OrderFlowAggregate,
 	OrderFlowOrderDetail,
 	OrderFlowOrderListItem,
 	OrderFlowStage,
+	OrderFlowSubstep,
+	OrderFlowSubstepUnit,
 } from '/@/views/aidop/s8/api/s8OrderFlowDomainApi';
+import type {
+	S8OrderFlowAggregateSnapshotDto,
+	S8OrderFlowStageSnapshotItem,
+} from '/@/views/aidop/s8/api/s8OrderFlowApi';
 
 // 'YYYY-MM-DD HH:mm' → 'MM-DD HH:mm';'YYYY-MM-DD' → 'MM-DD'。与旧 store.mapStage 同义,复制以避免跨文件依赖。
 function toMonthDayTime(s: string | null | undefined): string {
@@ -76,6 +84,76 @@ export function mapDomainOrderDetailLifecycle(detail: OrderFlowOrderDetail): Sta
 	return detail.lifecycle.map(mapDomainStage);
 }
 
+// t3i:单 unit (责任单元) → 旧 SubStepDetail 叶子。breakdown 不再嵌套。
+export function mapDomainSubstepUnit(unit: OrderFlowSubstepUnit): SubStepDetail {
+	return {
+		name: unit.unitName,
+		piHours: unit.piHours,
+		actualHours: unit.actualHours,
+		status: asStageStatus(unit.status),
+	};
+}
+
+// t3i:单 substep → 旧 SubStepDetail。units 非空时映射为 breakdown 数组。
+export function mapDomainSubstep(substep: OrderFlowSubstep): SubStepDetail {
+	const breakdown = Array.isArray(substep.units) && substep.units.length > 0
+		? substep.units.map(mapDomainSubstepUnit)
+		: undefined;
+	return {
+		name: substep.substepName,
+		piHours: substep.piHours,
+		actualHours: substep.actualHours,
+		status: asStageStatus(substep.status),
+		...(breakdown ? { breakdown } : {}),
+	};
+}
+
+// t3i:chain / detail lifecycle 注入 subSteps(仅含 substeps 的阶段填,其余沿用 mapDomainStage 默认)。
+export function mapDomainOrderDetailLifecycleWithSubsteps(
+	detail: OrderFlowOrderDetail,
+): StageSnapshot[] {
+	if (!detail || !Array.isArray(detail.lifecycle)) return [];
+	return detail.lifecycle.map((stage) => {
+		const base = mapDomainStage(stage);
+		if (Array.isArray(stage.substeps) && stage.substeps.length > 0) {
+			return { ...base, subSteps: stage.substeps.map(mapDomainSubstep) };
+		}
+		return base;
+	});
+}
+
+// t3i:正式 aggregate → 旧 S8OrderFlowAggregateSnapshotDto shape,让 ChainBaselineMatrix 无需修改。
+// ownerDept 后端未返,用 '--' 兜底,不引入硬编码部门名;snapshotCode/Label 沿用 baseline 语义。
+export function mapDomainAggregateToLegacyBaseline(
+	agg: OrderFlowAggregate,
+): S8OrderFlowAggregateSnapshotDto {
+	const stageSnapshots: S8OrderFlowStageSnapshotItem[] = Array.isArray(agg?.stageAggregates)
+		? agg.stageAggregates.map((s) => ({
+			stageKey: mapOrderFlowCodeToNodeKey(s.orderFlowCode),
+			stageName: s.orderFlowName,
+			ownerDept: '--',
+			kpiAvgDays: s.kpiAvgDays,
+			actualAvgDays: s.actualAvgDays,
+			onTimeRate: s.onTimeRate,
+			green: s.green,
+			yellow: s.yellow,
+			red: s.red,
+			pending: s.pending,
+		}))
+		: [];
+	return {
+		snapshotCode: 'BASELINE_PPT',
+		snapshotLabel: '基线聚合',
+		totalOrders: agg?.totalOrders ?? 0,
+		totalCustomers: agg?.totalCustomers ?? 0,
+		avgResponseMinutes: agg?.avgResponseMinutes ?? 0,
+		avgProcessingMinutes: agg?.avgProcessingMinutes ?? 0,
+		avgLossMinutes: agg?.avgLossMinutes ?? 0,
+		stageSnapshots,
+		remark: null,
+	};
+}
+
 export function mapDomainOrderListItem(dto: OrderFlowOrderListItem): SalesOrderExecution {
 	const currentKey = mapOrderFlowCodeToNodeKey(dto.currentOrderFlowCode);
 	return {