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

fix(s8): add final assembly delivery drilldown data

- add final assembly gate and parallel order-level seed data
- expose final-assembly-pivot endpoint with BASELINE_PPT/CURRENT_FILTERED scopes
- replace final assembly panel placeholder fields with API-backed values
- keep aggregate collab summary as fallback for exception metrics
- skip pending orders and avoid baseline fallback

bump version Web 2.4.179 / server 1.0.153
YY968XX 20 годин тому
батько
коміт
8ea7de74f1

+ 1 - 1
Web/package.json

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

+ 58 - 0
Web/src/views/aidop/s8/api/s8OrderFlowDomainApi.ts

@@ -327,6 +327,57 @@ export interface OrderFlowManufacturingPivot {
 	operators: OrderFlowManufacturingOperator[];
 }
 
+// ────────────────────────────────────────────────────────────
+// Final assembly pivot (S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1)
+// 总装发货「主门禁里程碑 / 并行准备」两表透视。
+// 与既有 aggregate.finalAssemblyCollabSummary 通道并存:
+//   - 本 API:门禁 KPI / 实际 / 偏差 / 状态 + 并行准备齐套率 / 状态(专属 pivot 表)。
+//   - aggregate.finalAssemblyCollabSummary:保留 exception 派生的 impacted/risk 摘要作为补充。
+// ────────────────────────────────────────────────────────────
+
+export interface OrderFlowFinalAssemblyPivotQuery {
+	tenantId?: number;
+	factoryId?: number;
+	/** BASELINE_PPT | CURRENT_FILTERED */
+	scope: string;
+	/** 订单编码 CSV(与本体生产 / 产品设计 / 采购透视 API 对齐)。 */
+	orderCodes?: string;
+}
+
+export interface OrderFlowFinalAssemblyGate {
+	gateCode: string;
+	gateName: string;
+	plannedOffsetDays: number;
+	actualOffsetDays: number | null;
+	varianceDays: number | null;
+	status: string;
+	impactedOrderCount: number | null;
+	riskOrderCount: number | null;
+	topRiskType: string | null;
+	topRiskLabel: string | null;
+	ownerSideText: string | null;
+	sortNo: number;
+}
+
+export interface OrderFlowFinalAssemblyParallel {
+	prepCode: string;
+	prepName: string;
+	kittingRate: number | null;
+	status: string;
+	impactedOrderCount: number | null;
+	riskOrderCount: number | null;
+	topRiskType: string | null;
+	topRiskLabel: string | null;
+	ownerSideText: string | null;
+	sortNo: number;
+}
+
+export interface OrderFlowFinalAssemblyPivot {
+	scope: string;
+	gates: OrderFlowFinalAssemblyGate[];
+	parallelPreps: OrderFlowFinalAssemblyParallel[];
+}
+
 // ────────────────────────────────────────────────────────────
 // API methods
 // ────────────────────────────────────────────────────────────
@@ -379,6 +430,12 @@ export function getOrderFlowManufacturingPivot(query: OrderFlowManufacturingPivo
 		.then(unwrap);
 }
 
+export function getOrderFlowFinalAssemblyPivot(query: OrderFlowFinalAssemblyPivotQuery) {
+	return service
+		.get<OrderFlowFinalAssemblyPivot>(`${BASE}/final-assembly-pivot`, { params: query })
+		.then(unwrap);
+}
+
 export const s8OrderFlowDomainApi = {
 	getOrderFlowOrders,
 	getOrderFlowOrder,
@@ -387,4 +444,5 @@ export const s8OrderFlowDomainApi = {
 	getOrderFlowProcurementPivot,
 	getOrderFlowProductDesignDrawings,
 	getOrderFlowManufacturingPivot,
+	getOrderFlowFinalAssemblyPivot,
 };

+ 61 - 0
Web/src/views/aidop/s8/monitoring/OrderChainOverviewPage.vue

@@ -26,11 +26,17 @@ import {
 	type ProcurementDetailFromApi,
 	type ManufacturingDetailFromApi,
 } from '/@/views/aidop/s8/monitoring/data/order-execution/stage-detail';
+// S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1:总装发货 pivot adapter。
+import {
+	adaptFinalAssemblyPivotFromApi,
+	type FinalAssemblyPivotFromApi,
+} from '/@/views/aidop/s8/monitoring/data/order-execution/final-assembly-collab';
 // S8-ORDER-CHAIN-PRODUCT-DESIGN-PPT-STATIC-AND-SINGLE-ORDER-ALIGN-1:默认不勾选态产品设计业务基线数据。
 import { PRODUCT_DESIGN_BASELINE_DETAIL } from '/@/views/aidop/s8/monitoring/data/order-execution/product-design-baseline';
 import {
 	getOrderFlowProcurementPivot,
 	getOrderFlowManufacturingPivot,
+	getOrderFlowFinalAssemblyPivot,
 } from '/@/views/aidop/s8/api/s8OrderFlowDomainApi';
 // ORDER-FLOW-CHAIN-PAGE2-ORIGINAL-LOGIC-RESTORE-1:baseline (isUnfiltered) 态下 L2/L3 使用 PPT 常量。
 import {
@@ -460,6 +466,55 @@ watch(
 	{ immediate: true },
 );
 
+// S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1:总装发货 pivot 二态加载。
+//   默认(isUnfiltered=true)→ scope=BASELINE_PPT,不传 orderCodes,显示 baseline(5 门禁 + 4 并行准备)。
+//   单订单态 → scope=CURRENT_FILTERED,orderCodes=[detailOrder.soNo];pending 订单返回空骨架。
+//   多订单态 → scope=CURRENT_FILTERED,orderCodes=filteredChainOrders.map(soNo);后端按门禁/准备项 AVG/SUM 聚合。
+//   旧请求 seq 去重,避免覆盖新响应;失败仅显示错误态,不静默回退 baseline。
+//   与 aggregateSnapshot.finalAssemblyCollabSummary 并存:pivot 提供 KPI/实际/偏差/齐套率,
+//   aggregate collabSummary 留作 impacted/risk/topRisk 兼容兜底。
+const finalAssemblyApiDetail = ref<FinalAssemblyPivotFromApi | null>(null);
+const finalAssemblyApiLoading = ref(false);
+const finalAssemblyApiError = ref<string | null>(null);
+let finalAssemblyRequestSeq = 0;
+
+async function loadFinalAssemblyPivot() {
+	const mySeq = ++finalAssemblyRequestSeq;
+	const isBaseline = isUnfiltered.value;
+	const scope = isBaseline ? 'BASELINE_PPT' : 'CURRENT_FILTERED';
+	const orderCodes = isBaseline
+		? undefined
+		: filteredChainOrders.value.map((o) => o.soNo).join(',');
+
+	finalAssemblyApiLoading.value = true;
+	finalAssemblyApiError.value = null;
+	try {
+		const pivot = await getOrderFlowFinalAssemblyPivot({ scope, orderCodes });
+		if (mySeq !== finalAssemblyRequestSeq) return;
+		finalAssemblyApiDetail.value = adaptFinalAssemblyPivotFromApi(pivot);
+	} catch (e) {
+		if (mySeq !== finalAssemblyRequestSeq) return;
+		finalAssemblyApiError.value = '总装发货数据加载失败,请稍后重试';
+		finalAssemblyApiDetail.value = null;
+	} finally {
+		if (mySeq === finalAssemblyRequestSeq) {
+			finalAssemblyApiLoading.value = false;
+		}
+	}
+}
+
+watch(
+	[
+		() => showFinalAssemblyCollab.value,
+		() => isUnfiltered.value,
+		() => filteredChainOrders.value,
+	],
+	([active]) => {
+		if (active) void loadFinalAssemblyPivot();
+	},
+	{ immediate: true },
+);
+
 // S8-ORDER-CHAIN-PRODUCT-DESIGN-PPT-STATIC-AND-SINGLE-ORDER-ALIGN-1:产品设计阶段二态数据源。
 // 默认不勾选态(isUnfiltered=true)→ 直接使用前端业务基线数据 PRODUCT_DESIGN_BASELINE_DETAIL,
 //   不调用 /api/aidop/s8/order-flow/product-design/drawings 接口。
@@ -597,6 +652,9 @@ watch(
 				:aggregate-final="finalStageSnapshot"
 				:collab-summary="finalCollabSummary"
 				:hit-count="filteredChainOrders.length"
+				:api-detail="finalAssemblyApiDetail"
+				:loading="finalAssemblyApiLoading"
+				:error-message="finalAssemblyApiError"
 			/>
 		</template>
 		<template v-else-if="detailOrder">
@@ -668,6 +726,9 @@ watch(
 				:aggregate-final="finalStageSnapshot"
 				:collab-summary="finalCollabSummary"
 				:hit-count="filteredChainOrders.length"
+				:api-detail="finalAssemblyApiDetail"
+				:loading="finalAssemblyApiLoading"
+				:error-message="finalAssemblyApiError"
 			/>
 		</template>
 

+ 98 - 23
Web/src/views/aidop/s8/monitoring/components/order-execution/FinalAssemblyCollabPanel.vue

@@ -1,7 +1,10 @@
 <script setup lang="ts" name="FinalAssemblyCollabPanel">
 // ORDER-CHAIN-FINAL-ASSEMBLY-DEPT-COLLAB-MVP-1:总装发货 · 末端协同达成面板。
-// 顶部摘要走 stageSnapshot 真实字段;主门禁/并行准备矩阵中无真实来源字段统一显示 "--",
-// 状态灯仅在真实状态可派生时着色,不复制总装发货整体绿灯到每个门禁。
+// S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1:
+//   接入 final-assembly-pivot API,门禁 KPI / 实际 / 偏差 / 达标情况 + 并行准备齐套率 / 状态
+//   不再硬编码 "--"。
+//   pivot 有数据时优先 pivot;impacted/risk/topRisk 兼容 aggregate.collabSummary(fallback)。
+//   pivot 为空骨架(CURRENT_FILTERED + orderCodes 空 / 全 pending)→ 显示空态文案。
 import { computed } from 'vue';
 import type { S8OrderFlowStageSnapshotItem } from '/@/views/aidop/s8/monitoring/data/order-execution/domainMapper';
 import {
@@ -9,12 +12,19 @@ import {
 	PARALLEL_PREPS,
 	type FinalAssemblyCollabSummary,
 	type FinalAssemblyObjectStat,
+	type FinalAssemblyPivotFromApi,
+	type FinalAssemblyGateData,
+	type FinalAssemblyParallelData,
+	isFinalAssemblyPivotEmpty,
 } from '/@/views/aidop/s8/monitoring/data/order-execution/final-assembly-collab';
 
 interface Props {
 	aggregateFinal: S8OrderFlowStageSnapshotItem | null;
 	collabSummary: FinalAssemblyCollabSummary | null;
 	hitCount: number;
+	apiDetail?: FinalAssemblyPivotFromApi | null;
+	loading?: boolean;
+	errorMessage?: string | null;
 }
 
 const props = defineProps<Props>();
@@ -36,6 +46,15 @@ const TONE_COLOR: Record<Tone, string> = {
 	red: '#ff4d4f',
 };
 
+function statusToTone(status: string | null | undefined): Tone | null {
+	if (!status) return null;
+	const s = status.toLowerCase();
+	if (s === 'green') return 'green';
+	if (s === 'yellow') return 'yellow';
+	if (s === 'red') return 'red';
+	return null;
+}
+
 const summary = computed(() => {
 	const final = props.aggregateFinal;
 	if (!final) {
@@ -87,26 +106,62 @@ interface ParallelRow {
 	statusTone: Tone | null;
 }
 
-function pickStat(map: Map<string, FinalAssemblyObjectStat> | undefined, code: string): FinalAssemblyObjectStat | null {
+function pickCollabStat(map: Map<string, FinalAssemblyObjectStat> | undefined, code: string): FinalAssemblyObjectStat | null {
 	if (!map) return null;
 	return map.get(code) ?? null;
 }
 
+function pickGateData(code: string): FinalAssemblyGateData | null {
+	return props.apiDetail?.gates.get(code) ?? null;
+}
+
+function pickParallelData(code: string): FinalAssemblyParallelData | null {
+	return props.apiDetail?.parallels.get(code) ?? null;
+}
+
+function formatDays(v: number | null | undefined): string {
+	if (v == null) return DASH;
+	return `${v.toFixed(1)} 天`;
+}
+
+function formatVariance(v: number | null | undefined): string {
+	if (v == null) return DASH;
+	if (v === 0) return '0 天';
+	const sign = v > 0 ? '+' : '';
+	return `${sign}${v.toFixed(1)} 天`;
+}
+
+function formatKittingRate(v: number | null | undefined): string {
+	if (v == null) return DASH;
+	return `${(v * 100).toFixed(1)}%`;
+}
+
+const isPivotEmpty = computed(() => {
+	if (!props.apiDetail) return false;  // 未加载 / 加载中:不显示空态
+	return isFinalAssemblyPivotEmpty(props.apiDetail);
+});
+
+const showEmptyState = computed(() =>
+	!props.loading && !props.errorMessage && isPivotEmpty.value,
+);
+
 const gateCells = computed<GateCell[]>(() =>
 	MAIN_GATES.map((g) => {
-		const stat = pickStat(props.collabSummary?.gates, g.code);
-		const impacted = stat?.impactedOrderCount;
+		const collab = pickCollabStat(props.collabSummary?.gates, g.code);
+		const gate = pickGateData(g.code);
+		// impacted_order_count:pivot > collabSummary fallback。
+		const impacted = gate?.impactedOrderCount ?? collab?.impactedOrderCount ?? null;
+		const tone = statusToTone(gate?.status ?? null);
 		return {
 			code: g.code,
 			name: g.name,
-			ownerSide: g.ownerSideText,
-			// 门禁级 KPI / 实际达成 / 偏差 / 达标情况:当前系统无门禁级字段,统一 "--",不复制节点级数据。
-			kpiText: DASH,
-			actualText: DASH,
-			varianceText: DASH,
+			ownerSide: gate?.ownerSideText ?? g.ownerSideText,
+			kpiText: formatDays(gate?.plannedOffsetDays ?? null),
+			actualText: formatDays(gate?.actualOffsetDays ?? null),
+			varianceText: formatVariance(gate?.varianceDays ?? null),
 			impactedOrderText: impacted == null ? DASH : `${impacted} 单`,
-			complianceText: DASH,
-			complianceTone: null,
+			complianceText: tone ? TONE_LABEL[tone] : DASH,
+			complianceTone: tone,
 		};
 	}),
 );
@@ -121,20 +176,28 @@ function pickSeverityColor(severity: string | null): string | null {
 
 const parallelRows = computed<ParallelRow[]>(() =>
 	PARALLEL_PREPS.map((p) => {
-		const stat = pickStat(props.collabSummary?.parallels, p.code);
-		const risk = stat?.riskOrderCount;
-		const title = stat?.topRiskTitle;
+		const collab = pickCollabStat(props.collabSummary?.parallels, p.code);
+		const parallel = pickParallelData(p.code);
+		// risk_order_count / topRisk:pivot > collabSummary fallback。
+		const risk = parallel?.riskOrderCount ?? collab?.riskOrderCount ?? null;
+		const pivotTopLabel = parallel?.topRiskLabel ?? null;
+		const collabTopTitle = collab?.topRiskTitle ?? null;
+		const topText = pivotTopLabel && pivotTopLabel.length > 0
+			? pivotTopLabel
+			: (collabTopTitle && collabTopTitle.length > 0 ? collabTopTitle : DASH);
+		// pivot 优先:用 pivot.status 派生颜色;fallback 用 collab.topRiskSeverity。
+		const tone = statusToTone(parallel?.status ?? null);
+		const topColor = tone ? TONE_COLOR[tone] : pickSeverityColor(collab?.topRiskSeverity ?? null);
 		return {
 			code: p.code,
 			name: p.name,
-			ownerSide: p.ownerSideText,
-			// 齐套率:无公式 / 无数据源,统一 "--"。
-			kittingRateText: DASH,
+			ownerSide: parallel?.ownerSideText ?? p.ownerSideText,
+			kittingRateText: formatKittingRate(parallel?.kittingRate ?? null),
 			riskOrderText: risk == null ? DASH : `${risk} 单`,
-			topRiskText: title && title.length > 0 ? title : DASH,
-			topRiskColor: pickSeverityColor(stat?.topRiskSeverity ?? null),
-			statusText: DASH,
-			statusTone: null,
+			topRiskText: topText,
+			topRiskColor: topColor,
+			statusText: tone ? TONE_LABEL[tone] : DASH,
+			statusTone: tone,
 		};
 	}),
 );
@@ -145,6 +208,9 @@ const parallelRows = computed<ParallelRow[]>(() =>
 		<header class="fac-panel__head">
 			<span class="fac-panel__bar" />
 			<h2 class="fac-panel__title">总装发货 · 末端协同达成</h2>
+			<span v-if="loading" class="fac-panel__status fac-panel__status--loading">数据加载中…</span>
+			<span v-else-if="errorMessage" class="fac-panel__status fac-panel__status--error">数据加载失败:{{ errorMessage }}</span>
+			<span v-else-if="showEmptyState" class="fac-panel__status fac-panel__status--empty">当前订单尚未进入总装发货阶段,无下钻数据</span>
 		</header>
 
 		<div class="fac-panel__summary">
@@ -259,9 +325,18 @@ const parallelRows = computed<ParallelRow[]>(() =>
 
 <style scoped>
 .fac-panel { display: flex; flex-direction: column; gap: 12px; }
-.fac-panel__head { display: flex; align-items: center; gap: 10px; }
+.fac-panel__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
 .fac-panel__bar { width: 5px; height: 18px; border-radius: 999px; background: var(--order-accent, #7bd0ff); }
 .fac-panel__title { margin: 0; font-size: 16px; font-weight: 700; }
+.fac-panel__status {
+	font-size: 12px;
+	padding: 2px 8px;
+	border-radius: 999px;
+	margin-left: 4px;
+}
+.fac-panel__status--loading { color: #7bd0ff; background: rgba(123, 208, 255, 0.12); }
+.fac-panel__status--error { color: #ff4d4f; background: rgba(255, 77, 79, 0.12); }
+.fac-panel__status--empty { color: #909097; background: rgba(144, 144, 151, 0.12); }
 
 .fac-panel__summary {
 	display: grid;

+ 95 - 0
Web/src/views/aidop/s8/monitoring/data/order-execution/final-assembly-collab.ts

@@ -46,3 +46,98 @@ export interface FinalAssemblyCollabSummary {
 	gates: Map<string, FinalAssemblyObjectStat>;
 	parallels: Map<string, FinalAssemblyObjectStat>;
 }
+
+// ────────────────────────────────────────────────────────────
+// S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1
+// 总装发货 pivot adapter:从后端 OrderFlowFinalAssemblyPivot 转为 UI 友好的 Map 结构。
+// 与 collabSummary 通道并存:panel 优先用 pivot 数据,aggregate.collabSummary 留作 fallback。
+// ────────────────────────────────────────────────────────────
+
+import type {
+	OrderFlowFinalAssemblyPivot,
+	OrderFlowFinalAssemblyGate,
+	OrderFlowFinalAssemblyParallel,
+} from '/@/views/aidop/s8/api/s8OrderFlowDomainApi';
+
+/** 单门禁的真实数据;字段全 nullable,无数据保持 null(不转占位)。 */
+export interface FinalAssemblyGateData {
+	plannedOffsetDays: number | null;
+	actualOffsetDays: number | null;
+	varianceDays: number | null;
+	status: string | null;
+	impactedOrderCount: number | null;
+	riskOrderCount: number | null;
+	topRiskType: string | null;
+	topRiskLabel: string | null;
+	ownerSideText: string | null;
+}
+
+/** 单并行准备的真实数据。 */
+export interface FinalAssemblyParallelData {
+	kittingRate: number | null;
+	status: string | null;
+	impactedOrderCount: number | null;
+	riskOrderCount: number | null;
+	topRiskType: string | null;
+	topRiskLabel: string | null;
+	ownerSideText: string | null;
+}
+
+/** Pivot adapter 结果:scope + Map<code, data>;Map.get 在 panel 内 O(1) 查表。 */
+export interface FinalAssemblyPivotFromApi {
+	scope: string;
+	gates: Map<string, FinalAssemblyGateData>;
+	parallels: Map<string, FinalAssemblyParallelData>;
+}
+
+function adaptGate(raw: OrderFlowFinalAssemblyGate): FinalAssemblyGateData {
+	return {
+		plannedOffsetDays: raw.plannedOffsetDays ?? null,
+		actualOffsetDays: raw.actualOffsetDays ?? null,
+		varianceDays: raw.varianceDays ?? null,
+		status: raw.status ?? null,
+		impactedOrderCount: raw.impactedOrderCount ?? null,
+		riskOrderCount: raw.riskOrderCount ?? null,
+		topRiskType: raw.topRiskType ?? null,
+		topRiskLabel: raw.topRiskLabel ?? null,
+		ownerSideText: raw.ownerSideText ?? null,
+	};
+}
+
+function adaptParallel(raw: OrderFlowFinalAssemblyParallel): FinalAssemblyParallelData {
+	return {
+		kittingRate: raw.kittingRate ?? null,
+		status: raw.status ?? null,
+		impactedOrderCount: raw.impactedOrderCount ?? null,
+		riskOrderCount: raw.riskOrderCount ?? null,
+		topRiskType: raw.topRiskType ?? null,
+		topRiskLabel: raw.topRiskLabel ?? null,
+		ownerSideText: raw.ownerSideText ?? null,
+	};
+}
+
+/**
+ * 将后端 pivot 响应转为 Map 结构。
+ * 后端可能返回 gates=[] / parallelPreps=[](CURRENT_FILTERED 全 pending 或 orderCodes 为空),Map 为空即可,
+ * panel 用 Map.get(code) === undefined 判定该对象当前无数据并显示 --。
+ */
+export function adaptFinalAssemblyPivotFromApi(
+	raw: OrderFlowFinalAssemblyPivot | null | undefined,
+): FinalAssemblyPivotFromApi | null {
+	if (!raw) return null;
+	const gates = new Map<string, FinalAssemblyGateData>();
+	for (const g of raw.gates ?? []) {
+		if (g && g.gateCode) gates.set(g.gateCode, adaptGate(g));
+	}
+	const parallels = new Map<string, FinalAssemblyParallelData>();
+	for (const p of raw.parallelPreps ?? []) {
+		if (p && p.prepCode) parallels.set(p.prepCode, adaptParallel(p));
+	}
+	return { scope: raw.scope ?? '', gates, parallels };
+}
+
+/** 判定 pivot 是否为空骨架(CURRENT_FILTERED + orderCodes 空 / 全 pending → 后端两个数组都为空)。 */
+export function isFinalAssemblyPivotEmpty(pivot: FinalAssemblyPivotFromApi | null): boolean {
+	if (!pivot) return true;
+	return pivot.gates.size === 0 && pivot.parallels.size === 0;
+}

+ 6 - 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.149</AssemblyVersion>
-    <FileVersion>1.0.149</FileVersion>
-    <Version>1.0.149</Version>
+    <AssemblyVersion>1.0.153</AssemblyVersion>
+    <FileVersion>1.0.153</FileVersion>
+    <Version>1.0.153</Version>
   </PropertyGroup>
 
   <ItemGroup>
@@ -103,6 +103,9 @@
     <None Update="UpdateScripts\1.0.148.sql">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </None>
+    <None Update="UpdateScripts\1.0.153.sql">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
   </ItemGroup>
 
   <ItemGroup>

+ 260 - 0
server/Admin.NET.Web.Entry/UpdateScripts/1.0.153.sql

@@ -0,0 +1,260 @@
+-- ──────────────────────────────────────────────────────────────────────
+-- S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1
+-- 在 ado_s8_order_flow_final_assembly_gate / _parallel 注入
+--   baseline 行(order_id IS NULL, scenario=BASELINE_PPT)+
+--   订单级 SEED 行(order_id 非 NULL, scenario=ORDER_LEVEL)。
+--
+-- 表 A(ado_s8_order_flow_final_assembly_gate):
+--   gate baseline 5 行 + gate order-level 15 单 × 5 门禁 = 75 行 → 80 行。
+-- 表 B(ado_s8_order_flow_final_assembly_parallel):
+--   parallel baseline 4 行 + parallel order-level 15 单 × 4 准备项 = 60 行 → 64 行。
+-- 合计 INSERT 144 行。pending 5 单(SO-2026-016~020)不生成 ORDER_LEVEL 行。
+--
+-- 业务口径(拍板):
+--   门禁 planned_offset_days = (0.6, 1.2, 1.8, 2.4, 3.0) 累计里程碑;
+--   order-level actual_offset_days = planned × stage.actual_days / 3.0,
+--   末位 SHIPMENT_CONFIRMATION 尾差修正 = stage.actual_days;
+--   variance_days = actual - planned;status:≤0 green / ≤0.3 yellow / 否则 red。
+--   parallel baseline kitting_rate = (0.9500, 0.9700, 0.9200, 0.9600);
+--   order-level:green 单保持 baseline,yellow 单 4 项统一 -0.0500;pending 不生成。
+--   status:≥0.95 green / ≥0.85 yellow / 否则 red(seed 时一次固化)。
+--
+-- data_source='SEED' / scenario_code∈('BASELINE_PPT','ORDER_LEVEL'):
+--   未来 IMPORT/AGG 接入后,service 按 (order_code, gate/prep_code) 优先 IMPORT/AGG,SEED 兜底。
+-- ──────────────────────────────────────────────────────────────────────
+
+-- 1A. 表 A 建表(条件创建,避免与既有迁移冲突)。
+CREATE TABLE IF NOT EXISTS `ado_s8_order_flow_final_assembly_gate` (
+  `id` BIGINT NOT NULL,
+  `order_id` BIGINT NULL,
+  `order_code` VARCHAR(64) NULL,
+  `gate_code` VARCHAR(32) NOT NULL,
+  `gate_name` VARCHAR(32) NOT NULL,
+  `planned_offset_days` DECIMAL(6,3) NOT NULL,
+  `actual_offset_days` DECIMAL(6,3) NULL,
+  `variance_days` DECIMAL(6,3) NULL,
+  `status` VARCHAR(16) NOT NULL,
+  `impacted_order_count` INT NULL,
+  `risk_order_count` INT NULL,
+  `top_risk_type` VARCHAR(64) NULL,
+  `top_risk_label` VARCHAR(255) NULL,
+  `owner_side_text` VARCHAR(64) NULL,
+  `sort_no` INT NOT NULL,
+  `scenario_code` VARCHAR(16) NOT NULL,
+  `data_source` VARCHAR(16) NOT NULL,
+  `tenant_id` BIGINT NOT NULL,
+  `factory_id` BIGINT NOT NULL,
+  `created_at` DATETIME NOT NULL,
+  `updated_at` DATETIME NULL,
+  `is_deleted` TINYINT(1) NOT NULL DEFAULT 0,
+  PRIMARY KEY (`id`),
+  KEY `idx_order_flow_fa_gate_baseline` (`tenant_id`, `factory_id`, `gate_code`),
+  KEY `idx_order_flow_fa_gate_order` (`tenant_id`, `factory_id`, `order_code`),
+  KEY `idx_order_flow_fa_gate_scenario` (`tenant_id`, `factory_id`, `scenario_code`, `is_deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='S8 订单执行链路总装发货主门禁里程碑';
+
+-- 1B. 表 B 建表。
+CREATE TABLE IF NOT EXISTS `ado_s8_order_flow_final_assembly_parallel` (
+  `id` BIGINT NOT NULL,
+  `order_id` BIGINT NULL,
+  `order_code` VARCHAR(64) NULL,
+  `prep_code` VARCHAR(32) NOT NULL,
+  `prep_name` VARCHAR(32) NOT NULL,
+  `kitting_rate` DECIMAL(5,4) NULL,
+  `status` VARCHAR(16) NOT NULL,
+  `impacted_order_count` INT NULL,
+  `risk_order_count` INT NULL,
+  `top_risk_type` VARCHAR(64) NULL,
+  `top_risk_label` VARCHAR(255) NULL,
+  `owner_side_text` VARCHAR(64) NULL,
+  `sort_no` INT NOT NULL,
+  `scenario_code` VARCHAR(16) NOT NULL,
+  `data_source` VARCHAR(16) NOT NULL,
+  `tenant_id` BIGINT NOT NULL,
+  `factory_id` BIGINT NOT NULL,
+  `created_at` DATETIME NOT NULL,
+  `updated_at` DATETIME NULL,
+  `is_deleted` TINYINT(1) NOT NULL DEFAULT 0,
+  PRIMARY KEY (`id`),
+  KEY `idx_order_flow_fa_parallel_baseline` (`tenant_id`, `factory_id`, `prep_code`),
+  KEY `idx_order_flow_fa_parallel_order` (`tenant_id`, `factory_id`, `order_code`),
+  KEY `idx_order_flow_fa_parallel_scenario` (`tenant_id`, `factory_id`, `scenario_code`, `is_deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='S8 订单执行链路总装发货并行准备';
+
+-- 2A. 表 A 幂等删除(仅 SEED + BASELINE_PPT / ORDER_LEVEL;不触碰未来 IMPORT/AGG 行)。
+DELETE FROM `ado_s8_order_flow_final_assembly_gate`
+WHERE `tenant_id` = 1
+  AND `factory_id` = 1
+  AND `is_deleted` = 0
+  AND `data_source` = 'SEED'
+  AND `scenario_code` IN ('BASELINE_PPT', 'ORDER_LEVEL');
+
+-- 2B. 表 B 幂等删除。
+DELETE FROM `ado_s8_order_flow_final_assembly_parallel`
+WHERE `tenant_id` = 1
+  AND `factory_id` = 1
+  AND `is_deleted` = 0
+  AND `data_source` = 'SEED'
+  AND `scenario_code` IN ('BASELINE_PPT', 'ORDER_LEVEL');
+
+-- 3A. 表 A 插入 5 baseline + 75 order-level = 80 行。
+INSERT INTO `ado_s8_order_flow_final_assembly_gate` (
+  `id`, `order_id`, `order_code`, `gate_code`, `gate_name`,
+  `planned_offset_days`, `actual_offset_days`, `variance_days`, `status`,
+  `impacted_order_count`, `risk_order_count`, `top_risk_type`, `top_risk_label`,
+  `owner_side_text`, `sort_no`, `scenario_code`, `data_source`,
+  `tenant_id`, `factory_id`, `created_at`, `updated_at`, `is_deleted`
+) VALUES
+(1329909220001, NULL, NULL, 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'BASELINE_PPT', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909220002, NULL, NULL, 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'BASELINE_PPT', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909220003, NULL, NULL, 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'BASELINE_PPT', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909220004, NULL, NULL, 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'BASELINE_PPT', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909220005, NULL, NULL, 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'BASELINE_PPT', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230001, 1329909100001, 'SO-2026-001', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230002, 1329909100001, 'SO-2026-001', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230003, 1329909100001, 'SO-2026-001', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230004, 1329909100001, 'SO-2026-001', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230005, 1329909100001, 'SO-2026-001', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230006, 1329909100002, 'SO-2026-002', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230007, 1329909100002, 'SO-2026-002', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230008, 1329909100002, 'SO-2026-002', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230009, 1329909100002, 'SO-2026-002', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230010, 1329909100002, 'SO-2026-002', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230011, 1329909100003, 'SO-2026-003', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230012, 1329909100003, 'SO-2026-003', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230013, 1329909100003, 'SO-2026-003', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230014, 1329909100003, 'SO-2026-003', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230015, 1329909100003, 'SO-2026-003', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230016, 1329909100004, 'SO-2026-004', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230017, 1329909100004, 'SO-2026-004', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230018, 1329909100004, 'SO-2026-004', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230019, 1329909100004, 'SO-2026-004', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230020, 1329909100004, 'SO-2026-004', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230021, 1329909100005, 'SO-2026-005', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230022, 1329909100005, 'SO-2026-005', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230023, 1329909100005, 'SO-2026-005', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230024, 1329909100005, 'SO-2026-005', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230025, 1329909100005, 'SO-2026-005', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230026, 1329909100006, 'SO-2026-006', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230027, 1329909100006, 'SO-2026-006', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230028, 1329909100006, 'SO-2026-006', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230029, 1329909100006, 'SO-2026-006', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230030, 1329909100006, 'SO-2026-006', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230031, 1329909100007, 'SO-2026-007', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230032, 1329909100007, 'SO-2026-007', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230033, 1329909100007, 'SO-2026-007', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230034, 1329909100007, 'SO-2026-007', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230035, 1329909100007, 'SO-2026-007', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230036, 1329909100008, 'SO-2026-008', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230037, 1329909100008, 'SO-2026-008', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230038, 1329909100008, 'SO-2026-008', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230039, 1329909100008, 'SO-2026-008', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230040, 1329909100008, 'SO-2026-008', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230041, 1329909100009, 'SO-2026-009', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230042, 1329909100009, 'SO-2026-009', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230043, 1329909100009, 'SO-2026-009', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230044, 1329909100009, 'SO-2026-009', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230045, 1329909100009, 'SO-2026-009', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230046, 1329909100010, 'SO-2026-010', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230047, 1329909100010, 'SO-2026-010', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230048, 1329909100010, 'SO-2026-010', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230049, 1329909100010, 'SO-2026-010', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230050, 1329909100010, 'SO-2026-010', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230051, 1329909100011, 'SO-2026-011', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.800, 0.200, 'yellow', 1, 0, 'ASSEMBLY_DELAY', '总装节拍偏差', '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230052, 1329909100011, 'SO-2026-011', 'QUALITY_RELEASE', '质量放行', 1.200, 1.600, 0.400, 'red', 1, 1, 'QUALITY_RELEASE_DELAY', '质量放行等待', '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230053, 1329909100011, 'SO-2026-011', 'PACKAGING_COMPLETION', '包装完成', 1.800, 2.400, 0.600, 'red', 1, 1, 'PACKAGING_DELAY', '包装完成偏差', '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230054, 1329909100011, 'SO-2026-011', 'GOODS_HANDOVER', '成品交接', 2.400, 3.200, 0.800, 'red', 1, 1, 'HANDOVER_DELAY', '成品交接等待', '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230055, 1329909100011, 'SO-2026-011', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 4.000, 1.000, 'red', 1, 1, 'SHIPMENT_DELAY', '发运确认等待', '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230056, 1329909100012, 'SO-2026-012', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230057, 1329909100012, 'SO-2026-012', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230058, 1329909100012, 'SO-2026-012', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230059, 1329909100012, 'SO-2026-012', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230060, 1329909100012, 'SO-2026-012', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230061, 1329909100013, 'SO-2026-013', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230062, 1329909100013, 'SO-2026-013', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230063, 1329909100013, 'SO-2026-013', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230064, 1329909100013, 'SO-2026-013', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230065, 1329909100013, 'SO-2026-013', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230066, 1329909100014, 'SO-2026-014', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230067, 1329909100014, 'SO-2026-014', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230068, 1329909100014, 'SO-2026-014', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230069, 1329909100014, 'SO-2026-014', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230070, 1329909100014, 'SO-2026-014', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230071, 1329909100015, 'SO-2026-015', 'ASSEMBLY_COMPLETION', '总装完工', 0.600, 0.600, 0.000, 'green', 0, 0, NULL, NULL, '总装线', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230072, 1329909100015, 'SO-2026-015', 'QUALITY_RELEASE', '质量放行', 1.200, 1.200, 0.000, 'green', 0, 0, NULL, NULL, '质量·终检', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230073, 1329909100015, 'SO-2026-015', 'PACKAGING_COMPLETION', '包装完成', 1.800, 1.800, 0.000, 'green', 0, 0, NULL, NULL, '包装线', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230074, 1329909100015, 'SO-2026-015', 'GOODS_HANDOVER', '成品交接', 2.400, 2.400, 0.000, 'green', 0, 0, NULL, NULL, '成品库', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909230075, 1329909100015, 'SO-2026-015', 'SHIPMENT_CONFIRMATION', '发运确认', 3.000, 3.000, 0.000, 'green', 0, 0, NULL, NULL, '发运协调', 50, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0);
+
+-- 3B. 表 B 插入 4 baseline + 60 order-level = 64 行。
+INSERT INTO `ado_s8_order_flow_final_assembly_parallel` (
+  `id`, `order_id`, `order_code`, `prep_code`, `prep_name`,
+  `kitting_rate`, `status`,
+  `impacted_order_count`, `risk_order_count`, `top_risk_type`, `top_risk_label`,
+  `owner_side_text`, `sort_no`, `scenario_code`, `data_source`,
+  `tenant_id`, `factory_id`, `created_at`, `updated_at`, `is_deleted`
+) VALUES
+(1329909240001, NULL, NULL, 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'BASELINE_PPT', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909240002, NULL, NULL, 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'BASELINE_PPT', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909240003, NULL, NULL, 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 0, 0, NULL, NULL, '发运协调', 30, 'BASELINE_PPT', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909240004, NULL, NULL, 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'BASELINE_PPT', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250001, 1329909100001, 'SO-2026-001', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250002, 1329909100001, 'SO-2026-001', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250003, 1329909100001, 'SO-2026-001', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250004, 1329909100001, 'SO-2026-001', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250005, 1329909100002, 'SO-2026-002', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250006, 1329909100002, 'SO-2026-002', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250007, 1329909100002, 'SO-2026-002', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250008, 1329909100002, 'SO-2026-002', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250009, 1329909100003, 'SO-2026-003', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250010, 1329909100003, 'SO-2026-003', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250011, 1329909100003, 'SO-2026-003', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250012, 1329909100003, 'SO-2026-003', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250013, 1329909100004, 'SO-2026-004', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250014, 1329909100004, 'SO-2026-004', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250015, 1329909100004, 'SO-2026-004', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250016, 1329909100004, 'SO-2026-004', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250017, 1329909100005, 'SO-2026-005', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250018, 1329909100005, 'SO-2026-005', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250019, 1329909100005, 'SO-2026-005', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250020, 1329909100005, 'SO-2026-005', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250021, 1329909100006, 'SO-2026-006', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250022, 1329909100006, 'SO-2026-006', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250023, 1329909100006, 'SO-2026-006', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250024, 1329909100006, 'SO-2026-006', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250025, 1329909100007, 'SO-2026-007', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250026, 1329909100007, 'SO-2026-007', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250027, 1329909100007, 'SO-2026-007', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250028, 1329909100007, 'SO-2026-007', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250029, 1329909100008, 'SO-2026-008', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250030, 1329909100008, 'SO-2026-008', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250031, 1329909100008, 'SO-2026-008', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250032, 1329909100008, 'SO-2026-008', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250033, 1329909100009, 'SO-2026-009', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250034, 1329909100009, 'SO-2026-009', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250035, 1329909100009, 'SO-2026-009', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250036, 1329909100009, 'SO-2026-009', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250037, 1329909100010, 'SO-2026-010', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250038, 1329909100010, 'SO-2026-010', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250039, 1329909100010, 'SO-2026-010', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250040, 1329909100010, 'SO-2026-010', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250041, 1329909100011, 'SO-2026-011', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9000, 'yellow', 1, 0, 'PACKAGING_MATERIAL_SHORTAGE', '包装材料齐套', '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250042, 1329909100011, 'SO-2026-011', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9200, 'yellow', 1, 0, 'LABEL_DOC_PENDING', '标签随箱资料齐套', '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250043, 1329909100011, 'SO-2026-011', 'SHIPPING_PLAN', '发运计划准备', 0.8700, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250044, 1329909100011, 'SO-2026-011', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9100, 'yellow', 1, 0, 'SHIPPING_DOC_PENDING', '出货单据齐套', '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250045, 1329909100012, 'SO-2026-012', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250046, 1329909100012, 'SO-2026-012', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250047, 1329909100012, 'SO-2026-012', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250048, 1329909100012, 'SO-2026-012', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250049, 1329909100013, 'SO-2026-013', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250050, 1329909100013, 'SO-2026-013', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250051, 1329909100013, 'SO-2026-013', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250052, 1329909100013, 'SO-2026-013', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250053, 1329909100014, 'SO-2026-014', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250054, 1329909100014, 'SO-2026-014', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250055, 1329909100014, 'SO-2026-014', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250056, 1329909100014, 'SO-2026-014', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250057, 1329909100015, 'SO-2026-015', 'PACKAGING_MATERIAL_PREP', '包装材料准备', 0.9500, 'green', 0, 0, NULL, NULL, '包装线·物料', 10, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250058, 1329909100015, 'SO-2026-015', 'LABEL_DOC_PREP', '标签随箱资料准备', 0.9700, 'green', 0, 0, NULL, NULL, '技术·质量', 20, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250059, 1329909100015, 'SO-2026-015', 'SHIPPING_PLAN', '发运计划准备', 0.9200, 'yellow', 1, 0, 'SHIPPING_PLAN_PENDING', '发运计划齐套', '发运协调', 30, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0),
+(1329909250060, 1329909100015, 'SO-2026-015', 'SHIPPING_DOC_PREP', '出货单据准备', 0.9600, 'green', 0, 0, NULL, NULL, '计划·商务', 40, 'ORDER_LEVEL', 'SEED', 1, 1, '2026-05-12 00:00:00', NULL, 0);

+ 14 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S8/AdoS8OrderFlowController.cs

@@ -70,4 +70,18 @@ public class AdoS8OrderFlowController : ControllerBase
     public async Task<IActionResult> ManufacturingPivotAsync(
         [FromQuery] AdoS8OrderFlowManufacturingPivotQueryDto query)
         => Ok(await _svc.GetManufacturingPivotAsync(query));
+
+    /// <summary>
+    /// S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1:总装发货透视。
+    /// scope=BASELINE_PPT 读两表 baseline 行(5 门禁 + 4 并行准备);
+    /// scope=CURRENT_FILTERED 按 orderCodes CSV 取订单级行并聚合,
+    /// 末位门禁 SHIPMENT_CONFIRMATION 守恒为 stage.actual_days;
+    /// pending 单不参与聚合;orderCodes 为空时返回空骨架,不 fallback baseline。
+    /// 与 aggregate.finalAssemblyCollabSummary 通道并存:本 API 给门禁 KPI / 实际 / 偏差 / 齐套率,
+    /// collabSummary 留作 exception 派生补充。
+    /// </summary>
+    [HttpGet("final-assembly-pivot")]
+    public async Task<IActionResult> FinalAssemblyPivotAsync(
+        [FromQuery] AdoS8FinalAssemblyPivotQueryDto query)
+        => Ok(await _svc.GetFinalAssemblyPivotAsync(query));
 }

+ 69 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/OrderFlow/AdoS8OrderFlowDtos.cs

@@ -419,3 +419,72 @@ public class AdoS8OrderFlowManufacturingPivotDto
 }
 
 #endregion
+
+#region 总装发货 — S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1
+
+/// <summary>
+/// 总装发货「主门禁里程碑 / 并行准备」透视查询入参。
+/// orderCodes CSV:与本体生产 / 产品设计 / 采购透视 API 对齐,规避 axios brackets 与 ASP.NET Core List 绑定二义性。
+/// scope=BASELINE_PPT 时 orderCodes 忽略;scope=CURRENT_FILTERED 时 orderCodes 为空返回空骨架(不静默 fallback baseline)。
+/// 与既有 aggregate.finalAssemblyCollabSummary 通道独立:本 API 提供门禁 KPI / 实际 / 偏差 / 齐套率,
+/// aggregate 仍保留异常派生通道作为补充。
+/// </summary>
+public class AdoS8FinalAssemblyPivotQueryDto
+{
+    public long? TenantId { get; set; }
+    public long? FactoryId { get; set; }
+    /// <summary>BASELINE_PPT | CURRENT_FILTERED</summary>
+    public string Scope { get; set; } = string.Empty;
+    /// <summary>逗号分隔订单号;scope=CURRENT_FILTERED 时使用。</summary>
+    public string? OrderCodes { get; set; }
+}
+
+/// <summary>
+/// 主门禁里程碑行:ASSEMBLY_COMPLETION / QUALITY_RELEASE / PACKAGING_COMPLETION / GOODS_HANDOVER / SHIPMENT_CONFIRMATION。
+/// planned_offset_days 为累计里程碑(0.6/1.2/1.8/2.4/3.0);末位 SHIPMENT_CONFIRMATION.actual_offset_days 守恒为 stage.actual_days。
+/// pending 单不进入聚合结果。
+/// </summary>
+public class AdoS8FinalAssemblyGateDto
+{
+    public string GateCode { get; set; } = string.Empty;
+    public string GateName { get; set; } = string.Empty;
+    public decimal PlannedOffsetDays { get; set; }
+    public decimal? ActualOffsetDays { get; set; }
+    public decimal? VarianceDays { get; set; }
+    public string Status { get; set; } = string.Empty;
+    public int? ImpactedOrderCount { get; set; }
+    public int? RiskOrderCount { get; set; }
+    public string? TopRiskType { get; set; }
+    public string? TopRiskLabel { get; set; }
+    public string? OwnerSideText { get; set; }
+    public int SortNo { get; set; }
+}
+
+/// <summary>
+/// 并行准备行:PACKAGING_MATERIAL_PREP / LABEL_DOC_PREP / SHIPPING_PLAN / SHIPPING_DOC_PREP。
+/// kitting_rate 取值 0.0000~1.0000;status 按 ≥0.95 green / ≥0.85 yellow / 否则 red 派生(seed 时固化,运行期不重判)。
+/// </summary>
+public class AdoS8FinalAssemblyParallelDto
+{
+    public string PrepCode { get; set; } = string.Empty;
+    public string PrepName { get; set; } = string.Empty;
+    public decimal? KittingRate { get; set; }
+    public string Status { get; set; } = string.Empty;
+    public int? ImpactedOrderCount { get; set; }
+    public int? RiskOrderCount { get; set; }
+    public string? TopRiskType { get; set; }
+    public string? TopRiskLabel { get; set; }
+    public string? OwnerSideText { get; set; }
+    public int SortNo { get; set; }
+}
+
+/// <summary>总装发货透视聚合结果:scope + 5 门禁 + 4 并行准备。</summary>
+public class AdoS8FinalAssemblyPivotDto
+{
+    /// <summary>BASELINE_PPT | CURRENT_FILTERED</summary>
+    public string Scope { get; set; } = string.Empty;
+    public List<AdoS8FinalAssemblyGateDto> Gates { get; set; } = new();
+    public List<AdoS8FinalAssemblyParallelDto> ParallelPreps { get; set; } = new();
+}
+
+#endregion

+ 112 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/OrderFlow/AdoS8OrderFlowFinalAssemblyGate.cs

@@ -0,0 +1,112 @@
+namespace Admin.NET.Plugin.AiDOP.Entity.S8.OrderFlow;
+
+/// <summary>
+/// S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1:
+/// S8 订单执行链路 FINAL_ASSEMBLY_DELIVERY 阶段「主门禁里程碑」表(baseline + 订单级 SEED)。
+///
+/// order_id / order_code 允许为空:
+///   - NULL → baseline / PPT 级别基线行(scenario_code=BASELINE_PPT)
+///   - 非 NULL → 订单级行(scenario_code=ORDER_LEVEL)
+///
+/// gate_code 取值:ASSEMBLY_COMPLETION / QUALITY_RELEASE / PACKAGING_COMPLETION / GOODS_HANDOVER / SHIPMENT_CONFIRMATION。
+/// planned_offset_days / actual_offset_days / variance_days 使用 decimal(6,3) 与本体生产同口径。
+///
+/// 业务口径(拍板版本):
+///   - 门禁是阶段内里程碑而非串行工序:
+///     planned_offset_days 累计到 3.0(0.6/1.2/1.8/2.4/3.0),不是各自时长。
+///   - 订单级 actual_offset_days 按 scale=stage.actual_days/3.0 缩放,
+///     最后一个门禁 SHIPMENT_CONFIRMATION 做尾差修正使 actual=stage.actual_days。
+///   - variance_days = actual_offset_days - planned_offset_days。
+///   - status 阈值:variance≤0 green / ≤0.3 yellow / 否则 red(seed 时一次固化,运行期不再重判)。
+///
+/// SEED 落地路径:不通过 IncreSeed 自动写入,由 UpdateScripts SQL(1.0.153.sql)
+///   的 DELETE+INSERT 落地(与 procurement / manufacturing 三表同模式)。
+/// </summary>
+[SugarTable("ado_s8_order_flow_final_assembly_gate", "S8 订单执行链路总装发货主门禁里程碑")]
+[SugarIndex("idx_order_flow_fa_gate_baseline", nameof(TenantId), OrderByType.Asc, nameof(FactoryId), OrderByType.Asc, nameof(GateCode), OrderByType.Asc)]
+[SugarIndex("idx_order_flow_fa_gate_order", nameof(TenantId), OrderByType.Asc, nameof(FactoryId), OrderByType.Asc, nameof(OrderCode), OrderByType.Asc)]
+[SugarIndex("idx_order_flow_fa_gate_scenario", nameof(TenantId), OrderByType.Asc, nameof(FactoryId), OrderByType.Asc, nameof(ScenarioCode), OrderByType.Asc, nameof(IsDeleted), OrderByType.Asc)]
+public class AdoS8OrderFlowFinalAssemblyGate
+{
+    [SugarColumn(ColumnName = "id", IsPrimaryKey = true, ColumnDataType = "bigint")]
+    public long Id { get; set; }
+
+    /// <summary>NULL = baseline 行;非 NULL = 订单级行。</summary>
+    [SugarColumn(ColumnName = "order_id", ColumnDataType = "bigint", IsNullable = true)]
+    public long? OrderId { get; set; }
+
+    /// <summary>NULL = baseline 行;非 NULL = 订单业务键。</summary>
+    [SugarColumn(ColumnName = "order_code", Length = 64, IsNullable = true)]
+    public string? OrderCode { get; set; }
+
+    /// <summary>门禁编码:ASSEMBLY_COMPLETION / QUALITY_RELEASE / PACKAGING_COMPLETION / GOODS_HANDOVER / SHIPMENT_CONFIRMATION。</summary>
+    [SugarColumn(ColumnName = "gate_code", Length = 32)]
+    public string GateCode { get; set; } = string.Empty;
+
+    /// <summary>门禁名称:总装完工 / 质量放行 / 包装完成 / 成品交接 / 发运确认。</summary>
+    [SugarColumn(ColumnName = "gate_name", Length = 32)]
+    public string GateName { get; set; } = string.Empty;
+
+    /// <summary>累计里程碑(天)。固定:0.6 / 1.2 / 1.8 / 2.4 / 3.0。decimal(6,3)。</summary>
+    [SugarColumn(ColumnName = "planned_offset_days", DecimalDigits = 3, Length = 6)]
+    public decimal PlannedOffsetDays { get; set; }
+
+    /// <summary>累计实际里程碑(天)。pending 时为 NULL;末位门禁尾差修正 = stage.actual_days。decimal(6,3)。</summary>
+    [SugarColumn(ColumnName = "actual_offset_days", DecimalDigits = 3, Length = 6, IsNullable = true)]
+    public decimal? ActualOffsetDays { get; set; }
+
+    /// <summary>偏差(天)= actual_offset_days - planned_offset_days。pending 时为 NULL。decimal(6,3)。</summary>
+    [SugarColumn(ColumnName = "variance_days", DecimalDigits = 3, Length = 6, IsNullable = true)]
+    public decimal? VarianceDays { get; set; }
+
+    /// <summary>状态:green / yellow / red / pending。variance≤0 green / ≤0.3 yellow / 否则 red(seed 时固化)。</summary>
+    [SugarColumn(ColumnName = "status", Length = 16)]
+    public string Status { get; set; } = "green";
+
+    /// <summary>影响订单数:order-level 行为 0 或 1(该门禁非 green 时为 1);baseline 行为聚合摘要(baseline 视图当前固定 0)。</summary>
+    [SugarColumn(ColumnName = "impacted_order_count", ColumnDataType = "int", IsNullable = true)]
+    public int? ImpactedOrderCount { get; set; }
+
+    /// <summary>风险订单数:order-level 行为 0 或 1(该门禁 red 时为 1);baseline 行为聚合摘要(baseline 视图当前固定 0)。</summary>
+    [SugarColumn(ColumnName = "risk_order_count", ColumnDataType = "int", IsNullable = true)]
+    public int? RiskOrderCount { get; set; }
+
+    /// <summary>主要风险类型码:稳定 sample(ASSEMBLY_DELAY / QUALITY_RELEASE_DELAY 等);status=green 或 baseline 行为 NULL。</summary>
+    [SugarColumn(ColumnName = "top_risk_type", Length = 64, IsNullable = true)]
+    public string? TopRiskType { get; set; }
+
+    /// <summary>主要风险展示文本:与 top_risk_type 同源(总装节拍偏差 / 质量放行等待 等);status=green 或 baseline 行为 NULL。</summary>
+    [SugarColumn(ColumnName = "top_risk_label", Length = 255, IsNullable = true)]
+    public string? TopRiskLabel { get; set; }
+
+    /// <summary>责任侧文案:总装线 / 质量·终检 / 包装线 / 成品库 / 发运协调。可空,前端 fallback fixture 字典。</summary>
+    [SugarColumn(ColumnName = "owner_side_text", Length = 64, IsNullable = true)]
+    public string? OwnerSideText { get; set; }
+
+    /// <summary>排序号 10/20/30/40/50(与 5 门禁固定顺序对齐)。</summary>
+    [SugarColumn(ColumnName = "sort_no", ColumnDataType = "int")]
+    public int SortNo { get; set; }
+
+    /// <summary>BASELINE_PPT / ORDER_LEVEL。</summary>
+    [SugarColumn(ColumnName = "scenario_code", Length = 16)]
+    public string ScenarioCode { get; set; } = "BASELINE_PPT";
+
+    /// <summary>SEED / IMPORT / AGG。同 (order_code, gate_code) 上 IMPORT/AGG 优先于 SEED。</summary>
+    [SugarColumn(ColumnName = "data_source", Length = 16)]
+    public string DataSource { get; set; } = "SEED";
+
+    [SugarColumn(ColumnName = "tenant_id", ColumnDataType = "bigint")]
+    public long TenantId { get; set; }
+
+    [SugarColumn(ColumnName = "factory_id", ColumnDataType = "bigint")]
+    public long FactoryId { get; set; }
+
+    [SugarColumn(ColumnName = "created_at")]
+    public DateTime CreatedAt { get; set; } = DateTime.Now;
+
+    [SugarColumn(ColumnName = "updated_at", IsNullable = true)]
+    public DateTime? UpdatedAt { get; set; }
+
+    [SugarColumn(ColumnName = "is_deleted", ColumnDataType = "boolean")]
+    public bool IsDeleted { get; set; }
+}

+ 104 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/OrderFlow/AdoS8OrderFlowFinalAssemblyParallel.cs

@@ -0,0 +1,104 @@
+namespace Admin.NET.Plugin.AiDOP.Entity.S8.OrderFlow;
+
+/// <summary>
+/// S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1:
+/// S8 订单执行链路 FINAL_ASSEMBLY_DELIVERY 阶段「并行准备」表(baseline + 订单级 SEED)。
+///
+/// order_id / order_code 允许为空:
+///   - NULL → baseline 行(scenario_code=BASELINE_PPT)
+///   - 非 NULL → 订单级行(scenario_code=ORDER_LEVEL)
+///
+/// prep_code 取值:PACKAGING_MATERIAL_PREP / LABEL_DOC_PREP / SHIPPING_PLAN / SHIPPING_DOC_PREP。
+/// kitting_rate 使用 decimal(5,4)(与 manufacturing achievement_rate 同口径),取值 0.0000~1.0000。
+///
+/// 业务口径(拍板版本):
+///   - baseline kitting_rate:0.9500 / 0.9700 / 0.9200 / 0.9600;
+///   - order-level kitting_rate 按 stage.status 修正:
+///     green 单:保持 baseline;
+///     yellow 单:baseline - 0.0500(4 项统一下调以保 status >= 0.85 yellow);
+///     pending 单:不生成 ORDER_LEVEL 行。
+///   - status 阈值:≥0.95 green / ≥0.85 yellow / 否则 red(seed 时固化,运行期不重判)。
+///
+/// SEED 落地路径:不通过 IncreSeed 自动写入,由 UpdateScripts SQL(1.0.153.sql)
+///   的 DELETE+INSERT 落地(与 procurement / manufacturing 三表同模式)。
+/// </summary>
+[SugarTable("ado_s8_order_flow_final_assembly_parallel", "S8 订单执行链路总装发货并行准备")]
+[SugarIndex("idx_order_flow_fa_parallel_baseline", nameof(TenantId), OrderByType.Asc, nameof(FactoryId), OrderByType.Asc, nameof(PrepCode), OrderByType.Asc)]
+[SugarIndex("idx_order_flow_fa_parallel_order", nameof(TenantId), OrderByType.Asc, nameof(FactoryId), OrderByType.Asc, nameof(OrderCode), OrderByType.Asc)]
+[SugarIndex("idx_order_flow_fa_parallel_scenario", nameof(TenantId), OrderByType.Asc, nameof(FactoryId), OrderByType.Asc, nameof(ScenarioCode), OrderByType.Asc, nameof(IsDeleted), OrderByType.Asc)]
+public class AdoS8OrderFlowFinalAssemblyParallel
+{
+    [SugarColumn(ColumnName = "id", IsPrimaryKey = true, ColumnDataType = "bigint")]
+    public long Id { get; set; }
+
+    /// <summary>NULL = baseline 行;非 NULL = 订单级行。</summary>
+    [SugarColumn(ColumnName = "order_id", ColumnDataType = "bigint", IsNullable = true)]
+    public long? OrderId { get; set; }
+
+    /// <summary>NULL = baseline 行;非 NULL = 订单业务键。</summary>
+    [SugarColumn(ColumnName = "order_code", Length = 64, IsNullable = true)]
+    public string? OrderCode { get; set; }
+
+    /// <summary>准备项编码:PACKAGING_MATERIAL_PREP / LABEL_DOC_PREP / SHIPPING_PLAN / SHIPPING_DOC_PREP。</summary>
+    [SugarColumn(ColumnName = "prep_code", Length = 32)]
+    public string PrepCode { get; set; } = string.Empty;
+
+    /// <summary>准备项名称:包装材料准备 / 标签随箱资料准备 / 发运计划准备 / 出货单据准备。</summary>
+    [SugarColumn(ColumnName = "prep_name", Length = 32)]
+    public string PrepName { get; set; } = string.Empty;
+
+    /// <summary>齐套率:decimal(5,4),取值 0.0000~1.0000;pending 时为 NULL。</summary>
+    [SugarColumn(ColumnName = "kitting_rate", DecimalDigits = 4, Length = 5, IsNullable = true)]
+    public decimal? KittingRate { get; set; }
+
+    /// <summary>状态:green / yellow / red / pending。≥0.95 green / ≥0.85 yellow / 否则 red(seed 时固化)。</summary>
+    [SugarColumn(ColumnName = "status", Length = 16)]
+    public string Status { get; set; } = "green";
+
+    /// <summary>影响订单数:order-level 行为 0 或 1;baseline 行为聚合摘要(baseline 视图当前固定 0)。</summary>
+    [SugarColumn(ColumnName = "impacted_order_count", ColumnDataType = "int", IsNullable = true)]
+    public int? ImpactedOrderCount { get; set; }
+
+    /// <summary>风险订单数:order-level 行为 0 或 1(status=red 时为 1);baseline 行为聚合摘要(baseline 视图当前固定 0)。</summary>
+    [SugarColumn(ColumnName = "risk_order_count", ColumnDataType = "int", IsNullable = true)]
+    public int? RiskOrderCount { get; set; }
+
+    /// <summary>主要风险类型码:稳定 sample;status=green 或 baseline 行为 NULL。</summary>
+    [SugarColumn(ColumnName = "top_risk_type", Length = 64, IsNullable = true)]
+    public string? TopRiskType { get; set; }
+
+    /// <summary>主要风险展示文本:与 top_risk_type 同源;status=green 或 baseline 行为 NULL。</summary>
+    [SugarColumn(ColumnName = "top_risk_label", Length = 255, IsNullable = true)]
+    public string? TopRiskLabel { get; set; }
+
+    /// <summary>责任侧文案:包装线·物料 / 技术·质量 / 发运协调 / 计划·商务。可空,前端 fallback fixture 字典。</summary>
+    [SugarColumn(ColumnName = "owner_side_text", Length = 64, IsNullable = true)]
+    public string? OwnerSideText { get; set; }
+
+    /// <summary>排序号 10/20/30/40(与 4 并行准备固定顺序对齐)。</summary>
+    [SugarColumn(ColumnName = "sort_no", ColumnDataType = "int")]
+    public int SortNo { get; set; }
+
+    /// <summary>BASELINE_PPT / ORDER_LEVEL。</summary>
+    [SugarColumn(ColumnName = "scenario_code", Length = 16)]
+    public string ScenarioCode { get; set; } = "BASELINE_PPT";
+
+    /// <summary>SEED / IMPORT / AGG。同 (order_code, prep_code) 上 IMPORT/AGG 优先于 SEED。</summary>
+    [SugarColumn(ColumnName = "data_source", Length = 16)]
+    public string DataSource { get; set; } = "SEED";
+
+    [SugarColumn(ColumnName = "tenant_id", ColumnDataType = "bigint")]
+    public long TenantId { get; set; }
+
+    [SugarColumn(ColumnName = "factory_id", ColumnDataType = "bigint")]
+    public long FactoryId { get; set; }
+
+    [SugarColumn(ColumnName = "created_at")]
+    public DateTime CreatedAt { get; set; } = DateTime.Now;
+
+    [SugarColumn(ColumnName = "updated_at", IsNullable = true)]
+    public DateTime? UpdatedAt { get; set; }
+
+    [SugarColumn(ColumnName = "is_deleted", ColumnDataType = "boolean")]
+    public bool IsDeleted { get; set; }
+}

+ 314 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/S8OrderFlowFinalAssemblySeedData.cs

@@ -0,0 +1,314 @@
+using Admin.NET.Plugin.AiDOP.Entity.S8.OrderFlow;
+
+namespace Admin.NET.Plugin.AiDOP.SeedData;
+
+/// <summary>
+/// S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1:
+/// 总装发货「主门禁里程碑 / 并行准备」两表 seed 算法 SOT。
+///
+/// ──────────────────────────────────────────────────────────────────────
+/// SEED 落地路径(关键):
+///   本项目 SEED 数据落地走 UpdateScripts SQL(DELETE+INSERT),不是 SqlSugar IncreSeed 自动写入。
+///   这与 procurement 1.0.148.sql / UpdateScripts SQL 模式一致。
+///   本类只作为算法 SOT,本批次实际行数据由
+///     UpdateScripts/1.0.153.sql
+///   的 INSERT 语句落地(DDL 创建表 + DELETE 幂等清理 + INSERT 144 行)。
+/// 因此本类不挂 [IncreSeed];保留算法 owner 身份,便于未来 IMPORT/AGG 接入对账。
+/// ──────────────────────────────────────────────────────────────────────
+///
+/// 业务口径(拍板版本):
+///   父级:ado_s8_order_flow_stage 中 stage_code=FINAL_ASSEMBLY_DELIVERY 行的 planned_days=3.0、actual_days/status。
+///   非 pending 订单 = 14 green (actual=3.0) + 1 yellow (SO-2026-011 actual=4.0);5 pending 不生成 ORDER_LEVEL 行。
+///
+///   Gate baseline 5 行:
+///     planned_offset_days = (0.6, 1.2, 1.8, 2.4, 3.0)(累计里程碑)。
+///     actual_offset_days = planned_offset_days、variance_days = 0、status = green(清洁 PPT 基线)。
+///     impacted_order_count = 0、risk_order_count = 0(baseline 视图不暴露单订单分布)。
+///     top_risk_type/label = NULL(baseline 行无风险摘要)。
+///
+///   Gate order-level:每个非 pending 单 5 行:
+///     scale = stage.actual_days / 3.0
+///     前 4 个门禁 actual_offset_days = planned_offset_days × scale,
+///     末位 SHIPMENT_CONFIRMATION 尾差修正:actual_offset_days = stage.actual_days。
+///     variance_days = actual_offset_days - planned_offset_days。
+///     status:variance≤0 green / ≤0.3 yellow / 否则 red(seed 时固化)。
+///     impacted_order_count = (status != green) ? 1 : 0。
+///     risk_order_count = (status == red) ? 1 : 0。
+///     top_risk_type/label:status != green 时取 GateRiskMap,否则 NULL。
+///
+///   Parallel baseline 4 行:
+///     kitting_rate = (0.9500, 0.9700, 0.9200, 0.9600)。
+///     status 按 ≥0.95 green / ≥0.85 yellow / 否则 red 派生(SHIPPING_PLAN 0.92 = yellow,其它 3 项 green)。
+///     impacted/risk = 0,top_risk_type/label = NULL。
+///
+///   Parallel order-level:每个非 pending 单 4 行:
+///     stage.status == "green" → kitting_rate = baseline(无调整)。
+///     stage.status == "yellow" → kitting_rate = baseline - 0.0500(4 项统一下调,保 yellow 范围)。
+///     status 按相同阈值派生。
+///     impacted/risk/top_risk_type/label 同 Gate 规则。
+///
+/// 真实数据源(IMPORT / AGG)接入后,service 优先采用 IMPORT/AGG,同 (order_code, gate/prep_code) SEED 行仅兜底。
+/// </summary>
+// 不挂 [IncreSeed]:实际数据落地路径见 1.0.153.sql。
+public class S8OrderFlowFinalAssemblyGateSeedData : ISqlSugarEntitySeedData<AdoS8OrderFlowFinalAssemblyGate>
+{
+    public IEnumerable<AdoS8OrderFlowFinalAssemblyGate> HasData()
+        => S8OrderFlowFinalAssemblyDataset.BuildGateBaselineRows()
+            .Concat(S8OrderFlowFinalAssemblyDataset.BuildGateOrderLevelRows());
+}
+
+// 不挂 [IncreSeed]:实际数据落地路径见 1.0.153.sql。
+public class S8OrderFlowFinalAssemblyParallelSeedData : ISqlSugarEntitySeedData<AdoS8OrderFlowFinalAssemblyParallel>
+{
+    public IEnumerable<AdoS8OrderFlowFinalAssemblyParallel> HasData()
+        => S8OrderFlowFinalAssemblyDataset.BuildParallelBaselineRows()
+            .Concat(S8OrderFlowFinalAssemblyDataset.BuildParallelOrderLevelRows());
+}
+
+internal static class S8OrderFlowFinalAssemblyDataset
+{
+    // ─────────────── ID base ranges(接续 manufacturing 1329909210000 段) ───────────────
+    internal const long GateBaselineIdBase     = 1329909220000L;
+    internal const long GateOrderLevelIdBase   = 1329909230000L;
+    internal const long ParallelBaselineIdBase = 1329909240000L;
+    internal const long ParallelOrderLevelIdBase = 1329909250000L;
+
+    internal const string ScenarioBaseline   = "BASELINE_PPT";
+    internal const string ScenarioOrderLevel = "ORDER_LEVEL";
+
+    // FINAL_ASSEMBLY_DELIVERY 在 5 阶段流水中固定为索引 4(order_review→product_design→material_procurement→body_production→final_assembly_shipping)。
+    private const int FinalAssemblyStageIndex = 4;
+    private const decimal StageBaselineDays = 3.0m;
+
+    // ─────────────── Gate baseline 真值(累计里程碑) ───────────────
+    private sealed record GateBaseline(
+        int SortNo, string Code, string Name, decimal PlannedOffsetDays, string OwnerSideText,
+        string RiskType, string RiskLabel);
+
+    private static readonly GateBaseline[] GateBaselines =
+    {
+        new(10, "ASSEMBLY_COMPLETION",   "总装完工", 0.6m, "总装线",     "ASSEMBLY_DELAY",        "总装节拍偏差"),
+        new(20, "QUALITY_RELEASE",       "质量放行", 1.2m, "质量·终检",  "QUALITY_RELEASE_DELAY", "质量放行等待"),
+        new(30, "PACKAGING_COMPLETION",  "包装完成", 1.8m, "包装线",     "PACKAGING_DELAY",       "包装完成偏差"),
+        new(40, "GOODS_HANDOVER",        "成品交接", 2.4m, "成品库",     "HANDOVER_DELAY",        "成品交接等待"),
+        new(50, "SHIPMENT_CONFIRMATION", "发运确认", 3.0m, "发运协调",   "SHIPMENT_DELAY",        "发运确认等待"),
+    };
+
+    // ─────────────── Parallel baseline 真值(齐套率) ───────────────
+    private sealed record ParallelBaseline(
+        int SortNo, string Code, string Name, decimal KittingRate, string OwnerSideText,
+        string RiskType, string RiskLabel);
+
+    private static readonly ParallelBaseline[] ParallelBaselines =
+    {
+        new(10, "PACKAGING_MATERIAL_PREP", "包装材料准备",     0.9500m, "包装线·物料", "PACKAGING_MATERIAL_SHORTAGE", "包装材料齐套"),
+        new(20, "LABEL_DOC_PREP",          "标签随箱资料准备", 0.9700m, "技术·质量",   "LABEL_DOC_PENDING",           "标签随箱资料齐套"),
+        new(30, "SHIPPING_PLAN",           "发运计划准备",     0.9200m, "发运协调",    "SHIPPING_PLAN_PENDING",       "发运计划齐套"),
+        new(40, "SHIPPING_DOC_PREP",       "出货单据准备",     0.9600m, "计划·商务",   "SHIPPING_DOC_PENDING",        "出货单据齐套"),
+    };
+
+    /// <summary>yellow 单 kitting_rate 统一下调量(保 ≥0.85 yellow 阈值)。</summary>
+    private const decimal YellowKittingPenalty = 0.0500m;
+
+    // ────────────────────────────────────────────────────────────
+    // Gate 行
+    // ────────────────────────────────────────────────────────────
+    public static IEnumerable<AdoS8OrderFlowFinalAssemblyGate> BuildGateBaselineRows()
+    {
+        long seq = 0;
+        foreach (var g in GateBaselines)
+        {
+            yield return new AdoS8OrderFlowFinalAssemblyGate
+            {
+                Id = GateBaselineIdBase + (++seq),
+                OrderId = null,
+                OrderCode = null,
+                GateCode = g.Code,
+                GateName = g.Name,
+                PlannedOffsetDays = g.PlannedOffsetDays,
+                ActualOffsetDays = g.PlannedOffsetDays,
+                VarianceDays = 0m,
+                Status = "green",
+                ImpactedOrderCount = 0,
+                RiskOrderCount = 0,
+                TopRiskType = null,
+                TopRiskLabel = null,
+                OwnerSideText = g.OwnerSideText,
+                SortNo = g.SortNo,
+                ScenarioCode = ScenarioBaseline,
+                DataSource = "SEED",
+                TenantId = 1,
+                FactoryId = 1,
+                CreatedAt = S8OrderFlowDataset.CreatedAt,
+                UpdatedAt = null,
+                IsDeleted = false,
+            };
+        }
+    }
+
+    public static IEnumerable<AdoS8OrderFlowFinalAssemblyGate> BuildGateOrderLevelRows()
+    {
+        long seq = 0;
+        foreach (var spec in S8OrderFlowDataset.Specs)
+        {
+            var lifecycle = S8OrderFlowStageDataset.BuildLifecycleValues(spec);
+            var stage = lifecycle[FinalAssemblyStageIndex];
+            if (stage.status == "pending") continue;
+
+            var A = stage.actualDays;
+            var scale = A / StageBaselineDays;
+            var orderId = S8OrderFlowDataset.OrderIdBase + spec.Idx;
+
+            for (var i = 0; i < GateBaselines.Length; i++)
+            {
+                var g = GateBaselines[i];
+                decimal actualOffset;
+                if (i == GateBaselines.Length - 1)
+                {
+                    // 末位门禁尾差修正,确保 SHIPMENT_CONFIRMATION.actual_offset_days = stage.actual_days。
+                    actualOffset = decimal.Round(A, 3);
+                }
+                else
+                {
+                    actualOffset = decimal.Round(g.PlannedOffsetDays * scale, 3);
+                }
+                var variance = decimal.Round(actualOffset - g.PlannedOffsetDays, 3);
+                var status = ClassifyGateStatus(variance);
+                var (riskType, riskLabel) = status == "green"
+                    ? (null, null)
+                    : ((string?)g.RiskType, (string?)g.RiskLabel);
+
+                yield return new AdoS8OrderFlowFinalAssemblyGate
+                {
+                    Id = GateOrderLevelIdBase + (++seq),
+                    OrderId = orderId,
+                    OrderCode = spec.OrderCode,
+                    GateCode = g.Code,
+                    GateName = g.Name,
+                    PlannedOffsetDays = g.PlannedOffsetDays,
+                    ActualOffsetDays = actualOffset,
+                    VarianceDays = variance,
+                    Status = status,
+                    ImpactedOrderCount = status == "green" ? 0 : 1,
+                    RiskOrderCount = status == "red" ? 1 : 0,
+                    TopRiskType = riskType,
+                    TopRiskLabel = riskLabel,
+                    OwnerSideText = g.OwnerSideText,
+                    SortNo = g.SortNo,
+                    ScenarioCode = ScenarioOrderLevel,
+                    DataSource = "SEED",
+                    TenantId = 1,
+                    FactoryId = 1,
+                    CreatedAt = S8OrderFlowDataset.CreatedAt,
+                    UpdatedAt = null,
+                    IsDeleted = false,
+                };
+            }
+        }
+    }
+
+    // ────────────────────────────────────────────────────────────
+    // Parallel 行
+    // ────────────────────────────────────────────────────────────
+    public static IEnumerable<AdoS8OrderFlowFinalAssemblyParallel> BuildParallelBaselineRows()
+    {
+        long seq = 0;
+        foreach (var p in ParallelBaselines)
+        {
+            yield return new AdoS8OrderFlowFinalAssemblyParallel
+            {
+                Id = ParallelBaselineIdBase + (++seq),
+                OrderId = null,
+                OrderCode = null,
+                PrepCode = p.Code,
+                PrepName = p.Name,
+                KittingRate = p.KittingRate,
+                Status = ClassifyParallelStatus(p.KittingRate),
+                ImpactedOrderCount = 0,
+                RiskOrderCount = 0,
+                TopRiskType = null,
+                TopRiskLabel = null,
+                OwnerSideText = p.OwnerSideText,
+                SortNo = p.SortNo,
+                ScenarioCode = ScenarioBaseline,
+                DataSource = "SEED",
+                TenantId = 1,
+                FactoryId = 1,
+                CreatedAt = S8OrderFlowDataset.CreatedAt,
+                UpdatedAt = null,
+                IsDeleted = false,
+            };
+        }
+    }
+
+    public static IEnumerable<AdoS8OrderFlowFinalAssemblyParallel> BuildParallelOrderLevelRows()
+    {
+        long seq = 0;
+        foreach (var spec in S8OrderFlowDataset.Specs)
+        {
+            var lifecycle = S8OrderFlowStageDataset.BuildLifecycleValues(spec);
+            var stage = lifecycle[FinalAssemblyStageIndex];
+            if (stage.status == "pending") continue;
+
+            var orderId = S8OrderFlowDataset.OrderIdBase + spec.Idx;
+            var stageStatus = stage.status;
+
+            foreach (var p in ParallelBaselines)
+            {
+                var kittingRate = stageStatus switch
+                {
+                    "yellow" => decimal.Round(p.KittingRate - YellowKittingPenalty, 4),
+                    _        => p.KittingRate,  // green:保持 baseline
+                };
+                var status = ClassifyParallelStatus(kittingRate);
+                var (riskType, riskLabel) = status == "green"
+                    ? (null, null)
+                    : ((string?)p.RiskType, (string?)p.RiskLabel);
+
+                yield return new AdoS8OrderFlowFinalAssemblyParallel
+                {
+                    Id = ParallelOrderLevelIdBase + (++seq),
+                    OrderId = orderId,
+                    OrderCode = spec.OrderCode,
+                    PrepCode = p.Code,
+                    PrepName = p.Name,
+                    KittingRate = kittingRate,
+                    Status = status,
+                    ImpactedOrderCount = status == "green" ? 0 : 1,
+                    RiskOrderCount = status == "red" ? 1 : 0,
+                    TopRiskType = riskType,
+                    TopRiskLabel = riskLabel,
+                    OwnerSideText = p.OwnerSideText,
+                    SortNo = p.SortNo,
+                    ScenarioCode = ScenarioOrderLevel,
+                    DataSource = "SEED",
+                    TenantId = 1,
+                    FactoryId = 1,
+                    CreatedAt = S8OrderFlowDataset.CreatedAt,
+                    UpdatedAt = null,
+                    IsDeleted = false,
+                };
+            }
+        }
+    }
+
+    // ────────────────────────────────────────────────────────────
+    // status classifiers(seed-time 一次性,运行期不再重判)
+    // ────────────────────────────────────────────────────────────
+
+    /// <summary>gate status:variance ≤ 0 green / ≤ 0.3 yellow / 否则 red。</summary>
+    private static string ClassifyGateStatus(decimal variance)
+    {
+        if (variance <= 0m) return "green";
+        if (variance <= 0.3m) return "yellow";
+        return "red";
+    }
+
+    /// <summary>parallel status:kitting_rate ≥ 0.95 green / ≥ 0.85 yellow / 否则 red。</summary>
+    private static string ClassifyParallelStatus(decimal kittingRate)
+    {
+        if (kittingRate >= 0.95m) return "green";
+        if (kittingRate >= 0.85m) return "yellow";
+        return "red";
+    }
+}

+ 274 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/OrderFlow/S8OrderFlowService.cs

@@ -49,6 +49,9 @@ public class S8OrderFlowService : ITransient
     private readonly SqlSugarRepository<AdoS8OrderFlowManufacturingProcess> _mfgProcessRep;
     private readonly SqlSugarRepository<AdoS8OrderFlowManufacturingLossFactor> _mfgLossRep;
     private readonly SqlSugarRepository<AdoS8OrderFlowManufacturingOperator> _mfgOperatorRep;
+    // S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1:总装发货 2 表 repository。
+    private readonly SqlSugarRepository<AdoS8OrderFlowFinalAssemblyGate> _faGateRep;
+    private readonly SqlSugarRepository<AdoS8OrderFlowFinalAssemblyParallel> _faParallelRep;
 
     public S8OrderFlowService(
         SqlSugarRepository<AdoS8OrderFlowOrder> orderRep,
@@ -61,7 +64,9 @@ public class S8OrderFlowService : ITransient
         SqlSugarRepository<AdoS8OrderFlowProductDesignDrawing> pddRep,
         SqlSugarRepository<AdoS8OrderFlowManufacturingProcess> mfgProcessRep,
         SqlSugarRepository<AdoS8OrderFlowManufacturingLossFactor> mfgLossRep,
-        SqlSugarRepository<AdoS8OrderFlowManufacturingOperator> mfgOperatorRep)
+        SqlSugarRepository<AdoS8OrderFlowManufacturingOperator> mfgOperatorRep,
+        SqlSugarRepository<AdoS8OrderFlowFinalAssemblyGate> faGateRep,
+        SqlSugarRepository<AdoS8OrderFlowFinalAssemblyParallel> faParallelRep)
     {
         _orderRep = orderRep;
         _stageRep = stageRep;
@@ -74,6 +79,8 @@ public class S8OrderFlowService : ITransient
         _mfgProcessRep = mfgProcessRep;
         _mfgLossRep = mfgLossRep;
         _mfgOperatorRep = mfgOperatorRep;
+        _faGateRep = faGateRep;
+        _faParallelRep = faParallelRep;
     }
 
     /// <summary>订单档案列表(无分页)。当前 baseline 20 单。</summary>
@@ -1308,4 +1315,270 @@ public class S8OrderFlowService : ITransient
         if (rate >= 0.80m) return "yellow";
         return "red";
     }
+
+    // ────────────────────────────────────────────────────────────
+    // 总装发货透视 — S8-ORDER-CHAIN-ASSEMBLY-DELIVERY-DATA-LINEAGE-AUDIT-1
+    //
+    // 与 aggregate.finalAssemblyCollabSummary(exception 派生)通道独立:
+    //   - 本 API 提供门禁 KPI / 实际 / 偏差 / 齐套率(来自专属 pivot 表)。
+    //   - 现有 BuildFinalAssemblyCollabSummaryAsync 保留,作为 collabSummary 补充。
+    // 数据源优先级:IMPORT > AGG > SEED(同 (order_code, gate_code/prep_code) 上去重)。
+    // 多订单聚合规则:
+    //   - planned_offset_days:取首单值(baseline 固定 0.6/1.2/1.8/2.4/3.0,不随订单变)。
+    //   - actual_offset_days:AVG。
+    //   - variance_days:AVG(等于 avg_actual - planned,因 planned 固定)。
+    //   - status:聚合后按 variance 重判(同 seed 阈值)。
+    //   - impacted_order_count / risk_order_count:SUM。
+    //   - top_risk_type / top_risk_label:稳定取首条 status != green 的样本(按 sort_no / order_code 升序)。
+    //   - parallel kitting_rate:AVG(保留 4 位小数);status 按聚合 kitting_rate 重判。
+    //   - orderCodes 为空 → 返回空骨架,不静默 fallback baseline;
+    //   - 全 pending → 自然空骨架(SEED 不包含 pending 行)。
+    // ────────────────────────────────────────────────────────────
+
+    public async Task<AdoS8FinalAssemblyPivotDto> GetFinalAssemblyPivotAsync(
+        AdoS8FinalAssemblyPivotQueryDto query)
+    {
+        var tenantId = query.TenantId ?? 1;
+        var factoryId = query.FactoryId ?? 1;
+        var scope = string.IsNullOrWhiteSpace(query.Scope) ? ScopeBaselinePpt : query.Scope;
+
+        if (string.Equals(scope, ScopeBaselinePpt, StringComparison.OrdinalIgnoreCase))
+        {
+            var gateRows = await _faGateRep.AsQueryable()
+                .Where(g => g.TenantId == tenantId && g.FactoryId == factoryId && !g.IsDeleted)
+                .Where(g => g.OrderId == null)
+                .ToListAsync();
+            var parallelRows = await _faParallelRep.AsQueryable()
+                .Where(p => p.TenantId == tenantId && p.FactoryId == factoryId && !p.IsDeleted)
+                .Where(p => p.OrderId == null)
+                .ToListAsync();
+
+            return BuildFinalAssemblyPivotFromBaseline(scope, gateRows, parallelRows);
+        }
+
+        if (string.Equals(scope, ScopeCurrentFiltered, StringComparison.OrdinalIgnoreCase))
+        {
+            var orderCodes = ParseOrderCodesCsv(query.OrderCodes);
+            if (orderCodes.Count == 0)
+                return new AdoS8FinalAssemblyPivotDto { Scope = scope };
+
+            var gateRows = await _faGateRep.AsQueryable()
+                .Where(g => g.TenantId == tenantId && g.FactoryId == factoryId && !g.IsDeleted)
+                .Where(g => g.OrderId != null && g.OrderCode != null)
+                .Where(g => orderCodes.Contains(g.OrderCode!))
+                .ToListAsync();
+            var parallelRows = await _faParallelRep.AsQueryable()
+                .Where(p => p.TenantId == tenantId && p.FactoryId == factoryId && !p.IsDeleted)
+                .Where(p => p.OrderId != null && p.OrderCode != null)
+                .Where(p => orderCodes.Contains(p.OrderCode!))
+                .ToListAsync();
+
+            if (gateRows.Count == 0 && parallelRows.Count == 0)
+                return new AdoS8FinalAssemblyPivotDto { Scope = scope };
+
+            var preferredGates = SelectPreferredFaGate(gateRows);
+            var preferredParallels = SelectPreferredFaParallel(parallelRows);
+
+            return new AdoS8FinalAssemblyPivotDto
+            {
+                Scope = scope,
+                Gates = BuildFaGateAggregated(preferredGates),
+                ParallelPreps = BuildFaParallelAggregated(preferredParallels),
+            };
+        }
+
+        return new AdoS8FinalAssemblyPivotDto { Scope = scope };
+    }
+
+    private static AdoS8FinalAssemblyPivotDto BuildFinalAssemblyPivotFromBaseline(
+        string scope,
+        List<AdoS8OrderFlowFinalAssemblyGate> gateRows,
+        List<AdoS8OrderFlowFinalAssemblyParallel> parallelRows)
+    {
+        var gates = gateRows
+            .OrderBy(g => g.SortNo)
+            .Select(g => new AdoS8FinalAssemblyGateDto
+            {
+                GateCode = g.GateCode,
+                GateName = g.GateName,
+                PlannedOffsetDays = g.PlannedOffsetDays,
+                ActualOffsetDays = g.ActualOffsetDays,
+                VarianceDays = g.VarianceDays,
+                Status = g.Status,
+                ImpactedOrderCount = g.ImpactedOrderCount,
+                RiskOrderCount = g.RiskOrderCount,
+                TopRiskType = g.TopRiskType,
+                TopRiskLabel = g.TopRiskLabel,
+                OwnerSideText = g.OwnerSideText,
+                SortNo = g.SortNo,
+            })
+            .ToList();
+
+        var parallels = parallelRows
+            .OrderBy(p => p.SortNo)
+            .Select(p => new AdoS8FinalAssemblyParallelDto
+            {
+                PrepCode = p.PrepCode,
+                PrepName = p.PrepName,
+                KittingRate = p.KittingRate,
+                Status = p.Status,
+                ImpactedOrderCount = p.ImpactedOrderCount,
+                RiskOrderCount = p.RiskOrderCount,
+                TopRiskType = p.TopRiskType,
+                TopRiskLabel = p.TopRiskLabel,
+                OwnerSideText = p.OwnerSideText,
+                SortNo = p.SortNo,
+            })
+            .ToList();
+
+        return new AdoS8FinalAssemblyPivotDto
+        {
+            Scope = scope,
+            Gates = gates,
+            ParallelPreps = parallels,
+        };
+    }
+
+    private static List<AdoS8OrderFlowFinalAssemblyGate> SelectPreferredFaGate(
+        List<AdoS8OrderFlowFinalAssemblyGate> rows)
+    {
+        var grouped = rows.GroupBy(r => (r.OrderCode, r.GateCode));
+        var result = new List<AdoS8OrderFlowFinalAssemblyGate>(rows.Count);
+        foreach (var g in grouped)
+        {
+            var preferred = g.FirstOrDefault(r =>
+                string.Equals(r.DataSource, "IMPORT", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(r.DataSource, "AGG", StringComparison.OrdinalIgnoreCase));
+            result.Add(preferred ?? g.First());
+        }
+        return result;
+    }
+
+    private static List<AdoS8OrderFlowFinalAssemblyParallel> SelectPreferredFaParallel(
+        List<AdoS8OrderFlowFinalAssemblyParallel> rows)
+    {
+        var grouped = rows.GroupBy(r => (r.OrderCode, r.PrepCode));
+        var result = new List<AdoS8OrderFlowFinalAssemblyParallel>(rows.Count);
+        foreach (var g in grouped)
+        {
+            var preferred = g.FirstOrDefault(r =>
+                string.Equals(r.DataSource, "IMPORT", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(r.DataSource, "AGG", StringComparison.OrdinalIgnoreCase));
+            result.Add(preferred ?? g.First());
+        }
+        return result;
+    }
+
+    private static List<AdoS8FinalAssemblyGateDto> BuildFaGateAggregated(
+        List<AdoS8OrderFlowFinalAssemblyGate> rows)
+    {
+        return rows
+            .GroupBy(r => r.GateCode)
+            .OrderBy(g => g.First().SortNo)
+            .Select(g =>
+            {
+                var sample = g.First();
+                var actualList = g.Where(r => r.ActualOffsetDays.HasValue).ToList();
+                var varianceList = g.Where(r => r.VarianceDays.HasValue).ToList();
+
+                decimal? actualAvg = actualList.Count == 0
+                    ? null
+                    : decimal.Round(actualList.Average(r => r.ActualOffsetDays!.Value), 3);
+                decimal? varianceAvg = varianceList.Count == 0
+                    ? null
+                    : decimal.Round(varianceList.Average(r => r.VarianceDays!.Value), 3);
+
+                var aggregatedStatus = varianceAvg.HasValue
+                    ? ClassifyFaGateStatus(varianceAvg.Value)
+                    : sample.Status;
+
+                var impactedSum = g.Sum(r => r.ImpactedOrderCount ?? 0);
+                var riskSum = g.Sum(r => r.RiskOrderCount ?? 0);
+
+                // 稳定取首条非 green 行的 top_risk_type / label(按 order_code 升序保证确定性)。
+                var riskSample = g
+                    .Where(r => !string.Equals(r.Status, "green", StringComparison.OrdinalIgnoreCase)
+                              && r.TopRiskType != null)
+                    .OrderBy(r => r.OrderCode, StringComparer.Ordinal)
+                    .FirstOrDefault();
+
+                return new AdoS8FinalAssemblyGateDto
+                {
+                    GateCode = sample.GateCode,
+                    GateName = sample.GateName,
+                    PlannedOffsetDays = sample.PlannedOffsetDays,
+                    ActualOffsetDays = actualAvg,
+                    VarianceDays = varianceAvg,
+                    Status = aggregatedStatus,
+                    ImpactedOrderCount = impactedSum,
+                    RiskOrderCount = riskSum,
+                    TopRiskType = riskSample?.TopRiskType,
+                    TopRiskLabel = riskSample?.TopRiskLabel,
+                    OwnerSideText = sample.OwnerSideText,
+                    SortNo = sample.SortNo,
+                };
+            })
+            .ToList();
+    }
+
+    private static List<AdoS8FinalAssemblyParallelDto> BuildFaParallelAggregated(
+        List<AdoS8OrderFlowFinalAssemblyParallel> rows)
+    {
+        return rows
+            .GroupBy(r => r.PrepCode)
+            .OrderBy(g => g.First().SortNo)
+            .Select(g =>
+            {
+                var sample = g.First();
+                var rateList = g.Where(r => r.KittingRate.HasValue).ToList();
+
+                decimal? kittingAvg = rateList.Count == 0
+                    ? null
+                    : decimal.Round(rateList.Average(r => r.KittingRate!.Value), 4);
+
+                var aggregatedStatus = kittingAvg.HasValue
+                    ? ClassifyFaParallelStatus(kittingAvg.Value)
+                    : sample.Status;
+
+                var impactedSum = g.Sum(r => r.ImpactedOrderCount ?? 0);
+                var riskSum = g.Sum(r => r.RiskOrderCount ?? 0);
+
+                var riskSample = g
+                    .Where(r => !string.Equals(r.Status, "green", StringComparison.OrdinalIgnoreCase)
+                              && r.TopRiskType != null)
+                    .OrderBy(r => r.OrderCode, StringComparer.Ordinal)
+                    .FirstOrDefault();
+
+                return new AdoS8FinalAssemblyParallelDto
+                {
+                    PrepCode = sample.PrepCode,
+                    PrepName = sample.PrepName,
+                    KittingRate = kittingAvg,
+                    Status = aggregatedStatus,
+                    ImpactedOrderCount = impactedSum,
+                    RiskOrderCount = riskSum,
+                    TopRiskType = riskSample?.TopRiskType,
+                    TopRiskLabel = riskSample?.TopRiskLabel,
+                    OwnerSideText = sample.OwnerSideText,
+                    SortNo = sample.SortNo,
+                };
+            })
+            .ToList();
+    }
+
+    /// <summary>gate status:variance ≤ 0 green / ≤ 0.3 yellow / 否则 red(与 seed 同口径)。</summary>
+    private static string ClassifyFaGateStatus(decimal variance)
+    {
+        if (variance <= 0m) return "green";
+        if (variance <= 0.3m) return "yellow";
+        return "red";
+    }
+
+    /// <summary>parallel status:kitting_rate ≥ 0.95 green / ≥ 0.85 yellow / 否则 red(与 seed 同口径)。</summary>
+    private static string ClassifyFaParallelStatus(decimal kittingRate)
+    {
+        if (kittingRate >= 0.95m) return "green";
+        if (kittingRate >= 0.85m) return "yellow";
+        return "red";
+    }
 }