Quellcode durchsuchen

feat: 合同评审/发货单/物料选择弹窗修复及蓝图文档补充 (Web 2.4.184 / Server 1.0.158)

- fix(contract): SQL表名SysUser修正、负责人下拉框改为点击展开全部选项

- fix(contract): 已完成状态隐藏编辑和删除按钮

- fix(auth): request.ts增加Cookie Token回退机制修复物料弹窗无数据

- feat(asnShipper): 发货单子表列名调整(物料编码/物料名称/发出库位)

- docs: S1-S4蓝图设计方案及业务需求描述文档、生成脚本
Pengxy vor 15 Stunden
Ursprung
Commit
7663636c33

+ 1 - 1
Web/package.json

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

+ 3 - 3
Web/src/utils/request.ts

@@ -1,6 +1,6 @@
 import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
 import { ElMessage } from 'element-plus';
-import { Local, } from '/@/utils/storage';
+import { Local, Session } from '/@/utils/storage';
 import {clearAccessAfterReload} from "/@/utils/axios-utils";
 import { getApiPublicBase } from '/@/utils/api-public-base';
 
@@ -87,8 +87,8 @@ service.interceptors.request.use(
 		const url = config.url || '';
 		abortControllerMap.set(url, controller);
 
-		// 获取本地的 token
-		const accessToken = Local.get(accessTokenKey);
+		// 获取本地的 token(与 axios-utils.ts 一致:优先 Local,回退 Cookie)
+		const accessToken = Local.get(accessTokenKey) || Session.get('token');
 		if (accessToken) {
 			// 将 token 添加到请求报文头中
 			config.headers!['Authorization'] = `Bearer ${accessToken}`;

+ 3 - 3
Web/src/views/aidop/business/asnShipperForm.vue

@@ -107,7 +107,7 @@
 						<span v-else>{{ row.ordLine }}</span>
 					</template>
 				</el-table-column>
-				<el-table-column label="集装箱零件" width="130">
+				<el-table-column label="物料编码" width="130">
 					<template #default="{ row, $index }">
 						<template v-if="editingRows.has($index) && !isView">
 							<el-input v-model="row.containerItem" size="small" />
@@ -115,7 +115,7 @@
 						<span v-else>{{ row.containerItem }}</span>
 					</template>
 				</el-table-column>
-				<el-table-column label="描述" min-width="130">
+				<el-table-column label="物料名称" min-width="130">
 					<template #default="{ row, $index }">
 						<template v-if="editingRows.has($index) && !isView">
 							<el-input v-model="row.descr" size="small" />
@@ -131,7 +131,7 @@
 						<span v-else>{{ row.um }}</span>
 					</template>
 				</el-table-column>
-				<el-table-column label="库位" width="180">
+				<el-table-column label="发出库位" width="180">
 					<template #default="{ row, $index }">
 						<template v-if="editingRows.has($index) && !isView">
 							<el-select

+ 6 - 10
Web/src/views/aidop/business/contractReviewForm.vue

@@ -41,7 +41,7 @@
 				</div>
 				<div class="cr-label">合同负责人</div>
 				<div class="cr-value">
-					<el-select v-model="form.responsibleAccount" filterable remote :remote-method="searchResponsibleUsers" placeholder="请选择合同负责人" clearable style="width: 100%" @change="onResponsibleChange">
+					<el-select v-model="form.responsibleAccount" filterable placeholder="请选择合同负责人" clearable style="width: 100%" @change="onResponsibleChange">
 						<el-option v-for="item in responsibleUserOptions" :key="item.value" :label="item.label" :value="item.value" />
 					</el-select>
 				</div>
@@ -258,10 +258,9 @@ const rules = reactive<FormRules>({
 
 const responsibleUserOptions = ref<{ value: string; label: string }[]>([]);
 
-async function searchResponsibleUsers(query: string) {
-	if (!query) return;
+async function loadResponsibleUsers() {
 	try {
-		const list = await fetchResponsibleUsers(query);
+		const list = await fetchResponsibleUsers();
 		responsibleUserOptions.value = list ?? [];
 	} catch {
 		responsibleUserOptions.value = [];
@@ -274,12 +273,8 @@ function onResponsibleChange(val: string) {
 }
 
 async function initResponsibleOptions() {
-	if (!form.responsibleAccount) return;
-	try {
-		const list = await fetchResponsibleUsers(form.responsibleAccount);
-		responsibleUserOptions.value = list ?? [];
-	} catch {
-		responsibleUserOptions.value = [];
+	if (responsibleUserOptions.value.length === 0) {
+		await loadResponsibleUsers();
 	}
 }
 
@@ -481,6 +476,7 @@ async function onApprovalRefresh() {
 watch(
 	() => props.recordId,
 	(id) => {
+		loadResponsibleUsers();
 		if (id) loadDetail();
 		else resetCreatePlaceholder();
 	},

+ 1 - 1
Web/src/views/aidop/business/contractReviewList.vue

@@ -63,7 +63,7 @@
 						@click="openEdit(row)"
 					>编辑</el-button>
 					<el-button
-						v-if="row.flowStatus !== 'reviewing'"
+						v-if="row.flowStatus === 'draft' || row.flowStatus === 'rejected'"
 						link type="danger"
 						@click="onDelete(row)"
 					>删除</el-button>

BIN
doc/S1产销协同_UAT测试报告v1.0.xlsx


BIN
doc/S3供应协同业务需求描述.docx


BIN
doc/S3供应协同模块蓝图设计方案.docx


BIN
doc/S4采购执行业务需求描述.docx


BIN
doc/S4采购执行模块蓝图设计方案.docx


+ 1136 - 0
doc/generate_s1_blueprint_docx.js

@@ -0,0 +1,1136 @@
+/**
+ * Generate S1 产销协同模块蓝图设计方案.docx
+ * 严格按照原文档模板格式:每个功能章节含 8 个子章节
+ *   [目标/宗旨, 业务流程图, 业务流程说明, 业务流程规则, 变化和改进点, 权限管理需求, 系统接口集成, 报表需求]
+ */
+const fs = require('fs');
+const sharp = require('sharp');
+const {
+  Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
+  HeadingLevel, AlignmentType, BorderStyle, WidthType, ShadingType,
+  convertInchesToTwip, PageBreak, ImageRun, TabStopType, TabStopPosition,
+  InternalHyperlink, TableOfContents
+} = require('docx');
+
+// ═══════════════════════════════════════════════════
+// 通用样式与辅助函数
+// ═══════════════════════════════════════════════════
+const FONT = '微软雅黑';
+const thinBorder = {
+  top: { style: BorderStyle.SINGLE, size: 1 },
+  bottom: { style: BorderStyle.SINGLE, size: 1 },
+  left: { style: BorderStyle.SINGLE, size: 1 },
+  right: { style: BorderStyle.SINGLE, size: 1 },
+};
+const headerShading = { fill: '4472C4', type: ShadingType.CLEAR };
+
+function p(text, opts = {}) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT, ...opts })],
+    spacing: { after: 120, line: 360 },
+  });
+}
+function bp(text) { return p(text, { bold: true }); }
+function empty() { return new Paragraph({ spacing: { after: 80 }, children: [] }); }
+function pageBreak() { return new Paragraph({ children: [new PageBreak()] }); }
+
+function h1(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 32, font: FONT })],
+    heading: HeadingLevel.HEADING_1,
+    spacing: { before: 400, after: 200 },
+  });
+}
+function h2(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 300, after: 150 },
+  });
+}
+function h3(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 24, font: FONT })],
+    heading: HeadingLevel.HEADING_3,
+    spacing: { before: 200, after: 100 },
+  });
+}
+function bullet(text, indent = 0) {
+  return new Paragraph({
+    children: [new TextRun({ text: `  ${text}`, size: 21, font: FONT })],
+    spacing: { after: 60, line: 340 },
+    indent: { left: 300 + indent * 400 },
+  });
+}
+
+// Table helpers
+function hdrCell(text, w = 1800) {
+  return new TableCell({
+    children: [new Paragraph({ children: [new TextRun({ text, bold: true, size: 20, font: FONT, color: 'FFFFFF' })], alignment: AlignmentType.CENTER })],
+    width: { size: w, type: WidthType.DXA }, borders: thinBorder, shading: headerShading,
+  });
+}
+function tc(text, w = 1800, opts = {}) {
+  return new TableCell({
+    children: [new Paragraph({ children: [new TextRun({ text: String(text ?? ''), size: 20, font: FONT })], alignment: opts.center ? AlignmentType.CENTER : AlignmentType.LEFT })],
+    width: { size: w, type: WidthType.DXA }, borders: thinBorder,
+  });
+}
+function table(headers, rows, widths) {
+  return new Table({
+    rows: [
+      new TableRow({ children: headers.map((h, i) => hdrCell(h, widths ? widths[i] : 1800)), tableHeader: true }),
+      ...rows.map(r => new TableRow({ children: r.map((c, i) => tc(c, widths ? widths[i] : 1800)) })),
+    ],
+    width: { size: 100, type: WidthType.PERCENTAGE },
+  });
+}
+
+// ── 活动流程表(5列) ──
+function activityTable(rows) {
+  const w = [600, 1400, 1100, 3800, 1400, 1700];
+  return table(['编号', '活动名称', '执行角色', '活动描述', '输入', '输出'], rows, w);
+}
+// ── 业务规则表(3列) ──
+function ruleTable(rows) {
+  return table(['序列号', '业务情形', '描述/方案'], rows, [900, 2800, 6300]);
+}
+// ── 接口表(4列) ──
+function interfaceTable(rows) {
+  return table(['序号', '接口名称', '接口说明', '频次及触发方式'], rows, [600, 2400, 4200, 2800]);
+}
+// ── 报表表(4列) ──
+function reportTable(rows) {
+  return table(['序号', '名称', '描述', '方案'], rows, [600, 2600, 3600, 3200]);
+}
+// ── 权限表(3列) ──
+function permTable(rows) {
+  return table(['序号', '岗位名称', '对应系统权限'], rows, [600, 2000, 7400]);
+}
+
+// ── 目录条目(模板格式:编号 + Tab + 标题 + Tab + 页码占位) ──
+// Template uses: TOC2 tab at 2880 twip, TOC3 tab at 3600 twip, left-align tab
+const TOC2_TAB = 2880;  // ~2 inches for chapter entries
+const TOC3_TAB = 3600;  // ~2.5 inches for sub-section entries
+const TOC_FONT_SIZE = 20; // 10pt, matching template
+
+function tocChapter(num, title) {
+  // TOC2 style: "1" + TAB(at 2880) + "总体业务方案" + TAB + page placeholder
+  return new Paragraph({
+    children: [
+      new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: title, bold: true, size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
+    ],
+    tabStops: [
+      { type: TabStopType.LEFT, position: TOC2_TAB },
+      { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
+    ],
+    spacing: { before: 80, after: 40 },
+  });
+}
+
+function tocSub(num, title) {
+  // TOC3 style: "1.1" + TAB(at 3600) + "目标和宗旨" + TAB + page placeholder
+  return new Paragraph({
+    children: [
+      new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: title, size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
+    ],
+    tabStops: [
+      { type: TabStopType.LEFT, position: TOC3_TAB },
+      { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
+    ],
+    spacing: { before: 30, after: 30 },
+  });
+}
+
+function buildTOC() {
+  const ch = [];
+
+  // TOC field code (user updates in Word to auto-generate with page numbers)
+  ch.push(new TableOfContents('目录', {
+    headingStyleRange: '1-2',
+    hyperlink: true,
+  }));
+
+  ch.push(new Paragraph({
+    children: [new TextRun({ text: '(如目录未显示,请在 Word 中右键此处 → 更新域)', size: 18, font: FONT, italics: true, color: '888888' })],
+    spacing: { before: 40, after: 200 },
+  }));
+
+  // ── 目录前导区(文档控制) ──
+  ch.push(tocChapter('', '文档控制'));
+  ch.push(tocSub('', '更改记录'));
+  ch.push(tocSub('', '审核'));
+  ch.push(tocSub('', '发布'));
+
+  ch.push(tocChapter('', '目录'));
+
+  // ── 第1章 ──
+  ch.push(tocChapter('1', '总体业务方案'));
+  ch.push(tocSub('1.1', '目标和宗旨'));
+  ch.push(tocSub('1.2', '总体业务流程图'));
+  ch.push(tocSub('1.3', '方案设计'));
+
+  // ── 第2~10章(8子章节) ──
+  const chSubs = [
+    '目标/宗旨', '业务流程图', '业务流程说明', '业务流程规则',
+    '变化和改进点', '权限管理需求', '系统接口集成', '报表需求',
+  ];
+  const chTitles = [
+    '产品设计管理', '订单评审管理', '订单交付管理', '订单发货管理',
+    '合同评审管理', '计划联动', '需求明细核验', '工单下达', '产销协同看板与KPI',
+  ];
+  for (let i = 0; i < chTitles.length; i++) {
+    const n = i + 2;
+    ch.push(tocChapter(String(n), chTitles[i]));
+    for (let j = 0; j < chSubs.length; j++) {
+      ch.push(tocSub(`${n}.${j + 1}`, chSubs[j]));
+    }
+  }
+
+  // ── 第11章 ──
+  ch.push(tocChapter('11', '数据中台架构'));
+  ['目标/宗旨', '分层设计', 'S1核心数据流', '对象映射总表', 'MDP作业调度'].forEach((title, i) => {
+    ch.push(tocSub(`11.${i + 1}`, title));
+  });
+
+  // ── 第12章 ──
+  ch.push(tocChapter('12', '权限管理汇总'));
+
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 流程图生成:根据步骤数组生成 SVG → sharp 转 PNG
+// ═══════════════════════════════════════════════════
+const FLOW_W = 680, BOX_W = 220, BOX_H = 42, GAP_Y = 28;
+const ROLE_X = 30, BOX_X = 130, MARGIN_Y = 40;
+
+// 流程图 PNG 缓存:key = 章节号,value = Buffer
+const flowImageCache = {};
+
+function flowImageParagraph(pngBuffer) {
+  return new Paragraph({
+    children: [new ImageRun({ data: pngBuffer, transformation: { width: 540, height: 360 }, type: 'png' })],
+    alignment: AlignmentType.CENTER,
+    spacing: { before: 120, after: 120 },
+  });
+}
+
+async function generateFlowPNG(steps) {
+  // steps: [{label: '活动名称', role: '执行角色'}, ...] — 纯纵向流程
+  const N = steps.length;
+  const svgH = MARGIN_Y * 2 + N * (BOX_H + GAP_Y) - GAP_Y;
+  const centerX = BOX_X + BOX_W / 2;
+
+  let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${FLOW_W}" height="${svgH}">`;
+  svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
+
+  steps.forEach((s, i) => {
+    const y = MARGIN_Y + i * (BOX_H + GAP_Y);
+    // 角色标签
+    svg += `<text x="${ROLE_X}" y="${y + BOX_H / 2 + 5}" font-family="Microsoft YaHei" font-size="12" fill="#666" text-anchor="start">${s.role || ''}</text>`;
+    // 节点框
+    const isStart = i === 0, isEnd = i === N - 1;
+    const fill = isStart ? '#1565C0' : (isEnd ? '#2E7D32' : '#E3F2FD');
+    const stroke = isStart ? '#0D47A1' : (isEnd ? '#1B5E20' : '#1565C0');
+    const textColor = (isStart || isEnd) ? '#FFFFFF' : '#333333';
+    svg += `<rect x="${BOX_X}" y="${y}" width="${BOX_W}" height="${BOX_H}" rx="6" fill="${fill}" stroke="${stroke}" stroke-width="1.5"/>`;
+    svg += `<text x="${centerX}" y="${y + BOX_H / 2 + 5}" font-family="Microsoft YaHei" font-size="13" fill="${textColor}" text-anchor="middle">${s.label}</text>`;
+
+    // 箭头
+    if (i < N - 1) {
+      const ay = y + BOX_H + 4, by2 = ay + GAP_Y - 8;
+      svg += `<line x1="${centerX}" y1="${ay}" x2="${centerX}" y2="${by2}" stroke="#888" stroke-width="1.5"/>`;
+      svg += `<polygon points="${centerX - 5},${by2 - 10} ${centerX + 5},${by2 - 10} ${centerX},${by2}" fill="#888"/>`;
+    }
+  });
+
+  svg += '</svg>';
+  return sharp(Buffer.from(svg)).png().toBuffer();
+}
+
+// 第1章专用:三层复合流程图(主线+支撑+数据流)
+async function generateOverviewFlowPNG() {
+  const W = 780, H = 620;
+  let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}">`;
+  svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
+
+  // ── 标题栏 ──
+  svg += `<text x="${W/2}" y="28" font-family="Microsoft YaHei" font-size="15" font-weight="bold" fill="#1F4E79" text-anchor="middle">S1 产销协同 — 总体业务流程</text>`;
+
+  // ── 区域背景 ──
+  const zones = [
+    { y: 42, h: 200, color: '#E8F0FE', label: '主线流程(订单履约链)', lx: 10, ly: 60 },
+    { y: 250, h: 120, color: '#FFF3E0', label: '支撑流程', lx: 10, ly: 268 },
+    { y: 378, h: 90, color: '#E8F5E9', label: '数据中台链路', lx: 10, ly: 396 },
+  ];
+  zones.forEach(z => {
+    svg += `<rect x="8" y="${z.y}" width="${W-16}" height="${z.h}" rx="6" fill="${z.color}" stroke="#ccc" stroke-width="0.5" stroke-dasharray="4,3"/>`;
+    svg += `<text x="${z.lx}" y="${z.ly}" font-family="Microsoft YaHei" font-size="12" font-weight="bold" fill="#555">${z.label}</text>`;
+  });
+
+  // ── 主线流程节点 ──
+  const mainNodes = [
+    { x: 30, y: 85, w: 130, h: 38, label: '销售订单接收', color: '#1565C0' },
+    { x: 190, y: 85, w: 130, h: 38, label: '订单评审\n(交期评估)', color: '#1565C0' },
+    { x: 350, y: 85, w: 110, h: 38, label: '工单下达', color: '#1565C0' },
+    { x: 485, y: 85, w: 110, h: 38, label: '排产执行', color: '#6C757D' },
+    { x: 620, y: 85, w: 110, h: 38, label: '采购备料', color: '#6C757D' },
+    { x: 485, y: 155, w: 110, h: 38, label: '生产制造', color: '#6C757D' },
+    { x: 620, y: 155, w: 110, h: 38, label: '成品入库', color: '#2E7D32' },
+    { x: 350, y: 170, w: 100, h: 38, label: '交期确认', color: '#1565C0' },
+    { x: 215, y: 170, w: 100, h: 38, label: '变更管理', color: '#F57C00' },
+  ];
+  // 子流程节点 (smaller, secondary)
+  const subNodes = [
+    { x: 30, y: 170, w: 145, h: 38, label: '发货计划/ASN发货', color: '#2E7D32' },
+    { x: 190, y: 170, w: 24, h: 0, label: '', color: '' }, // dummy
+  ];
+
+  [...mainNodes, subNodes[0]].filter(n => n.h > 0).forEach(n => {
+    const fill = n.color === '#6C757D' ? '#E0E0E0' : n.color;
+    const stroke = n.color === '#6C757D' ? '#999' : n.color;
+    const tc = n.color === '#6C757D' ? '#555' : '#fff';
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + n.h / 2;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="11" fill="${tc}" text-anchor="middle">${l}</text>`;
+    });
+  });
+
+  // 主线箭头 (simplified)
+  const arrows = [
+    { x1: 160, y1: 104, x2: 190, y2: 104 },  // 接收→评审
+    { x1: 320, y1: 104, x2: 350, y2: 104 },   // 评审→工单
+    { x1: 460, y1: 104, x2: 485, y2: 104 },   // 工单→排程
+    { x1: 595, y1: 104, x2: 620, y2: 104 },   // 排程→采购
+    { x1: 595, y1: 123, x2: 540, y2: 155 },   // 采购→生产 (down-left)
+    { x1: 540, y1: 193, x2: 460, y2: 185 },   // 生产→入库 (left)
+    { x1: 350, y1: 123, x2: 350, y2: 170 },   // 工单→交期确认 (down)
+  ];
+  arrows.forEach(a => {
+    svg += `<line x1="${a.x1}" y1="${a.y1}" x2="${a.x2}" y2="${a.y2}" stroke="#666" stroke-width="1.2"/>`;
+    const ang = Math.atan2(a.y2 - a.y1, a.x2 - a.x1);
+    const lx = a.x2 - 6 * Math.cos(ang), ly = a.y2 - 6 * Math.sin(ang);
+    svg += `<polygon points="${lx + 4 * Math.sin(ang)},${ly - 4 * Math.cos(ang)} ${lx - 4 * Math.sin(ang)},${ly + 4 * Math.cos(ang)} ${lx + 6 * Math.cos(ang)},${ly + 6 * Math.sin(ang)}" fill="#666"/>`;
+  });
+  // 灰色标记:虚线边框表示"跨模块就绪,待其他模块补齐"
+  svg += `<text x="530" y="108" font-family="Microsoft YaHei" font-size="9" fill="#999" text-anchor="middle">(S2/S4/S5/S6/S7)</text>`;
+
+  // ── 支撑流程节点 ──
+  const suppNodes = [
+    { x: 40, y: 295, w: 160, h: 36, label: '产品设计管理', color: '#E65100' },
+    { x: 250, y: 295, w: 160, h: 36, label: '合同评审管理', color: '#E65100' },
+    { x: 460, y: 295, w: 160, h: 36, label: '需求明细核验', color: '#E65100' },
+  ];
+  suppNodes.forEach(n => {
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${n.color}" stroke="${n.color}" stroke-width="1.2"/>`;
+    svg += `<text x="${n.x + n.w/2}" y="${n.y + n.h/2 + 4}" font-family="Microsoft YaHei" font-size="11" fill="#fff" text-anchor="middle">${n.label}</text>`;
+  });
+  // 支撑箭头
+  svg += `<line x1="200" y1="313" x2="250" y2="313" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="246,308 246,318 253,313" fill="#666"/>`;
+  svg += `<line x1="410" y1="313" x2="460" y2="313" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="456,308 456,318 463,313" fill="#666"/>`;
+
+  // ── 数据流节点 ──
+  const dataNodes = [
+    { x: 40, y: 418, w: 140, h: 36, label: '业务运行表', color: '#2E7D32' },
+    { x: 210, y: 418, w: 120, h: 36, label: 'mdp_stg_*', color: '#6A1B9A' },
+    { x: 360, y: 418, w: 120, h: 36, label: 'mdp_std_*', color: '#6A1B9A' },
+    { x: 510, y: 418, w: 120, h: 36, label: 'dwd_* (宽表)', color: '#0D47A1' },
+    { x: 660, y: 418, w: 100, h: 36, label: 'KPI 指标层', color: '#B71C1C' },
+  ];
+  dataNodes.forEach(n => {
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${n.color}" stroke="${n.color}" stroke-width="1.2"/>`;
+    svg += `<text x="${n.x + n.w/2}" y="${n.y + n.h/2 + 4}" font-family="Microsoft YaHei" font-size="10" fill="#fff" text-anchor="middle">${n.label}</text>`;
+  });
+  // 数据箭头
+  for (let i = 0; i < dataNodes.length - 1; i++) {
+    const a = dataNodes[i], b = dataNodes[i + 1];
+    const ax = a.x + a.w, ay = a.y + a.h / 2;
+    const bx = b.x;
+    svg += `<line x1="${ax}" y1="${ay}" x2="${bx}" y2="${ay}" stroke="#666" stroke-width="1.2"/>`;
+    svg += `<polygon points="${bx - 6},${ay - 4} ${bx - 6},${ay + 4} ${bx},${ay}" fill="#666"/>`;
+  }
+
+  // ── 层级标注 ──
+  svg += `<text x="45" y="475" font-family="Microsoft YaHei" font-size="10" fill="#555">STG(贴源)</text>`;
+  svg += `<text x="215" y="475" font-family="Microsoft YaHei" font-size="10" fill="#555">STD(标准)</text>`;
+  svg += `<text x="365" y="475" font-family="Microsoft YaHei" font-size="10" fill="#555">DWD(宽表)</text>`;
+  svg += `<text x="515" y="475" font-family="Microsoft YaHei" font-size="10" fill="#555">KPI(指标)</text>`;
+
+  // ── 串联标注(支撑→主线) ──
+  svg += `<text x="150" y="240" font-family="Microsoft YaHei" font-size="10" fill="#888" text-anchor="middle">↓ 为订单评审提供设计/合同/物料约束 ↓</text>`;
+
+  svg += '</svg>';
+  return sharp(Buffer.from(svg)).png().toBuffer();
+}
+
+// ═══════════════════════════════════════════════════
+// 章节构建函数:自动生成 8 子章节
+// ═══════════════════════════════════════════════════
+// cfg.flowSteps: [{label, role}] → 自动生成纵向流程图
+// cfg.flowImage: Buffer → 直接嵌入(优先于 flowSteps)
+function buildChapter(num, title, cfg) {
+  const ch = [];
+  const pre = `${num}.`;
+
+  // ── §X.1 目标/宗旨 ──
+  ch.push(h2(`${pre}1  目标/宗旨`));
+  (cfg.targets || []).forEach(t => ch.push(bullet(t)));
+
+  // ── §X.2 业务流程图 ──
+  ch.push(h2(`${pre}2  业务流程图`));
+  const img = cfg.flowImage || (cfg.flowSteps ? flowImageCache[num] : null);
+  if (img) {
+    ch.push(flowImageParagraph(img));
+  } else {
+    ch.push(p('(见系统操作流程图 / 附件 Visio 流程图)'));
+  }
+  if (cfg.flowNote) ch.push(p(cfg.flowNote));
+
+  // ── §X.3 业务流程说明 ──
+  ch.push(h2(`${pre}3  业务流程说明`));
+  if (cfg.activities && cfg.activities.length) {
+    ch.push(activityTable(cfg.activities));
+  } else {
+    ch.push(p('参照系统页面操作流程,主要操作步骤包含:'));
+    (cfg.activityDesc || []).forEach(a => ch.push(bullet(a)));
+  }
+
+  // ── §X.4 业务流程规则 ──
+  ch.push(h2(`${pre}4  业务流程规则`));
+  if (cfg.rules && cfg.rules.length) {
+    ch.push(ruleTable(cfg.rules));
+  } else {
+    ch.push(p('无特殊业务规则'));
+  }
+
+  // ── §X.5 变化和改进点 ──
+  ch.push(h2(`${pre}5  变化和改进点`));
+  (cfg.improvements || []).forEach(i => ch.push(bullet(i)));
+
+  // ── §X.6 权限管理需求 ──
+  ch.push(h2(`${pre}6  权限管理需求`));
+  if (cfg.permissions && cfg.permissions.length) {
+    ch.push(permTable(cfg.permissions));
+  } else {
+    ch.push(p('参照第 12 章 权限管理汇总'));
+  }
+
+  // ── §X.7 系统接口集成 ──
+  ch.push(h2(`${pre}7  系统接口集成`));
+  if (cfg.interfaces && cfg.interfaces.length) {
+    ch.push(interfaceTable(cfg.interfaces));
+  } else {
+    ch.push(p('无外部系统接口;页面数据通过 Admin.NET 内置 API 获取'));
+  }
+
+  // ── §X.8 报表需求 ──
+  ch.push(h2(`${pre}8  报表需求`));
+  if (cfg.reports && cfg.reports.length) {
+    ch.push(reportTable(cfg.reports));
+  } else {
+    ch.push(p('无独立报表需求,相关数据通过看板和列表页面查看'));
+  }
+
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 章节数据配置
+// ═══════════════════════════════════════════════════
+
+// ── Chapter 2: 产品设计管理 ──
+const _ch2cfg = {
+  targets: [
+    '实现产品设计任务的在线管理,涵盖产品设计BOM和工艺路线的录入与维护。',
+    '将产品设计任务纳入 KPI 考核体系,监控设计周期、满足率和人效指标。',
+    '产品设计采用直接保存模式,无审批流环节,保存即生效。',
+  ],
+  flowNote: '流程路径:产品设计新建 → BOM子表录入 → 工艺子表录入 → 保存 → 设计记录生效。',
+  flowSteps: [
+    { label: '新建产品设计任务', role: '设计员' },
+    { label: 'BOM子表录入', role: '设计员' },
+    { label: '工艺子表录入', role: '设计员' },
+    { label: '保存生效', role: '设计员' },
+  ],
+  activities: [
+    ['10', '新建产品设计任务', '设计员', '录入产品设计基本信息,选择成品物料编码,填写设计要求', '产品需求', '产品设计任务'],
+    ['20', 'BOM子表录入', '设计员', '在BOM子表中新增行,选择子物料编码,填写单位用量和损耗率', '物料主数据', 'BOM清单'],
+    ['30', '工艺子表录入', '设计员', '在工艺子表中新增行,通过下拉框选择产线,手动输入路线编码', '产线数据', '工艺路线'],
+    ['40', '保存', '设计员', '填写完整信息后点击保存,单据编号自动生成,数据即时生效', '产品设计任务', '已保存记录'],
+  ],
+  rules: [
+    ['1', 'BOM子表物料选择', '选择子物料编码后系统自动填入物料名称;父级物料关系通过任务主表关联;单位用量和固定损耗支持手动编辑'],
+    ['2', '工艺子表产线选择', '产线列显示下拉框,从LineMaster表远程搜索选择,显示格式为「Line|Describe」;路线编码支持手动输入'],
+    ['3', '保存/编辑/删除', '保存后列表显示新记录、单据编号自动生成;编辑后字段正确更新;删除后记录移除且关联BOM/工艺子表数据同步删除'],
+    ['4', '必填字段校验', '物料编码、任务名称、计划完成日期为必填项;BOM子表至少保留一行;前后端双重校验'],
+    ['5', '无审批流', '产品设计采用直接保存模式,保存即生效,无需经过审批流程;无审批状态列'],
+  ],
+  improvements: [
+    '产品设计任务实现在线化闭环管理,替代线下Excel跟踪方式。',
+    'BOM和工艺信息直接录入系统,避免数据孤岛。',
+    '设计周期、满足率和人效纳入KPI自动化考核。',
+    '采用直接保存模式,简化操作流程,提升设计效率。',
+  ],
+  permissions: [
+    ['1', '设计员', '新建/编辑/删除产品设计任务;录入BOM和工艺子表;保存操作'],
+    ['2', '计划工程师', '查看产品设计列表和KPI指标'],
+  ],
+  interfaces: [
+    ['1', '物料主数据同步', '通过MDP从SAP定时同步物料主数据,供产品设计选择使用', '定时(日)'],
+    ['2', '产线主数据查询', '工艺子表通过API实时查询LineMaster表获取产线列表', '实时(下拉触发)'],
+  ],
+  reports: [
+    ['1', '产品设计周期分析', '统计各设计任务的计划周期与实际完成周期差异', '按任务/按月度'],
+    ['2', '产品设计满足率', '统计按时完成的设计任务占比', '按月度/按人员'],
+  ],
+};
+const ch2 = () => buildChapter('2', '产品设计管理', _ch2cfg);
+
+// ── Chapter 3: 订单评审管理 ──
+const _ch3cfg = {
+  targets: [
+    '实现销售订单的在线录入、查询、编辑和删除操作。',
+    '对无法被成品库存直接满足的订单进行交期评审,给出预计交付日期。',
+    '支持订单变更管理,变更记录可追溯,变更时通过审批流管控。',
+    '订单主流程无审批流:录入→评审→确认直接完成;仅变更场景触发审批。',
+  ],
+  flowNote: '流程路径:订单信息录入 → 订单评审(交期评估) → 交期确认 → 如需变更则发起变更审批流。',
+  flowSteps: [
+    { label: '订单信息录入', role: '销售/计划员' },
+    { label: '订单评审(交期评估)', role: '计划员' },
+    { label: '交期确认 / 资源锁定', role: '计划员' },
+    { label: '变更审批(如需要)', role: '审批人' },
+  ],
+  activities: [
+    ['10', '订单列表查询', '销售/计划员', '通过订单号、客户、日期范围等条件筛选查询订单', '查询条件', '订单列表'],
+    ['20', '订单新增保存', '销售/计划员', '录入销售订单基本信息(客户、物料、数量、交期等)', '销售需求', '待评审订单'],
+    ['30', '订单评审/交期评估', '计划员', '系统根据成品库存、在制工单、物料齐套约束给出预计交期', '订单信息', '建议交期'],
+    ['40', '交期确认', '计划员', '确认交期结果,锁定产能和物料资源', '建议交期', '已确认订单'],
+    ['50', '订单变更(审批流)', '销售/计划员→审批人', '交期确认后如需修改订单,发起变更申请,走审批流;变更记录自动归档并累加次数', '变更需求', '变更记录/审批流'],
+  ],
+  rules: [
+    ['1', '订单编号', '订单编号全局唯一,保存时自动生成'],
+    ['2', '订单主流程无审批', '订单从录入到评审到交期确认,无需经过审批流;保存和确认操作直接生效'],
+    ['3', '变更审批流', '交期确认后如需修改订单,必须发起变更申请并通过审批流;变更记录可追溯,变更次数+1'],
+    ['4', '变更历史可查', '变更历史记录在订单交付Tab2(评审信息)中查看'],
+    ['5', '订单类型', '订单类型分为「销售订单」和「计划订单」,系统按类型区别处理'],
+    ['6', '交期确认', '调用外部交期确认接口返回OK表示确认成功;确认后订单状态更新为已确认'],
+    ['7', 'closed字段', '订单closed字段默认值为0(未关闭),订单关闭时更新为1'],
+  ],
+  improvements: [
+    '订单管理实现在线化,替代线下Excel和纸质单据管理方式。',
+    '变更记录自动归档,每次变更次数累计,支持历史追踪,审批流仅在变更时触发。',
+    '交期评估系统化,替代人工估算方式。',
+    '主流程简化(无审批),仅变更场景走审批流,降低操作复杂度。',
+  ],
+  permissions: [
+    ['1', '销售/客服专员', '查看订单列表;新建/编辑/删除订单;发起变更申请'],
+    ['2', '计划工程师', '查看订单列表;执行订单评审和交期确认操作'],
+    ['3', '审批人', '审核订单变更申请(仅变更场景触发)'],
+  ],
+  interfaces: [
+    ['1', '销售订单同步', '通过MDP从SAP定时同步销售订单到中台贴源层mdp_stg_so', '定时(日)'],
+    ['2', '交期确认接口', '调用外部交期确认接口(不含Authorization头),返回OK表示成功', '手动触发'],
+    ['3', '订单列表API', 'GET /api/Order/list 读取mdp_std_so标准层数据', '实时(页面加载)'],
+  ],
+  reports: [
+    ['1', '订单评审周期分析', '统计从订单录入到交期确认的各环节耗时', '按月度/按客户'],
+    ['2', '订单变更频率分析', '统计订单变更次数和变更原因分布', '按月度/按产品线'],
+  ],
+};
+const ch3 = () => buildChapter('3', '订单评审管理', _ch3cfg);
+
+// ── Chapter 4: 订单交付管理 ──
+const _ch4cfg = {
+  targets: [
+    '以订单行(明细)为粒度,集中展示每笔订单的交付状态。',
+    '通过8个Tab页签实现从"订单接收"到"成品发运"的全链路履约追踪。',
+    '接入数据中台宽表层dwd_ship_trans,实现交付数据的统一口径。',
+  ],
+  flowNote: '流程路径:订单交付列表 → 多条件筛选 → 点击订单行 → 8Tab全景追踪(订单信息→评审→排程→物料需求→采购→备料→生产→入库发运)。',
+  flowSteps: [
+    { label: '交付列表筛选查询', role: '计划员/物流' },
+    { label: '点击订单行进入详情', role: '计划员/物流' },
+    { label: 'Tab1 查看订单信息', role: '计划员/物流' },
+    { label: 'Tab2 查看评审/变更', role: '计划员/物流' },
+    { label: 'Tab4 查看物料需求', role: '计划员/物流' },
+    { label: 'Tab8 入库发运追踪', role: '计划员/物流' },
+  ],
+  activities: [
+    ['10', '交付列表查询', '计划员/物流', '通过订单号、客户、交付状态、日期范围等条件筛选', '筛选条件', '交付列表数据'],
+    ['20', '进入交付详情', '计划员/物流', '点击交付列表中的订单行,进入8Tab全景追踪页面', '订单行ID', '交付详情'],
+    ['30', '查看订单信息(Tab1)', '计划员/物流', '查看订单基础档案信息(订单编号、客户、状态、审核人等)', 'mdp_std_so', '订单档案'],
+    ['40', '查看评审信息(Tab2)', '计划员/物流', '查看交期评估结果和订单变更历史记录', 'dwd_requirement_examine_detail', '评审详情'],
+    ['50', '查看物料需求(Tab4)', '计划员/物流', '查看BOM展开、库存占用、缺料分析和替代料信息', 'dwd_requirement_examine_detail', '物料需求明细'],
+  ],
+  rules: [
+    ['1', '交付列表数据来源', '读取dwd_ship_trans宽表,只取最新成功批次(sync_batch_id=max);确保数据口径一致'],
+    ['2', '交付列表字段', '客户编码、订单号、工单编号、物料编码、物料名称、规格型号、数量、客户单号、计划交付日期、实际交付日期、交付状态'],
+    ['3', '交付状态流转', '待评审→已评审→排产中→采购中→生产中→已入库→已发运'],
+    ['4', '8Tab数据来源', 'Tab1读mdp_std_so;Tab2读dwd_requirement_examine_detail+mdp_stg_so变更;Tab4读dwd_requirement_examine_detail;Tab8发运读mdp_std_ship_trans'],
+    ['5', '跨模块Tab渐进就绪', 'Tab3排程待S2补齐;Tab5采购待S4补齐;Tab6备料待S6/WMS补齐;Tab7报工IQC待S5/S6补齐;Tab8 FQC入库待S7补齐'],
+  ],
+  improvements: [
+    '交付状态一屏展示,替代多地分散查询的方式。',
+    '8Tab全景追踪实现"一单到底",从订单到发运全链路可视化。',
+    '接入数据中台宽表,确保各页面读取的数据口径一致。',
+    '跨模块Tab结构已就绪,后续模块补齐后自动填充数据。',
+  ],
+  permissions: [
+    ['1', '计划工程师', '查看交付列表和详情;筛选和排序操作'],
+    ['2', '物流/仓管', '查看交付列表;查看发运相关Tab'],
+    ['3', '销售/客服', '查看交付列表;跟踪订单交付状态'],
+  ],
+  interfaces: [
+    ['1', '订单交付列表API', 'GET /api/Order/delivery/list 读取dwd_ship_trans', '实时(页面加载)'],
+    ['2', '订单交付详情API', 'GET /api/Order/delivery/flow?entryId= 聚合多表数据', '实时(点击触发)'],
+    ['3', '需求核验API', 'GET /api/RequirementExamineDetail/list 读取dwd_requirement_examine_detail', '实时'],
+  ],
+  reports: [
+    ['1', '订单交付状态统计', '按交付状态汇总订单数量,分析履约瓶颈', '按日/周/月'],
+    ['2', '订单交付周期分析', '统计从订单确认到实际交付的各阶段耗时', '按订单/按客户'],
+  ],
+};
+const ch4 = () => buildChapter('4', '订单交付管理', _ch4cfg);
+
+// ── Chapter 5: 订单发货管理 ──
+const _ch5cfg = {
+  targets: [
+    '实现发货单的在线录入、查询、编辑和删除管理。',
+    '通过销售单号关联,将发货单与销售订单联动。',
+    '实现发货计划覆盖追踪和ASN发货记录管理。',
+    '将发货数据纳入KPI考核(发货计划覆盖率、发货达成率)。',
+  ],
+  flowNote: '流程路径:发货计划制定 → 发货单新建 → 选择销售单号 → 填写发货信息 → 保存发货单 → 数据进入MDP中台。',
+  flowSteps: [
+    { label: '制定发货计划', role: '物流/仓管' },
+    { label: '新建发货单', role: '物流/仓管' },
+    { label: '选择关联销售单号', role: '物流/仓管' },
+    { label: '填写发货明细行', role: '物流/仓管' },
+    { label: '保存 → MDP数据同步', role: '系统(MDP)' },
+  ],
+  activities: [
+    ['10', '发货单列表查询', '物流/仓管', '通过发货单号、销售单号、客户、日期范围筛选', '查询条件', '发货单列表'],
+    ['20', '新建发货单', '物流/仓管', '选择销售单号(下拉搜索),选择客户,填写发货信息', '销售订单', '发货单'],
+    ['30', '发货明细行管理', '物流/仓管', '在明细子表中新增行,填写物料、数量、批次等信息', '发货单', '发货明细'],
+    ['40', 'ASN发货录入', '物流/仓管', '录入ASN发货单号、物料、发货数量、发货日期', '发货通知', 'ASN记录'],
+    ['50', '数据同步中台', '系统(MDP)', '发货计划和ASN数据定时同步至mdp_stg_ship_trans,经标准化后进入dwd_ship_trans', '运行时表', '中台宽表'],
+  ],
+  rules: [
+    ['1', '发货单号自动生成', '保存时自动生成,格式为SH+日期+流水号'],
+    ['2', '销售单号下拉框', '支持远程搜索,回显格式为「BillNo|客户名」'],
+    ['3', '客户下拉框', '支持远程搜索选择客户'],
+    ['4', '明细行管理', '支持增删行;物料选择后自动填入物料名称;至少保留一行'],
+    ['5', '已发货不可删除', '已发货的发货单不可删除,防止数据丢失'],
+    ['6', '发货计划覆盖', '发货计划与销售订单关联,用于计算S1_L2_013发货计划覆盖率'],
+    ['7', 'ASN发货追踪', 'ASN发货记录进入mdp_std_ship_trans,用于计算S1_L2_014发货达成率'],
+  ],
+  improvements: [
+    '发货单实现在线管理,替代线下纸质单据流转。',
+    '发货单号和销售单号自动关联,数据联动减少重复录入。',
+    '发货数据和KPI自动对接,发货计划覆盖率和发货达成率自动计算。',
+  ],
+  permissions: [
+    ['1', '物流/仓管', '新建/编辑/删除发货单和ASN发货单;管理发货明细行'],
+    ['2', '计划工程师', '查看发货列表和发货KPI指标'],
+  ],
+  interfaces: [
+    ['1', '发货数据同步', '运行时表ShippingPlan→mdp_stg_ship_trans→mdp_std_ship_trans→dwd_ship_trans', '定时(日)'],
+    ['2', 'ASN数据同步', '运行时表ASNBOLShipper→mdp_stg_ship_trans→mdp_std_ship_trans', '定时(日)'],
+  ],
+  reports: [
+    ['1', '发货计划覆盖率', 'S1_L2_013:统计有发货计划覆盖的订单占比', '按月度/按客户'],
+    ['2', '发货达成率', 'S1_L2_014:统计实际发货与计划发货的一致率', '按月度/按产品线'],
+  ],
+};
+const ch5 = () => buildChapter('5', '订单发货管理', _ch5cfg);
+
+// ── Chapter 6: 合同评审管理 ──
+const _ch6cfg = {
+  targets: [
+    '实现客户合同的在线评审管理,与销售订单评审协同运作。',
+    '通过审批流管控合同评审的多级审核流程。',
+    '将合同评审数据纳入KPI考核体系(周期、满足率、人效及L3/L4节点指标)。',
+  ],
+  flowNote: '流程路径:合同录入 → 合同评审列表 → 提交审批 → 审批流流转 → 审核通过/驳回 → KPI计算。',
+  flowSteps: [
+    { label: '合同录入', role: '商务人员' },
+    { label: '查看合同评审列表', role: '商务人员' },
+    { label: '提交审批流', role: '商务人员' },
+    { label: '多级审批流转', role: '审批人' },
+    { label: '审核通过/驳回→KPI计算', role: '系统' },
+  ],
+  activities: [
+    ['10', '合同评审列表查询', '商务人员', '查看合同评审列表,支持多条件筛选', '查询条件', '合同评审列表'],
+    ['20', '新建合同评审', '商务人员', '录入合同基本信息(合同编号、客户、签订日期等)', '合同信息', '待评审合同'],
+    ['30', '提交审批', '商务人员', '提交合同进入审批流(审批流类型:CONTRACT_REVIEW)', '待评审合同', '审批中合同'],
+    ['40', '审批流流转', '审批人', '多级审批节点流转,同意或驳回', '审批中合同', '已完成/已驳回'],
+  ],
+  rules: [
+    ['1', '审批流类型', '使用CONTRACT_REVIEW审批流类型,通过IFlowBizHandler接口回调处理业务逻辑'],
+    ['2', 'KPI关联', '合同评审周期(S1_L2_001)、满足率(S1_L2_002)、人效(S1_L2_003)自动计算'],
+    ['3', 'L3/L4节点指标', '合同评审流程每个审批节点耗时单独统计(S1_L3_001~005、S1_L4_001~004)'],
+    ['4', '数据链路', '运行时表ado_contract_review→mdp_stg_so贴源→KPI直接消费贴源层计算'],
+  ],
+  improvements: [
+    '合同评审实现线上审批,替代纸质审批流程。',
+    '合同评审周期和节点耗时自动统计,支持流程优化分析。',
+    '与销售订单评审数据协同,形成完整的评审闭环。',
+  ],
+  permissions: [
+    ['1', '商务人员', '新建/编辑合同评审;提交审批'],
+    ['2', '审批人', '多级审核合同(同意/驳回)'],
+    ['3', '计划工程师', '查看合同评审列表和KPI指标'],
+  ],
+  interfaces: [
+    ['1', '合同评审数据同步', '运行时表ado_contract_review→mdp_stg_so→KPI计算', '定时(日)'],
+  ],
+  reports: [
+    ['1', '合同评审周期分析', 'S1_L2_001:统计合同审批平均周期', '按月度'],
+    ['2', '合同评审满足率', 'S1_L2_002:统计合同评审通过率', '按月度'],
+    ['3', '合同节点耗时统计', 'S1_L3/L4:统计各审批节点耗时分布', '按节点/按月度'],
+  ],
+};
+const ch6 = () => buildChapter('6', '合同评审管理', _ch6cfg);
+
+// ── Chapter 7: 计划联动 ──
+const _ch7cfg = {
+  targets: [
+    '实现销售订单、发货计划、ASN发货等业务数据的联动整合。',
+    '通过MDP同步转换,将多源数据汇聚到dwd_ship_trans宽表。',
+    '提供手动刷新机制,支持业务人员触发最新的数据联动计算。',
+  ],
+  flowNote: '流程路径:计划联动列表 → 查看联动状态 → 点击刷新按钮 → 触发S1_MDP_SYNC_TRANSFORM作业 → 数据联动更新 → 返回最新联动结果。',
+  flowSteps: [
+    { label: '查看联动列表/状态', role: '计划员' },
+    { label: '点击手动刷新按钮', role: '计划员' },
+    { label: 'MDP执行全量同步转换', role: '系统(MDP)' },
+    { label: 'STG→STD→DWD→KPI 链路', role: '系统(MDP)' },
+    { label: '返回最新联动结果', role: '系统' },
+  ],
+  activities: [
+    ['10', '查看联动列表', '计划员', '查看各业务对象的联动状态(订单/发货/ASN)', '—', '联动列表'],
+    ['20', '手动刷新', '计划员', '点击刷新按钮,触发S1_MDP_SYNC_TRANSFORM全量同步转换', '刷新请求', '最新联动数据'],
+    ['30', 'MDP同步转换', '系统(MDP)', '执行STG贴源→STD标准化→DWD宽表构建→KPI计算全链路', 'S1_MDP作业', '更新后数据'],
+    ['40', '查看联动结果', '计划员', '刷新完成后查看最新的联动达成率和数据一致性状态', 'MDP结果', '联动报告'],
+  ],
+  rules: [
+    ['1', '作业编码', 'S1_MDP_SYNC_TRANSFORM,批次格式S1_MDP_FULL_yyyyMMddHHmmss'],
+    ['2', '联动数据源', '订单(crm_seorder)、发货计划(ShippingPlan)、ASN(ASNBOLShipper)、联动(LinkagePlan)'],
+    ['3', '联动状态', '联动成功/未联动/数据异常三种状态'],
+    ['4', '刷新方式', '支持手动触发;未来可扩展定时调度'],
+    ['5', '监控入口', 'mdp_transform_run_log记录作业状态;mdp_sync_log记录实体同步日志'],
+    ['6', 'KPI关联', 'S1_L2_015计划联动达成率自动计算'],
+  ],
+  improvements: [
+    '多源数据自动联动,替代人工Excel手工比对方式。',
+    '手动刷新机制灵活可控,业务人员可根据需要随时触发。',
+    '作业日志可观测,异常可追溯。',
+  ],
+  permissions: [
+    ['1', '计划工程师', '查看联动列表;执行手动刷新操作;查看联动KPI'],
+    ['2', '系统管理员', '监控MDP作业状态;处理执行异常'],
+  ],
+  interfaces: [
+    ['1', '手动刷新API', 'POST /api/Order/linkageplan/refresh 触发全量同步转换', '手动触发'],
+    ['2', 'MDP作业监控', 'mdp_transform_run_log/mdp_sync_log记录作业状态', '作业执行时写入'],
+    ['3', '联动数据同步', 'LinkagePlan运行数据→mdp_stg_ship_trans→mdp_std_ship_trans→dwd_ship_trans', '定时(日)/手动'],
+  ],
+  reports: [
+    ['1', '计划联动达成率', 'S1_L2_015:统计联动成功的业务对象占比', '按批次/按日'],
+    ['2', 'MDP作业状态监控', '查看每次MDP转换作业的执行状态、处理行数和耗时', '按批次'],
+  ],
+};
+const ch7 = () => buildChapter('7', '计划联动', _ch7cfg);
+
+// ── Chapter 8: 需求明细核验 ──
+const _ch8cfg = {
+  targets: [
+    '展示销售订单的BOM需求展开与核验结果。',
+    '帮助计划人员确认物料需求数量和到货时间的准确性。',
+    '核验数据服务于订单交付Tab2评审和Tab4物料需求。',
+  ],
+  flowNote: '流程路径:需求核验列表 → 多条件筛选 → 查看核验结果 → 核验结果同步展示在订单交付Tab2和Tab4。',
+  flowSteps: [
+    { label: '核验列表筛选查询', role: '计划员' },
+    { label: '查看BOM需求展开', role: '计划员' },
+    { label: '确认库存占用/缺料', role: '计划员' },
+    { label: '数据同步到交付Tab2/Tab4', role: '系统' },
+  ],
+  activities: [
+    ['10', '核验列表查询', '计划员', '通过订单号、物料编码、核验状态等条件筛选', '筛选条件', '核验列表'],
+    ['20', '查看核验明细', '计划员', '查看订单行的BOM需求展开、库存占用、缺料分析和替代料信息', '核验结果', '需求明细'],
+    ['30', '同步订单交付', '系统', '核验结果自动同步到订单交付详情Tab2和Tab4', '核验数据', '交付详情'],
+  ],
+  rules: [
+    ['1', '数据来源', '运行时表b_examine_result/b_bom_child_examine→mdp_stg_so→dwd_requirement_examine_detail'],
+    ['2', '核验内容', '物料编码、BOM编码、需求数量、缺料数量、替代料方案、满足时间'],
+    ['3', '数据过滤', '只取最新成功批次的核验数据,确保数据一致性'],
+    ['4', '跨页面复用', '核验结果同时在需求核验页面和订单交付Tab2/Tab4中使用'],
+  ],
+  improvements: [
+    '需求核验数据在线化,替代手工Excel核对方式。',
+    '核验结果跨页面共享,避免数据重复计算。',
+    '接入中台DWD层,数据口径统一。',
+  ],
+  permissions: [
+    ['1', '计划工程师', '查看需求核验列表;筛选和排序操作'],
+  ],
+  interfaces: [
+    ['1', '需求核验API', 'GET /api/RequirementExamineDetail/list 读取dwd_requirement_examine_detail', '实时(页面加载)'],
+    ['2', '核验数据同步', 'b_examine_result→mdp_stg_so→dwd_requirement_examine_detail', '定时(日)'],
+  ],
+  reports: [],
+};
+const ch8 = () => buildChapter('8', '需求明细核验', _ch8cfg);
+
+// ── Chapter 9: 工单下达 ──
+const _ch9cfg = {
+  targets: [
+    '管理生产工单的下达,是产销协同到制造执行的衔接环节。',
+    '下达前必须执行物料齐套检查和物料需求同步(生成MRP),确保物料准备到位。',
+    '工单逐单下达(非批量),每行操作列独立触发,下达后MES系统可见。',
+    '支持工单状态的全程跟踪。',
+  ],
+  flowNote: '流程路径:工单列表 → 物料齐套检查(工具栏按钮) → 生成物料需求/同步MRP → 点击单行[工单下达]按钮 → 填写下达信息确认 → 工单状态变更 → MES可见。',
+  flowSteps: [
+    { label: '工单列表筛选查询', role: '计划员/调度' },
+    { label: '物料齐套检查', role: '计划员/调度' },
+    { label: '生成物料需求(MRP同步)', role: '计划员/调度' },
+    { label: '逐行点击工单下达', role: '计划员/调度' },
+    { label: '确认下达→状态变更→MES可见', role: '系统' },
+  ],
+  activities: [
+    ['10', '工单列表查询', '计划员/调度', '通过工单号、物料编码、开工日期、状态筛选', '查询条件', '工单列表'],
+    ['20', '物料齐套检查', '计划员/调度', '点击工具栏"物料齐套检查"按钮,调用外部资源审查服务检查各工单物料齐套状态', '工单列表', '齐套状态列更新'],
+    ['30', '生成物料需求', '计划员/调度', '点击工具栏"生成物料需求"按钮,同步生成物料需求计划(MRP)', '工单列表', '物料需求计划生成'],
+    ['40', '工单下达(逐单)', '计划员/调度', '点击单行操作列"工单下达"按钮,弹出下达表单填写开工日期和生产批号,确认后工单状态变更为"已下达"', '单行工单', '已下达工单'],
+    ['50', '工单状态跟踪', '计划员/调度', '按状态筛选工单,跟踪工单从下达到完成的全过程', '工单列表', '状态跟踪'],
+  ],
+  rules: [
+    ['1', '工单状态', '待下达→已下达→生产中→已完成→已关闭;下达前状态为"待下达"'],
+    ['2', '防重复下达', '已下达工单不可重复下达;前端按钮置灰+后端校验双重保障'],
+    ['3', '逐单下达', '工单逐行单个下达,操作列每行独立[工单下达]按钮,不支持批量一键下达'],
+    ['4', '物料齐套检查', '下达前必须先通过工具栏"物料齐套检查"确认物料准备状态;调用外部资源审查服务'],
+    ['5', '物料需求同步', '下达前需按需通过工具栏"生成物料需求"同步生成MRP物料需求计划'],
+    ['6', '下游联动', '下达后MES系统可见,进入S6生产执行阶段'],
+    ['7', '工单编码', '工单号全局唯一,由上游系统生成'],
+  ],
+  improvements: [
+    '工单下达实现在线逐单操作,替代线下手工派发方式。',
+    '物料齐套检查与物料需求同步(MRP生成)前置化,确保下达前物料准备到位。',
+    '工单状态实时跟踪,下游系统联动自动化。',
+    '防重复下达机制保障数据安全。',
+  ],
+  permissions: [
+    ['1', '计划员/调度', '查看工单列表;执行工单下达操作;跟踪工单状态'],
+  ],
+  interfaces: [
+    ['1', '工单下达列表API', 'GET /api/WorkOrder/dispatch/list', '实时(页面加载)'],
+    ['2', '物料齐套检查API', 'GET 外部资源审查服务(前端直连)', '手动触发(工具栏)'],
+    ['3', '生成物料需求API', 'GET 外部MRP服务(前端直连)', '手动触发(工具栏)'],
+    ['4', '工单下达操作API', 'POST /api/WorkOrder/dispatch/release', '手动触发(逐行下达)'],
+    ['5', '工单状态同步MES', '工单下达后状态同步至MES系统', '实时(下达触发)'],
+  ],
+  reports: [
+    ['1', '工单下达及时率', '统计计划下达时间和工单下达时间差异', '按周/按月'],
+  ],
+};
+const ch9 = () => buildChapter('9', '工单下达', _ch9cfg);
+
+// ── Chapter 10: 产销协同看板与KPI ──
+const _ch10cfg = {
+  targets: [
+    '为管理层和业务人员提供S1模块的核心KPI可视化看板。',
+    '通过L1/L2/L3/L4多层级指标全面监控产销协同健康度。',
+    '支持按客户维度筛选,灵活下钻分析。',
+  ],
+  flowNote: '流程路径:产销协同看板入口 → L1核心指标卡片 → 趋势图表 → 客户筛选 → 指标下钻 → L2/L3/L4分解指标。',
+  flowSteps: [
+    { label: '进入S1产销协同看板', role: '管理层/计划员' },
+    { label: '查看L1核心KPI卡片', role: '管理层/计划员' },
+    { label: '客户维度筛选过滤', role: '管理层/计划员' },
+    { label: '指标下钻L2→L3→L4', role: '管理层/计划员' },
+    { label: '趋势图表分析', role: '管理层/计划员' },
+  ],
+  activities: [
+    ['10', '查看看板', '管理层/计划员', '进入S1产销协同看板,观察L1核心指标卡片', '—', 'L1指标展示'],
+    ['20', '客户筛选', '管理层/计划员', '选择客户维度,看板数据按客户过滤刷新', '客户选择', '筛选后数据'],
+    ['30', '指标下钻', '管理层/计划员', '点击指标卡片下钻查看L2分解指标详情', 'L1指标', 'L2指标详情'],
+    ['40', '趋势分析', '管理层/计划员', '查看指标周期趋势图表,分析变化规律', '历史数据', '趋势图表'],
+  ],
+  rules: [
+    ['1', '组件架构', '使用DynamicModuleDashboard动态看板组件,module-code="S1"'],
+    ['2', 'L1核心指标', '订单及时交付率(S1_L1_001)、订单交付周期(S1_L1_002)、订单评审完成率(S1_L1_003)、成品库存周转天数(S1_L1_004)'],
+    ['3', 'L2分解指标', '合同评审3项(S1_L2_001~003)、产品设计3项(S1_L2_004~006)、交期评审3项(S1_L2_010~012)、发货联动3项(S1_L2_013~015)'],
+    ['4', 'L3/L4节点指标', '合同评审各审批节点的周期和通过率(S1_L3_001~005/101~105、S1_L4_001~004/101~104)'],
+    ['5', '指标存储', '指标定义写入ado_smart_ops_kpi_master;计算结果写入ado_s9_kpi_value_l1~l4_day'],
+    ['6', '刷新方式', '通过MDP作业S1_MDP_SYNC_TRANSFORM定时或手动刷新KPI数据'],
+    ['7', 'S1_L1_004口径说明', '当前为数量代理口径(基于dwd_ship_trans),正式成本口径待S7成品库存快照和成本数据确认后升级'],
+  ],
+  improvements: [
+    '实现S1模块KPI在线可视化和实时监控。',
+    '多层级指标下钻,支持从L1到L4逐层分析。',
+    'KPI数据自动计算,替代人工Excel统计。',
+  ],
+  permissions: [
+    ['1', '管理层', '查看S1看板全部指标'],
+    ['2', '计划工程师', '查看S1看板;按客户维度筛选分析'],
+    ['3', '销售/商务', '查看S1看板(本部门相关指标)'],
+  ],
+  interfaces: [
+    ['1', '看板聚合API', 'GET /api/AidopKanban/module-detail?moduleCode=S1', '实时(页面加载)'],
+    ['2', 'KPI数据源', 'ado_s9_kpi_value_l1~l4_day 按天粒度存储', 'MDP定时刷新'],
+    ['3', '手动刷新(计划联动)', 'POST /api/Order/linkageplan/refresh 触发S1全链路刷新', '手动触发'],
+  ],
+  reports: [],
+};
+const ch10 = () => buildChapter('10', '产销协同看板与KPI', _ch10cfg);
+
+// ── Chapter 11: 数据中台架构 ──
+function buildCh11() {
+  const ch = [];
+  ch.push(h1('11  数据中台架构'));
+  
+  ch.push(h2('11.1  目标/宗旨'));
+  ch.push(p('S1产销协同模块采用Ai-DOP统一数据中台分层架构,将业务运行数据经过贴源(STG)、标准化(STD)、宽表构建(DWD)、指标计算(KPI)四层处理,最终服务于前端页面查询、看板展示和KPI考核。'));
+  
+  ch.push(h2('11.2  分层设计'));
+  ch.push(table(
+    ['分层', '表前缀', '职责', '更新机制'],
+    [
+      ['贴源层(STG)', 'mdp_stg_*', '保留源系统原始数据,含raw_data/source_table/sync_batch_id', 'MDP同步覆盖写入'],
+      ['标准层(STD)', 'mdp_std_*', '统一字段口径,清洗脏数据,形成标准业务对象', 'MDP转换(取最新批次)'],
+      ['宽表层(DWD)', 'dwd_*', '面向页面和KPI的主题事实表,关联多源数据', 'MDP转换(取最新批次)'],
+      ['指标层(KPI)', 'ado_s9_kpi_value_*', '按天存储L1-L4指标值', 'MDP KPI计算(取最新批次)'],
+    ],
+    [1500, 2000, 4200, 2300]
+  ));
+
+  ch.push(h2('11.3  S1核心数据流'));
+  ch.push(bp('订单/评审/设计链路'));
+  ch.push(p('crm_seorder / ado_contract_review / ado_product_design / b_examine_result'));
+  ch.push(p('  → mdp_stg_so(贴源层)→ mdp_std_so(标准层)→ dwd_ship_trans / dwd_requirement_examine_detail(宽表)→ ado_s9_kpi_value_*(指标层)'));
+  ch.push(bp('发货/ASN/联动链路'));
+  ch.push(p('ShippingPlan / ASNBOLShipper / LinkagePlan'));
+  ch.push(p('  → mdp_stg_ship_trans(贴源层)→ mdp_std_ship_trans(标准层)→ dwd_ship_trans(宽表)→ ado_s9_kpi_value_*(指标层)'));
+
+  ch.push(h2('11.4  对象映射总表'));
+  ch.push(table(
+    ['业务对象', '运行表', '贴源层', '标准层/DWD', '消费端'],
+    [
+      ['销售订单', 'crm_seorder', 'mdp_stg_so', 'mdp_std_so / dwd_ship_trans', '订单列表、交付、KPI'],
+      ['订单变更', 'crm_seorder_change', 'mdp_stg_so', '关联mdp_std_so', 'Tab2变更记录'],
+      ['合同评审', 'ado_contract_review', 'mdp_stg_so', 'KPI直接消费', 'S1_L2_001~003'],
+      ['产品设计', 'ado_product_design', 'mdp_stg_so', 'KPI直接消费', 'S1_L2_004~006'],
+      ['发货计划', 'ShippingPlan', 'mdp_stg_ship_trans', 'mdp_std_ship_trans / dwd_ship_trans', 'S1_L2_013'],
+      ['ASN发货', 'ASNBOLShipper', 'mdp_stg_ship_trans', 'mdp_std_ship_trans / dwd_ship_trans', 'S1_L2_014'],
+      ['计划联动', 'LinkagePlan', 'mdp_stg_ship_trans', 'mdp_std_ship_trans / dwd_ship_trans', 'S1_L2_015'],
+      ['需求核验', 'b_examine_result', 'mdp_stg_so', 'dwd_requirement_examine_detail', '核验页/Tab2/Tab4'],
+    ],
+    [1100, 1800, 1800, 2600, 2700]
+  ));
+
+  ch.push(h2('11.5  MDP作业调度'));
+  ch.push(table(
+    ['配置项', '内容'],
+    [
+      ['作业编码', 'S1_MDP_SYNC_TRANSFORM'],
+      ['批次格式', 'S1_MDP_FULL_yyyyMMddHHmmss'],
+      ['手动刷新入口', 'POST /api/Order/linkageplan/refresh'],
+      ['监控入口', 'mdp_transform_run_log(作业日志)、mdp_sync_log(同步日志)、前端MDP监控页'],
+      ['执行方式', '手动触发 + 未来扩展定时调度'],
+    ],
+    [2200, 7800]
+  ));
+
+  ch.push(h2('11.6  变化和改进点'));
+  ch.push(bullet('建立统一数据中台分层架构,业务数据从"各自为政"收敛到标准数据链路。'));
+  ch.push(bullet('页面读取统一从DWD/KPI层消费,确保数据口径一致。'));
+  ch.push(bullet('MDP作业可观测,支持日志查询和异常追溯。'));
+
+  return ch;
+}
+
+// ── Chapter 12: 权限管理汇总 ──
+function buildCh12() {
+  const ch = [];
+  ch.push(h1('12  权限管理汇总'));
+  ch.push(p('以下汇总S1产销协同模块各功能所涉及的角色和权限:'));
+  ch.push(table(
+    ['序号', '角色', '权限范围', '涉及模块'],
+    [
+      ['1', '销售/客服专员', '查看销售订单列表、订单交付列表;新建/编辑/删除订单;发起订单变更申请', '订单评审、订单交付'],
+      ['2', '计划工程师', '查看和操作订单评审、工单下达、计划联动、需求核验;执行交期确认;查看KPI看板', '所有模块(操作权限)'],
+      ['3', '商务人员', '新建/编辑合同评审;提交审批;查看订单交付', '合同评审'],
+      ['4', '设计员', '新建/编辑/删除产品设计任务;录入BOM和工艺子表;保存操作', '产品设计'],
+      ['5', '物流/仓管', '新建/编辑/删除发货单和ASN发货单;管理发货明细行;查看订单交付', '订单发货'],
+      ['6', '管理层', '查看S1看板全部指标', '看板与KPI'],
+      ['7', '审批人', '审核订单变更申请/合同评审的审批流', '订单评审(变更)、合同评审'],
+      ['8', '系统管理员', '管理用户角色权限;监控MDP作业状态;数据修复操作', '系统配置'],
+    ],
+    [600, 1800, 5000, 2600]
+  ));
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 文档构建主流程
+// ═══════════════════════════════════════════════════
+async function buildDocument() {
+  // ── 预生成所有流程图 PNG ──
+  console.log('Generating flow chart images...');
+  flowImageCache['overview'] = await generateOverviewFlowPNG();
+
+  // 收集所有章节的 flowSteps 定义并预生成流程图
+  const allChapterConfigs = {
+    '2': _ch2cfg, '3': _ch3cfg, '4': _ch4cfg, '5': _ch5cfg,
+    '6': _ch6cfg, '7': _ch7cfg, '8': _ch8cfg, '9': _ch9cfg, '10': _ch10cfg,
+  };
+  for (const [num, cfg] of Object.entries(allChapterConfigs)) {
+    if (cfg.flowSteps) {
+      flowImageCache[num] = await generateFlowPNG(cfg.flowSteps);
+    }
+  }
+  console.log('Flow chart images generated.');
+  const sections = [];
+
+  // ── 封面 ──
+  sections.push(empty(), empty(), empty());
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: 'S1 产销协同模块', bold: true, size: 52, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 100 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '蓝图设计方案', bold: true, size: 52, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 600 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: 'Ai-DOP 智慧运营决策平台', size: 28, font: FONT, color: '4472C4' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 120 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '版本:V2.0    日期:2026年6月8日    作者:彭熙玉', size: 22, font: FONT, color: '808080' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 2400 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '【机密文档 · 仅限项目内部使用】', size: 21, font: FONT, color: 'C00000', italics: true })],
+    alignment: AlignmentType.CENTER,
+  }));
+
+  // ── 文档控制 ──
+  sections.push(pageBreak(), h1('文档控制'));
+  sections.push(h2('更改记录'));
+  sections.push(table(['日期', '姓名', '版本', '变更说明'],
+    [['2026/06/08', '彭熙玉', 'V1.0', '初版(基于DOP整体方案设计)'],
+     ['2026/06/08', '彭熙玉', 'V2.0', '根据系统实际S1模块实现重写,聚焦产销协同范围,按模板格式补充8子章节']],
+    [1800, 1200, 1000, 6000]));
+  sections.push(empty());
+  sections.push(h2('审核'));
+  sections.push(table(['姓名', '职位', '签字/日期'], [['', '', ''], ['', '', '']], [3000, 3000, 4000]));
+  sections.push(empty());
+  sections.push(h2('发布'));
+  sections.push(table(['编号', '名称', '地点'], [['', '', '']], [3000, 4000, 3000]));
+
+  // ── 目录 ──
+  sections.push(pageBreak(), h1('目录'));
+  sections.push(...buildTOC());
+  sections.push(pageBreak());
+
+  // ── 第1章: 总体业务方案 ──
+  sections.push(h1('1  总体业务方案'));
+  sections.push(h2('1.1  目标和宗旨'));
+  sections.push(bullet('S1产销协同模块是Ai-DOP平台订单履约链路的起点,承担"从订单到交付"的产销协同管理职能。'));
+  sections.push(bullet('指导销售订单从接收→评审→排产→采购→生产→发运的全流程在线化协同。'));
+  sections.push(bullet('通过KPI指标体系监控产销协同健康度,驱动业务流程持续改善。'));
+  sections.push(h2('1.2  总体业务流程图'));
+  sections.push(p('S1产销协同模块覆盖从"产品设计"到"成品发运"的订单履约全链路,下图展示了主线流程(订单履约链)、支撑流程和底层数据中台链路的整体关系:'));
+  // 嵌入总体流程图(在 buildDocument 头部预生成)
+  sections.push(flowImageParagraph(flowImageCache['overview']));
+  sections.push(h2('1.3  方案设计'));
+  sections.push(p('S1模块作为Ai-DOP S0-S9模块化架构中的"产销协同"功能域,覆盖以下核心功能模块:'));
+  sections.push(bullet('产品设计管理:新产品开发中BOM和工艺路线的在线录入与保存,无审批流'));
+  sections.push(bullet('订单评审管理:销售订单的CRUD、交期评估与确认;变更场景走审批流'));
+  sections.push(bullet('订单交付管理:8Tab全景追踪,从订单信息到成品发运的全链路可视化'));
+  sections.push(bullet('订单发货管理:发货计划、ASN发货、计划联动的在线管理'));
+  sections.push(bullet('合同评审管理:客户合同的多级审批与KPI考核'));
+  sections.push(bullet('需求明细核验:BOM需求展开与核验结果的在线查看'));
+  sections.push(bullet('工单下达:生产工单的物料齐套检查、MRP同步与逐单下达'));
+  sections.push(bullet('产销协同看板:L1/L2/L3/L4多层级KPI可视化监控'));
+  sections.push(p('数据层面,S1采用数据中台分层架构,业务运行数据通过MDP同步转换作业沉淀到中台表,前端页面和看板统一消费中台数据,确保数据口径一致。跨模块依赖的Tab数据(排程、采购、备料、报工IQC、FQC入库)界面结构已就绪,待S2/S3/S4/S5/S6/S7模块补齐后自动填充。'));
+
+  // ── 第2~10章: 各功能模块 ──
+  const moduleChapters = [ch2, ch3, ch4, ch5, ch6, ch7, ch8, ch9, ch10];
+  for (const chFn of moduleChapters) {
+    sections.push(pageBreak());
+    sections.push(h1(chFn().find(el => {
+      // extract the chapter title from h2 children
+      if (el.root && el.root[0] && el.root[0].children) {
+        return chFn.name;
+      }
+      return false;
+    })));
+    // Actually, the above approach is wrong. Let me just push the results.
+  }
+
+  // Let me restructure - push each chapter directly
+  sections.push(pageBreak()); sections.push(h1('2  产品设计管理')); sections.push(...ch2());
+  sections.push(pageBreak()); sections.push(h1('3  订单评审管理')); sections.push(...ch3());
+  sections.push(pageBreak()); sections.push(h1('4  订单交付管理')); sections.push(...ch4());
+  sections.push(pageBreak()); sections.push(h1('5  订单发货管理')); sections.push(...ch5());
+  sections.push(pageBreak()); sections.push(h1('6  合同评审管理')); sections.push(...ch6());
+  sections.push(pageBreak()); sections.push(h1('7  计划联动')); sections.push(...ch7());
+  sections.push(pageBreak()); sections.push(h1('8  需求明细核验')); sections.push(...ch8());
+  sections.push(pageBreak()); sections.push(h1('9  工单下达')); sections.push(...ch9());
+  sections.push(pageBreak()); sections.push(h1('10  产销协同看板与KPI')); sections.push(...ch10());
+
+  // ── 第11~12章 ──
+  sections.push(pageBreak()); sections.push(...buildCh11());
+  sections.push(pageBreak()); sections.push(...buildCh12());
+
+  return sections;
+}
+
+// ═══════════════════════════════════════════════════
+// 生成文档
+// ═══════════════════════════════════════════════════
+async function main() {
+  const doc = new Document({
+    styles: { default: { document: { run: { font: FONT, size: 21 } } } },
+    sections: [{
+      properties: { page: { margin: { top: convertInchesToTwip(0.8), bottom: convertInchesToTwip(0.8), left: convertInchesToTwip(1.0), right: convertInchesToTwip(1.0) } } },
+      children: await buildDocument(),
+    }],
+  });
+
+  const buffer = await Packer.toBuffer(doc);
+  const outPath = 'd:\\DEMONET\\doc\\S1产销协同模块蓝图设计方案.docx';
+  const fallbackPath = 'd:\\DEMONET\\doc\\S1产销协同模块蓝图设计方案_V2.docx';
+  const tempPath = 'd:\\DEMONET\\doc\\S1_blueprint_temp.docx';
+  fs.writeFileSync(tempPath, buffer);
+  // 先尝试写入主文件名,若被占用则写入 V2 副本
+  try { fs.unlinkSync(outPath); } catch(e) { /* 被占用则跳过 */ }
+  try { fs.renameSync(tempPath, outPath); console.log(`Renamed to: ${outPath}`); }
+    catch(e) {
+      // 主文件被锁定,写入 V2 副本
+      try { fs.unlinkSync(fallbackPath); } catch(_) {}
+      try { fs.renameSync(tempPath, fallbackPath); console.log(`Fallback to: ${fallbackPath}`); }
+        catch(e2) { console.log(`Generated: ${tempPath} (target locked)`); }
+    }
+  console.log(`Size: ${(buffer.length / 1024).toFixed(1)} KB`);
+}
+main().catch(err => { console.error('Error:', err); process.exit(1); });

+ 362 - 0
doc/generate_s1_req_docx.js

@@ -0,0 +1,362 @@
+/**
+ * Generate S1 产销协同业务需求描述.docx
+ * 严格按照模板 产销协同业务需求描述.docx 的格式:
+ *   标题 → 文档信息 → (一)模块功能说明+业务描述 → ...
+ */
+const fs = require('fs');
+const {
+  Document, Packer, Paragraph, TextRun, HeadingLevel,
+  AlignmentType, PageBreak, TableOfContents, convertInchesToTwip,
+  TabStopType, TabStopPosition, BorderStyle, WidthType, ShadingType, Table, TableRow, TableCell
+} = require('docx');
+
+const FONT = '微软雅黑';
+
+// ── 辅助函数 ──
+function empty() { return new Paragraph({ spacing: { after: 80 }, children: [] }); }
+function pageBreak() { return new Paragraph({ children: [new PageBreak()] }); }
+
+function normalPara(text, opts = {}) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT, ...opts })],
+    spacing: { after: 100, line: 360 },
+    indent: opts.indent ? { left: convertInchesToTwip(opts.indent) } : undefined,
+  });
+}
+
+function boldPara(text) {
+  return normalPara(text, { bold: true });
+}
+
+function bulletPara(text, opts = {}) {
+  return new Paragraph({
+    children: [
+      new TextRun({ text: '• ', size: 21, font: FONT }),
+      new TextRun({ text, size: 21, font: FONT, ...opts }),
+    ],
+    spacing: { after: 60, line: 340 },
+    indent: { left: convertInchesToTwip(0.3) },
+  });
+}
+
+// H2: (一) 产品设计管理
+function sectionHeading(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 400, after: 200 },
+  });
+}
+
+// H5: 功能说明 / 业务描述
+function subHeading(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 22, font: FONT })],
+    heading: HeadingLevel.HEADING_3,
+    spacing: { before: 200, after: 100 },
+  });
+}
+
+// Image placeholder paragraph
+function imagePlaceholder(label) {
+  return new Paragraph({
+    children: [new TextRun({ text: `【${label}截图】`, size: 18, font: FONT, italics: true, color: '999999' })],
+    spacing: { before: 80, after: 80 },
+    alignment: AlignmentType.CENTER,
+  });
+}
+
+// Title paragraph
+function titlePara(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 36, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER,
+    spacing: { after: 300 },
+  });
+}
+
+function docInfoPara(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT })],
+    spacing: { after: 40 },
+    bullet: { level: 0 },
+  });
+}
+
+// ═══════════════════════════════════════════════════
+// 各模块业务需求描述数据
+// ═══════════════════════════════════════════════════
+
+const CN_NUM = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一'];
+
+const modules = [
+  {
+    title: '产品设计管理',
+    funcDesc: [
+      '产品设计管理模块用于在线管理新产品开发过程中的产品设计BOM(物料清单)和工艺路线信息。',
+      '设计员可在系统中新建产品设计任务,录入BOM子表(子物料编码、单位用量、损耗率等)和工艺子表(产线选择、路线编码等),保存即生效。',
+      '该模块采用直接保存模式,无审批流环节,简化设计操作流程,提升设计效率。',
+      '产品设计数据通过MDP同步至数据中台,纳入KPI考核体系,监控设计周期、满足率和人效指标。',
+    ],
+    bizDesc: [
+      '设计员登录DOP系统,进入"产品设计管理"菜单,点击"添加"按钮新建产品设计任务。',
+      '在任务主表中填写产品设计基本信息:选择成品物料编码,填写任务名称、计划完成日期、设计要求等必填字段。',
+      '在BOM子表中逐行新增子物料信息:选择子物料编码后系统自动填入物料名称;填写单位用量和固定损耗率;支持多行物料录入。',
+      '在工艺子表中逐行新增工艺路线信息:通过下拉框远程搜索选择产线(显示格式为"Line|Describe"),手动输入路线编码。',
+      '填写完整后点击"保存"按钮,系统自动生成单据编号,数据即时生效,无需经过审批流程。',
+      '已保存的设计记录可在列表中查看、编辑和删除;删除时关联的BOM和工艺子表数据同步删除。',
+    ],
+  },
+  {
+    title: '订单评审管理',
+    funcDesc: [
+      '订单评审管理模块实现销售订单的在线录入、查询、编辑和删除操作,是产销协同的订单入口。',
+      '订单主流程(录入→评审→交期确认)无审批流,直接操作即可完成;仅当交期确认后需要修改时触发变更审批流。',
+      '系统根据成品库存、在制工单和物料齐套等约束条件,对订单进行交期评估,给出预计交付日期。',
+      '订单变更记录可追溯,变更次数自动累计,变更历史在订单交付Tab2(评审信息)中查看。',
+    ],
+    bizDesc: [
+      '销售/计划员登录DOP系统,进入"订单评审管理"菜单,点击"添加"按钮新建销售订单。',
+      '在订单表单中录入客户信息、物料编码、订单数量、需求交期等基本信息,订单编号保存时自动生成。',
+      '订单保存后进入"待评审"状态,计划员对订单执行交期评估:系统根据成品库存、在制工单和物料约束给出建议交期。',
+      '计划员确认交期结果后执行"交期确认"操作,调用外部交期确认接口,确认成功后订单状态更新为"已确认",产能和物料资源同步锁定。',
+      '交期确认后如需修改订单,必须发起变更申请并经过审批流;变更记录自动归档,变更次数累计。',
+      '列表支持按订单号、客户、日期范围等条件筛选,支持对订单进行编辑和删除操作。',
+    ],
+  },
+  {
+    title: '订单交付管理',
+    funcDesc: [
+      '订单交付管理模块以订单行(明细)为粒度,集中展示每笔销售订单的交付状态。',
+      '通过8个Tab页签实现从"订单接收"到"成品发运"的全链路履约追踪:订单信息(Tab1)、评审与变更(Tab2)、排程(Tab3)、物料需求(Tab4)、采购(Tab5)、备料(Tab6)、生产(Tab7)、入库发运(Tab8)。',
+      '交付列表数据来源于数据中台宽表层dwd_ship_trans,确保与看板KPI数据口径一致。',
+      '跨模块Tab(排程/采购/备料/报工IQC/FQC入库)结构已就绪,待S2/S3/S4/S5/S6/S7模块补齐后自动填充数据。',
+    ],
+    bizDesc: [
+      '计划员/物流人员进入"订单交付管理"菜单,查看交付列表,支持按订单号、客户、交付状态、日期范围等条件筛选。',
+      '点击列表中的订单行,进入交付详情页面,通过8个Tab页签查看该订单的全链路履约状态。',
+      'Tab1展示订单基础档案信息(订单编号、客户、物料、状态、审核人等),数据来源mdp_std_so标准层。',
+      'Tab2展示交期评估结果和订单变更历史记录,数据来源dwd_requirement_examine_detail及mdp_stg_so变更数据。',
+      'Tab4展示BOM需求展开、库存占用、缺料分析和替代料信息,数据来源dwd_requirement_examine_detail。',
+      'Tab8展示成品入库和发运记录,数据来源mdp_std_ship_trans。',
+      '交付状态实时反映订单从"待评审→已评审→排产中→采购中→生产中→已入库→已发运"的完整流转过程。',
+    ],
+  },
+  {
+    title: '订单发货管理',
+    funcDesc: [
+      '订单发货管理模块实现发货单和ASN(预先发货通知)的在线录入、查询、编辑和删除管理。',
+      '通过销售单号关联将发货单与销售订单联动,发货计划覆盖率和发货达成率自动计算并纳入KPI考核。',
+      '发货单号保存时自动生成(格式为SH+日期+流水号),客户和销售单号支持远程搜索选择。',
+      '发货计划和ASN发货数据通过MDP同步至数据中台dwd_ship_trans宽表,与订单交付数据联动。',
+    ],
+    bizDesc: [
+      '物流/仓管人员进入"订单发货管理"菜单,查看发货单列表,支持按发货单号、销售单号、客户、日期范围筛选。',
+      '点击"新建发货单"按钮,选择关联的销售单号(下拉搜索,回显格式为"BillNo|客户名"),选择客户,填写发货基本信息。',
+      '在发货明细子表中逐行新增物料信息:选择物料编码后自动填入物料名称,填写发货数量和批次信息;至少保留一行。',
+      '保存后发货单号自动生成,数据进入运行时表ShippingPlan,通过MDP同步至数据中台。',
+      'ASN发货记录单独管理:录入ASN发货单号、物料、发货数量和发货日期,数据同步至mdp_std_ship_trans。',
+      '已发货的发货单不可删除,防止数据丢失;发货计划覆盖率和发货达成率自动纳入KPI统计。',
+    ],
+  },
+  {
+    title: '合同评审管理',
+    funcDesc: [
+      '合同评审管理模块实现客户合同的在线评审管理,与销售订单评审协同运作。',
+      '通过审批流(审批流类型:CONTRACT_REVIEW)管控合同评审的多级审核流程,支持同意和驳回操作。',
+      '合同评审数据纳入KPI考核体系,自动计算合同评审周期(S1_L2_001)、满足率(S1_L2_002)、人效(S1_L2_003)及各审批节点耗时(L3/L4)。',
+    ],
+    bizDesc: [
+      '商务人员进入"合同评审管理"菜单,查看合同评审列表,支持多条件筛选查询。',
+      '点击"新建合同评审"按钮,录入合同基本信息:合同编号、客户名称、签订日期、合同金额等。',
+      '填写完整后点击"提交审批",合同进入审批流(CONTRACT_REVIEW类型),由配置的审批人进行多级审核。',
+      '审批人在审批列表中查看待审合同,可查看合同详情后执行"同意"或"驳回"操作。',
+      '审批通过后合同状态更新,KPI数据自动计算:评审周期(从提交到通过的总耗时)、满足率(通过占比)、各节点耗时。',
+      '合同评审数据通过MDP作业同步至数据中台贴源层mdp_stg_so,KPI计算直接消费贴源层数据。',
+    ],
+  },
+  {
+    title: '计划联动',
+    funcDesc: [
+      '计划联动模块实现销售订单、发货计划、ASN发货等业务数据的联动整合。',
+      '通过MDP作业(S1_MDP_SYNC_TRANSFORM)将多源数据汇聚到dwd_ship_trans宽表,确保各页面数据口径一致。',
+      '提供手动刷新机制,业务人员可随时触发全量同步转换,立即获取最新联动数据。',
+      '联动数据覆盖订单(crm_seorder)、发货计划(ShippingPlan)、ASN(ASNBOLShipper)和计划联动(LinkagePlan)四类业务对象。',
+    ],
+    bizDesc: [
+      '计划员进入"计划联动"菜单,查看各业务对象的联动状态列表(联动成功/未联动/数据异常三种状态)。',
+      '点击"刷新"按钮,触发S1_MDP_SYNC_TRANSFORM全量同步转换作业,批次格式为S1_MDP_FULL_yyyyMMddHHmmss。',
+      'MDP作业依次执行:STG贴源层同步→STD标准化转换→DWD宽表构建→KPI指标计算,全链路自动完成。',
+      '刷新完成后返回最新联动结果:联动达成率、数据一致性状态及各环节处理行数和耗时。',
+      '系统管理员可通过MDP监控页面查看作业日志(mdp_transform_run_log)和同步日志(mdp_sync_log),定位异常。',
+      '计划联动达成率(S1_L2_015)自动计算,反映联动成功的业务对象占比。',
+    ],
+  },
+  {
+    title: '需求明细核验',
+    funcDesc: [
+      '需求明细核验模块展示销售订单的BOM需求展开与核验结果,帮助计划人员确认物料需求数量和到货时间的准确性。',
+      '核验数据来源于运行时表b_examine_result,通过MDP标准化后进入dwd_requirement_examine_detail,只取最新成功批次数据。',
+      '核验内容包括:物料编码、BOM编码、需求数量、缺料数量、替代料方案、物料满足时间等。',
+      '核验结果同时服务于订单交付Tab2(评审信息)和Tab4(物料需求),实现跨页面数据共享。',
+    ],
+    bizDesc: [
+      '计划员进入"需求明细核验"菜单,通过订单号、物料编码、核验状态等条件筛选查询核验列表。',
+      '在核验列表中查看每条订单行的BOM需求展开结果:对应BOM编码下的子物料需求明细。',
+      '重点关注缺料数量和物料满足时间:系统自动计算当前库存与需求的差额,给出预计满足日期。',
+      '如存在替代料方案,可在核验明细中查看建议替代物料信息。',
+      '核验数据自动同步至订单交付详情页的Tab2和Tab4,无需重复查询。',
+    ],
+  },
+  {
+    title: '工单下达',
+    funcDesc: [
+      '工单下达模块管理生产工单的下达操作,是产销协同到制造执行(MES)的衔接环节。',
+      '工单逐单下达(非批量),每行操作列独立"工单下达"按钮,弹出下达表单填写开工日期和生产批号后确认。',
+      '下达前必须执行物料齐套检查(调用外部资源审查服务)和物料需求同步(生成MRP物料需求计划),确保物料准备到位。',
+      '工单下达后MES系统可见,进入S6生产执行阶段;工单状态支持全程跟踪(待下达→已下达→生产中→已完成→已关闭)。',
+    ],
+    bizDesc: [
+      '计划员/调度人员进入"工单下达"菜单,通过工单号、物料编码、开工日期、状态等条件筛选查询工单池列表。',
+      '点击工具栏"物料齐套检查"按钮,调用外部资源审查服务,检查各工单的物料齐套状态,齐套结果更新到列表的"物料齐套"列。',
+      '点击工具栏"生成物料需求"按钮,同步生成MRP物料需求计划,确保工单所需物料已纳入供应计划。',
+      '物料齐套检查和MRP生成通过后,在列表中点击目标工单行的"工单下达"按钮,弹出下达表单。',
+      '在下达表单中填写开工日期和生产批号(系统自动获取下一批次编号),点击确认后工单状态变更为"已下达"。',
+      '已下达工单不可重复下达(前端按钮置灰+后端校验双重保障);工单下达后自动同步至MES系统。',
+    ],
+  },
+  {
+    title: '产销协同看板与KPI',
+    funcDesc: [
+      '产销协同看板为管理层和业务人员提供S1模块的核心KPI可视化监控面板。',
+      '采用DynamicModuleDashboard动态看板组件(module-code="S1"),通过L1/L2/L3/L4多层级指标体系全面监控产销协同健康度。',
+      'L1核心指标:订单及时交付率、订单交付周期、订单评审完成率、成品库存周转天数。',
+      'L2分解指标:覆盖合同评审(3项)、产品设计(3项)、交期评审(3项)、发货联动(3项)共12项。',
+      'L3/L4节点指标:合同评审各审批节点的周期和通过率,支持逐层下钻分析。',
+      '支持按客户维度筛选过滤,灵活下钻分析,趋势图表展示周期变化规律。',
+    ],
+    bizDesc: [
+      '管理层/计划员进入"S1产销协同看板"菜单,系统自动加载L1核心KPI指标卡片(4项),直观展示当前产销协同健康度。',
+      '通过客户下拉框选择特定客户维度,看板所有指标数据自动按该客户过滤刷新。',
+      '点击L1指标卡片可下钻至L2分解指标详情,进一步点击L2指标可查看L3节点指标分布。',
+      'L4层级为审批节点明细指标(如合同评审各节点通过率和耗时),支持流程优化分析。',
+      '趋势图表展示各指标的历史变化规律,支持按日/周/月周期切换查看。',
+      'KPI数据通过MDP作业S1_MDP_SYNC_TRANSFORM定时或手动刷新,指标定义存储在ado_smart_ops_kpi_master,计算结果存储在ado_s9_kpi_value_l1~l4_day。',
+    ],
+  },
+  {
+    title: '数据中台架构',
+    funcDesc: [
+      'S1产销协同模块采用Ai-DOP统一数据中台分层架构(STG→STD→DWD→KPI),确保前端页面、看板展示和KPI考核的数据口径一致。',
+      '贴源层(STG/mdp_stg_*)保留源系统原始数据;标准层(STD/mdp_std_*)统一字段口径并清洗脏数据;宽表层(DWD/dwd_*)构建面向页面和KPI的主题事实表;指标层(KPI/ado_s9_kpi_value_*)按天存储L1-L4指标值。',
+      'MDP作业(S1_MDP_SYNC_TRANSFORM)负责全链路同步转换,支持手动触发和未来扩展定时调度,作业日志可观测、异常可追溯。',
+    ],
+    bizDesc: [
+      'S1模块的业务数据(销售订单、合同评审、产品设计、发货计划、ASN发货、计划联动、需求核验等)通过MDP作业自动同步至数据中台各层。',
+      '订单/评审/设计链路:crm_seorder等运行表→mdp_stg_so贴源→mdp_std_so标准→dwd_ship_trans/dwd_requirement_examine_detail宽表→KPI计算。',
+      '发货/ASN/联动链路:ShippingPlan等运行表→mdp_stg_ship_trans贴源→mdp_std_ship_trans标准→dwd_ship_trans宽表→KPI计算。',
+      '计划员可通过计划联动页面的"刷新"按钮手动触发全量同步;系统管理员通过MDP监控页面查看作业执行状态和处理日志。',
+      '页面查询统一消费DWD/KPI层数据,避免直接访问运行时表,确保数据口径一致和查询性能优化。',
+    ],
+  },
+  {
+    title: '权限管理汇总',
+    funcDesc: [
+      'S1产销协同模块涉及以下核心角色和权限:',
+      '销售/客服专员:查看销售订单列表和订单交付列表;新建/编辑/删除订单;发起订单变更申请。',
+      '计划工程师:查看和操作订单评审、工单下达、计划联动、需求核验;执行交期确认;查看KPI看板。',
+      '商务人员:新建/编辑合同评审;提交审批;查看订单交付。',
+      '设计员:新建/编辑/删除产品设计任务;录入BOM和工艺子表;保存操作。',
+      '物流/仓管:新建/编辑/删除发货单和ASN发货单;管理发货明细行;查看订单交付。',
+      '审批人:审核订单变更申请和合同评审的审批流。',
+      '管理层:查看S1看板全部指标。',
+      '系统管理员:管理用户角色权限;监控MDP作业状态;数据修复操作。',
+    ],
+  },
+];
+
+// ═══════════════════════════════════════════════════
+// 生成文档
+// ═══════════════════════════════════════════════════
+function buildDocument() {
+  const sections = [];
+
+  // ── 封面/标题 ──
+  sections.push(empty(), empty(), empty(), empty());
+  sections.push(titlePara('产销协同业务需求描述'));
+  sections.push(empty());
+
+  // ── 文档信息 ──
+  const infoItems = [
+    '文档作者:彭熙玉',
+    '创建日期:2026-06-08',
+    '更新日期:2026-06-08',
+    '文档编号:DOP-S1-REQ-001',
+    '当前版本:V1.0',
+  ];
+  infoItems.forEach(item => sections.push(docInfoPara(item)));
+  sections.push(empty(), empty());
+
+  // ── 目录 ──
+  sections.push(pageBreak());
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '目录', bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 200, after: 300 },
+  }));
+  sections.push(new TableOfContents('目录', {
+    headingStyleRange: '2-3',
+    hyperlink: true,
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '(如目录未显示,请在 Word 中右键此处 → 更新域)', size: 18, font: FONT, italics: true, color: '888888' })],
+    spacing: { before: 40, after: 200 },
+  }));
+
+  // ── 各功能模块 ──
+  for (let i = 0; i < modules.length; i++) {
+    const mod = modules[i];
+    sections.push(pageBreak());
+
+    // 章节标题:(一) 产品设计管理
+    sections.push(sectionHeading(`(${CN_NUM[i + 1]})${mod.title}`));
+
+    // 功能说明
+    sections.push(subHeading('功能说明'));
+    (mod.funcDesc || []).forEach(desc => sections.push(normalPara(desc)));
+
+    // 业务描述
+    if (mod.bizDesc && mod.bizDesc.length) {
+      sections.push(subHeading('业务描述'));
+      mod.bizDesc.forEach(desc => sections.push(normalPara(desc)));
+    }
+  }
+
+  return sections;
+}
+
+// ═══════════════════════════════════════════════════
+// 主流程
+// ═══════════════════════════════════════════════════
+async function main() {
+  const sections = buildDocument();
+
+  const doc = new Document({
+    styles: { default: { document: { run: { font: FONT, size: 21 } } } },
+    sections: [{
+      properties: { page: { margin: { top: convertInchesToTwip(0.8), bottom: convertInchesToTwip(0.8), left: convertInchesToTwip(1.2), right: convertInchesToTwip(1.2) } } },
+      children: sections,
+    }],
+  });
+
+  const buffer = await Packer.toBuffer(doc);
+  const outPath = 'd:\\DEMONET\\doc\\S1产销协同业务需求描述.docx';
+  const tempPath = 'd:\\DEMONET\\doc\\req_temp.docx';
+  fs.writeFileSync(tempPath, buffer);
+  try { fs.unlinkSync(outPath); } catch(e) {}
+  try { fs.renameSync(tempPath, outPath); console.log(`Renamed to: ${outPath}`); }
+    catch(e) { console.log(`Generated: ${tempPath}`); }
+  console.log(`Size: ${(buffer.length / 1024).toFixed(1)} KB`);
+}
+
+main().catch(err => { console.error('Error:', err); process.exit(1); });

+ 795 - 0
doc/generate_s2_blueprint_docx.js

@@ -0,0 +1,795 @@
+/**
+ * Generate S2 制造协同模块蓝图设计方案.docx
+ * 严格按照 S1 蓝图模板格式:每个功能章节含 8 个子章节
+ *   [目标/宗旨, 业务流程图, 业务流程说明, 业务流程规则, 变化和改进点, 权限管理需求, 系统接口集成, 报表需求]
+ */
+const fs = require('fs');
+const sharp = require('sharp');
+const {
+  Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
+  HeadingLevel, AlignmentType, BorderStyle, WidthType, ShadingType,
+  convertInchesToTwip, PageBreak, ImageRun, TabStopType, TabStopPosition,
+  InternalHyperlink, TableOfContents
+} = require('docx');
+
+// ═══════════════════════════════════════════════════
+// 通用样式与辅助函数
+// ═══════════════════════════════════════════════════
+const FONT = '微软雅黑';
+const thinBorder = {
+  top: { style: BorderStyle.SINGLE, size: 1 },
+  bottom: { style: BorderStyle.SINGLE, size: 1 },
+  left: { style: BorderStyle.SINGLE, size: 1 },
+  right: { style: BorderStyle.SINGLE, size: 1 },
+};
+const headerShading = { fill: '4472C4', type: ShadingType.CLEAR };
+
+function p(text, opts = {}) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT, ...opts })],
+    spacing: { after: 120, line: 360 },
+  });
+}
+function bp(text) { return p(text, { bold: true }); }
+function empty() { return new Paragraph({ spacing: { after: 80 }, children: [] }); }
+function pageBreak() { return new Paragraph({ children: [new PageBreak()] }); }
+
+function h1(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 32, font: FONT })],
+    heading: HeadingLevel.HEADING_1,
+    spacing: { before: 400, after: 200 },
+  });
+}
+function h2(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 300, after: 150 },
+  });
+}
+function h3(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 24, font: FONT })],
+    heading: HeadingLevel.HEADING_3,
+    spacing: { before: 200, after: 100 },
+  });
+}
+function bullet(text, indent = 0) {
+  return new Paragraph({
+    children: [new TextRun({ text: `  ${text}`, size: 21, font: FONT })],
+    spacing: { after: 60, line: 340 },
+    indent: { left: 300 + indent * 400 },
+  });
+}
+
+// Table helpers
+function hdrCell(text, w = 1800) {
+  return new TableCell({
+    children: [new Paragraph({ children: [new TextRun({ text, bold: true, size: 20, font: FONT, color: 'FFFFFF' })], alignment: AlignmentType.CENTER })],
+    width: { size: w, type: WidthType.DXA }, borders: thinBorder, shading: headerShading,
+  });
+}
+function tc(text, w = 1800, opts = {}) {
+  return new TableCell({
+    children: [new Paragraph({ children: [new TextRun({ text: String(text ?? ''), size: 20, font: FONT })], alignment: opts.center ? AlignmentType.CENTER : AlignmentType.LEFT })],
+    width: { size: w, type: WidthType.DXA }, borders: thinBorder,
+  });
+}
+function table(headers, rows, widths) {
+  return new Table({
+    rows: [
+      new TableRow({ children: headers.map((h, i) => hdrCell(h, widths ? widths[i] : 1800)), tableHeader: true }),
+      ...rows.map(r => new TableRow({ children: r.map((c, i) => tc(c, widths ? widths[i] : 1800)) })),
+    ],
+    width: { size: 100, type: WidthType.PERCENTAGE },
+  });
+}
+
+// 活动流程表(6列)
+function activityTable(rows) {
+  const w = [600, 1400, 1100, 3800, 1400, 1700];
+  return table(['编号', '活动名称', '执行角色', '活动描述', '输入', '输出'], rows, w);
+}
+// 业务规则表(3列)
+function ruleTable(rows) {
+  return table(['序列号', '业务情形', '描述/方案'], rows, [900, 2800, 6300]);
+}
+// 接口表(4列)
+function interfaceTable(rows) {
+  return table(['序号', '接口名称', '接口说明', '频次及触发方式'], rows, [600, 2400, 4200, 2800]);
+}
+// 报表表(4列)
+function reportTable(rows) {
+  return table(['序号', '名称', '描述', '方案'], rows, [600, 2600, 3600, 3200]);
+}
+// 权限表(3列)
+function permTable(rows) {
+  return table(['序号', '岗位名称', '对应系统权限'], rows, [600, 2000, 7400]);
+}
+
+// ── 目录条目(模板格式) ──
+const TOC2_TAB = 2880;
+const TOC3_TAB = 3600;
+const TOC_FONT_SIZE = 20;
+
+function tocChapter(num, title) {
+  return new Paragraph({
+    children: [
+      new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: title, bold: true, size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
+    ],
+    tabStops: [
+      { type: TabStopType.LEFT, position: TOC2_TAB },
+      { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
+    ],
+    spacing: { before: 80, after: 40 },
+  });
+}
+
+function tocSub(num, title) {
+  return new Paragraph({
+    children: [
+      new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: title, size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
+    ],
+    tabStops: [
+      { type: TabStopType.LEFT, position: TOC3_TAB },
+      { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
+    ],
+    spacing: { before: 30, after: 30 },
+  });
+}
+
+function buildTOC() {
+  const ch = [];
+  ch.push(new TableOfContents('目录', {
+    headingStyleRange: '1-2',
+    hyperlink: true,
+  }));
+  ch.push(new Paragraph({
+    children: [new TextRun({ text: '(如目录未显示,请在 Word 中右键此处 → 更新域)', size: 18, font: FONT, italics: true, color: '888888' })],
+    spacing: { before: 40, after: 200 },
+  }));
+
+  // 文档控制
+  ch.push(tocChapter('', '文档控制'));
+  ch.push(tocSub('', '更改记录'));
+  ch.push(tocSub('', '审核'));
+  ch.push(tocSub('', '发布'));
+  ch.push(tocChapter('', '目录'));
+
+  // 第1章
+  ch.push(tocChapter('1', '总体业务方案'));
+  ch.push(tocSub('1.1', '目标和宗旨'));
+  ch.push(tocSub('1.2', '总体业务流程图'));
+  ch.push(tocSub('1.3', '方案设计'));
+
+  // 第2~4章(8子章节)
+  const chSubs = [
+    '目标/宗旨', '业务流程图', '业务流程说明', '业务流程规则',
+    '变化和改进点', '权限管理需求', '系统接口集成', '报表需求',
+  ];
+  const chTitles = ['生产排程', '作业计划', '制造协同看板与KPI'];
+  for (let i = 0; i < chTitles.length; i++) {
+    const n = i + 2;
+    ch.push(tocChapter(String(n), chTitles[i]));
+    for (let j = 0; j < chSubs.length; j++) {
+      ch.push(tocSub(`${n}.${j + 1}`, chSubs[j]));
+    }
+  }
+
+  // 第5章 数据中台
+  ch.push(tocChapter('5', '数据中台架构'));
+  ['目标/宗旨', '分层设计', 'S2核心数据流', '对象映射总表', 'MDP作业调度'].forEach((title, i) => {
+    ch.push(tocSub(`5.${i + 1}`, title));
+  });
+
+  // 第6章 权限管理汇总
+  ch.push(tocChapter('6', '权限管理汇总'));
+
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 流程图生成
+// ═══════════════════════════════════════════════════
+const FLOW_W = 680, BOX_W = 220, BOX_H = 42, GAP_Y = 28;
+const ROLE_X = 30, BOX_X = 130, MARGIN_Y = 40;
+const flowImageCache = {};
+
+function flowImageParagraph(pngBuffer) {
+  return new Paragraph({
+    children: [new ImageRun({ data: pngBuffer, transformation: { width: 540, height: 360 }, type: 'png' })],
+    alignment: AlignmentType.CENTER,
+    spacing: { before: 120, after: 120 },
+  });
+}
+
+async function generateFlowPNG(steps) {
+  const N = steps.length;
+  const svgH = MARGIN_Y * 2 + N * (BOX_H + GAP_Y) - GAP_Y;
+  const centerX = BOX_X + BOX_W / 2;
+
+  let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${FLOW_W}" height="${svgH}">`;
+  svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
+
+  steps.forEach((s, i) => {
+    const y = MARGIN_Y + i * (BOX_H + GAP_Y);
+    svg += `<text x="${ROLE_X}" y="${y + BOX_H / 2 + 5}" font-family="Microsoft YaHei" font-size="12" fill="#666" text-anchor="start">${s.role || ''}</text>`;
+    const isStart = i === 0, isEnd = i === N - 1;
+    const fill = isStart ? '#1565C0' : (isEnd ? '#2E7D32' : '#E3F2FD');
+    const stroke = isStart ? '#0D47A1' : (isEnd ? '#1B5E20' : '#1565C0');
+    const textColor = (isStart || isEnd) ? '#FFFFFF' : '#333333';
+    svg += `<rect x="${BOX_X}" y="${y}" width="${BOX_W}" height="${BOX_H}" rx="6" fill="${fill}" stroke="${stroke}" stroke-width="1.5"/>`;
+    svg += `<text x="${centerX}" y="${y + BOX_H / 2 + 5}" font-family="Microsoft YaHei" font-size="13" fill="${textColor}" text-anchor="middle">${s.label}</text>`;
+
+    if (i < N - 1) {
+      const ay = y + BOX_H + 4, by2 = ay + GAP_Y - 8;
+      svg += `<line x1="${centerX}" y1="${ay}" x2="${centerX}" y2="${by2}" stroke="#888" stroke-width="1.5"/>`;
+      svg += `<polygon points="${centerX - 5},${by2 - 10} ${centerX + 5},${by2 - 10} ${centerX},${by2}" fill="#888"/>`;
+    }
+  });
+
+  svg += '</svg>';
+  return sharp(Buffer.from(svg)).png().toBuffer();
+}
+
+// 第1章专用:S2总体复合流程图
+async function generateOverviewFlowPNG() {
+  const W = 780, H = 580;
+  let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}">`;
+  svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
+
+  // 标题
+  svg += `<text x="${W/2}" y="28" font-family="Microsoft YaHei" font-size="15" font-weight="bold" fill="#1F4E79" text-anchor="middle">S2 制造协同 — 总体业务流程</text>`;
+
+  // 区域背景
+  const zones = [
+    { y: 42, h: 195, color: '#E8F0FE', label: '主线流程(排程到执行)', lx: 10, ly: 60 },
+    { y: 245, h: 125, color: '#FFF3E0', label: '基础数据管理(支撑排程)', lx: 10, ly: 263 },
+    { y: 378, h: 90, color: '#E8F5E9', label: '数据中台链路', lx: 10, ly: 396 },
+  ];
+  zones.forEach(z => {
+    svg += `<rect x="8" y="${z.y}" width="${W-16}" height="${z.h}" rx="6" fill="${z.color}" stroke="#ccc" stroke-width="0.5" stroke-dasharray="4,3"/>`;
+    svg += `<text x="${z.lx}" y="${z.ly}" font-family="Microsoft YaHei" font-size="12" font-weight="bold" fill="#555">${z.label}</text>`;
+  });
+
+  // 主线流程节点
+  const mainNodes = [
+    { x: 30, y: 85, w: 130, h: 38, label: 'S1工单下达\n(工单传入)', color: '#6C757D' },
+    { x: 190, y: 85, w: 130, h: 38, label: '生产排程\n(工序+优先级)', color: '#1565C0' },
+    { x: 350, y: 85, w: 130, h: 38, label: '物料需求同步\n(MRP确认)', color: '#1565C0' },
+    { x: 510, y: 85, w: 130, h: 38, label: '可执行日计划\n(下达产线)', color: '#1565C0' },
+    { x: 660, y: 85, w: 100, h: 38, label: 'S6 MES\n生产执行', color: '#6C757D' },
+    { x: 510, y: 150, w: 130, h: 38, label: '进度看板\n(完工/领料)', color: '#2E7D32' },
+  ];
+  mainNodes.forEach(n => {
+    const fill = n.color === '#6C757D' ? '#E0E0E0' : n.color;
+    const stroke = n.color === '#6C757D' ? '#999' : n.color;
+    const tc = n.color === '#6C757D' ? '#555' : '#fff';
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + n.h / 2;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="${tc}" text-anchor="middle">${l}</text>`;
+    });
+  });
+
+  // 箭头
+  const arrows = [
+    { x1: 160, y1: 104, x2: 190, y2: 104 }, { x1: 320, y1: 104, x2: 350, y2: 104 },
+    { x1: 480, y1: 104, x2: 510, y2: 104 }, { x1: 640, y1: 104, x2: 660, y2: 104 },
+    { x1: 575, y1: 123, x2: 575, y2: 150 },
+  ];
+  arrows.forEach(a => {
+    svg += `<line x1="${a.x1}" y1="${a.y1}" x2="${a.x2}" y2="${a.y2}" stroke="#666" stroke-width="1.2"/>`;
+    const ang = Math.atan2(a.y2 - a.y1, a.x2 - a.x1);
+    const lx = a.x2 - 6 * Math.cos(ang), ly = a.y2 - 6 * Math.sin(ang);
+    svg += `<polygon points="${lx + 4 * Math.sin(ang)},${ly - 4 * Math.cos(ang)} ${lx - 4 * Math.sin(ang)},${ly + 4 * Math.cos(ang)} ${lx + 6 * Math.cos(ang)},${ly + 6 * Math.sin(ang)}" fill="#666"/>`;
+  });
+  svg += `<text x="425" y="110" font-family="Microsoft YaHei" font-size="9" fill="#999" text-anchor="middle">(灰色=S1/S6跨模块衔接)</text>`;
+
+  // 支撑流程节点
+  const suppNodes = [
+    { x: 40, y: 280, w: 145, h: 36, label: '产线工作日历\n(班次定义)', color: '#E65100' },
+    { x: 215, y: 280, w: 145, h: 36, label: '休息时间管理\n(产线停休)', color: '#E65100' },
+    { x: 390, y: 280, w: 145, h: 36, label: '节假日管理\n(休假/调班)', color: '#E65100' },
+    { x: 565, y: 280, w: 145, h: 36, label: '加班管理\n(延长工时)', color: '#E65100' },
+  ];
+  suppNodes.forEach(n => {
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${n.color}" stroke="${n.color}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + n.h / 2;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="#fff" text-anchor="middle">${l}</text>`;
+    });
+  });
+
+  // 数据流节点
+  const dataNodes = [
+    { x: 40, y: 418, w: 130, h: 36, label: 'WorkOrdMaster', color: '#2E7D32' },
+    { x: 200, y: 418, w: 120, h: 36, label: 'mdp_stg_*', color: '#6A1B9A' },
+    { x: 350, y: 418, w: 120, h: 36, label: 'mdp_std_*', color: '#6A1B9A' },
+    { x: 500, y: 418, w: 120, h: 36, label: 'dwd_* (宽表)', color: '#0D47A1' },
+    { x: 650, y: 418, w: 100, h: 36, label: 'KPI 指标层', color: '#B71C1C' },
+  ];
+  dataNodes.forEach(n => {
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${n.color}" stroke="${n.color}" stroke-width="1.2"/>`;
+    svg += `<text x="${n.x + n.w/2}" y="${n.y + n.h/2 + 4}" font-family="Microsoft YaHei" font-size="10" fill="#fff" text-anchor="middle">${n.label}</text>`;
+  });
+  for (let i = 0; i < dataNodes.length - 1; i++) {
+    const a = dataNodes[i], b = dataNodes[i + 1];
+    const ax = a.x + a.w, ay = a.y + a.h / 2, bx = b.x;
+    svg += `<line x1="${ax}" y1="${ay}" x2="${bx}" y2="${ay}" stroke="#666" stroke-width="1.2"/>`;
+    svg += `<polygon points="${bx - 6},${ay - 4} ${bx - 6},${ay + 4} ${bx},${ay}" fill="#666"/>`;
+  }
+
+  svg += `<text x="265" y="240" font-family="Microsoft YaHei" font-size="10" fill="#888" text-anchor="middle">↑ 为排程和日计划提供可用产能约束 ↑</text>`;
+
+  svg += '</svg>';
+  return sharp(Buffer.from(svg)).png().toBuffer();
+}
+
+// ═══════════════════════════════════════════════════
+// 章节构建函数
+// ═══════════════════════════════════════════════════
+function buildChapter(num, title, cfg) {
+  const ch = [];
+  const pre = `${num}.`;
+
+  // §X.1 目标/宗旨
+  ch.push(h2(`${pre}1  目标/宗旨`));
+  (cfg.targets || []).forEach(t => ch.push(bullet(t)));
+
+  // §X.2 业务流程图
+  ch.push(h2(`${pre}2  业务流程图`));
+  const img = cfg.flowImage || (cfg.flowSteps ? flowImageCache[num] : null);
+  if (img) {
+    ch.push(flowImageParagraph(img));
+  } else {
+    ch.push(p('(见系统操作流程图 / 附件 Visio 流程图)'));
+  }
+  if (cfg.flowNote) ch.push(p(cfg.flowNote));
+
+  // §X.3 业务流程说明
+  ch.push(h2(`${pre}3  业务流程说明`));
+  if (cfg.activities && cfg.activities.length) {
+    ch.push(activityTable(cfg.activities));
+  } else {
+    ch.push(p('参照系统页面操作流程,主要操作步骤包含:'));
+    (cfg.activityDesc || []).forEach(a => ch.push(bullet(a)));
+  }
+
+  // §X.4 业务流程规则
+  ch.push(h2(`${pre}4  业务流程规则`));
+  if (cfg.rules && cfg.rules.length) {
+    ch.push(ruleTable(cfg.rules));
+  } else {
+    ch.push(p('无特殊业务规则'));
+  }
+
+  // §X.5 变化和改进点
+  ch.push(h2(`${pre}5  变化和改进点`));
+  (cfg.improvements || []).forEach(i => ch.push(bullet(i)));
+
+  // §X.6 权限管理需求
+  ch.push(h2(`${pre}6  权限管理需求`));
+  if (cfg.permissions && cfg.permissions.length) {
+    ch.push(permTable(cfg.permissions));
+  } else {
+    ch.push(p('参照第 6 章 权限管理汇总'));
+  }
+
+  // §X.7 系统接口集成
+  ch.push(h2(`${pre}7  系统接口集成`));
+  if (cfg.interfaces && cfg.interfaces.length) {
+    ch.push(interfaceTable(cfg.interfaces));
+  } else {
+    ch.push(p('无外部系统接口;页面数据通过 Admin.NET 内置 API 获取'));
+  }
+
+  // §X.8 报表需求
+  ch.push(h2(`${pre}8  报表需求`));
+  if (cfg.reports && cfg.reports.length) {
+    ch.push(reportTable(cfg.reports));
+  } else {
+    ch.push(p('无独立报表需求,相关数据通过看板和列表页面查看'));
+  }
+
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 章节数据配置
+// ═══════════════════════════════════════════════════
+
+// ── Chapter 2: 生产排程 ──
+const _ch2cfg = {
+  targets: [
+    '实现生产工单的在线排程管理,将S1下达的工单按工序展开并确定执行优先级。',
+    '通过"生产排程"按钮统一触发工单排程计算,结合产线日历和资源产能约束生成可执行的工序任务。',
+    '支持工单优先级调整(加急/特急),快速响应生产异常和插单需求。',
+    '提供排产异常记录管理和工单关闭功能,形成完整的排程闭环。',
+    '工单工序排程中支持物料需求同步(MRP联动)、工艺路线同步和工序明细查看。',
+  ],
+  flowNote: '流程路径:工单工序排程列表 → 筛选查询工单 → 生产排程(工具栏按钮) → 同步物料需求 → 调整优先级 → 排产数据维护异常记录 → 工单关闭。',
+  flowSteps: [
+    { label: '工单工序排程列表查询', role: '计划员/调度' },
+    { label: '生产排程(工具栏触发)', role: '计划员/调度' },
+    { label: '同步物料需求(MRP)', role: '计划员/调度' },
+    { label: '优先级调整(加急/特急)', role: '计划员/调度' },
+    { label: '工单关闭 / 异常处理', role: '计划员/调度' },
+  ],
+  activities: [
+    ['10', '工单排程列表查询', '计划员/调度', '通过工单编号、生产批号、物料编码、开工日期、状态等条件筛选排程工单', '筛选条件', '排程工单列表'],
+    ['20', '生产排程', '计划员/调度', '点击工具栏"生产排程"按钮,系统根据排程算法(考虑产线日历、工序、产能)生成排产结果', '工单列表+产线日历', '排产结果'],
+    ['30', '同步物料需求', '计划员/调度', '点击"同步物料需求"按钮,同步生成物料需求计划(MRP),确保排产物料齐套', '工单列表', 'MRP需求'],
+    ['40', '工单优先级别管理', '计划员/调度', '通过操作列"加急"或"特急"按钮调整工单优先级;弹出优先级调整表单,可选择原因说明', '生产异常/插单需求', '优先级更新'],
+    ['50', '同步工艺路线', '计划员/调度', '点击"同步工艺路线"按钮,从产品设计中同步最新工艺路线到排程工单', '产品设计工艺', '工单工序明细'],
+    ['60', '工单执行追踪', '计划员/调度', '点击"工单执行追踪"查看工单全流程执行进度', '工单编号', '执行进度'],
+    ['70', '排产异常记录', '计划员/调度', '在排产异常记录页面新增/编辑/删除排产数据维护异常记录,记录工单编号、物料编码、操作类型和执行时间', '排产数据维护', '异常记录'],
+    ['80', '工单关闭', '计划员/调度', '勾选多行工单后点击"工单关闭"按钮(批量操作),将工单状态置为已关闭', '选中的工单行', '已关闭工单'],
+  ],
+  rules: [
+    ['1', '排程数据来源', '工单排程页面数据来自WorkOrdMaster表,通过/api/WorkOrder/scheduling/list读取'],
+    ['2', '工单状态流转', '待下达(S1)→已排产→生产中→已完成→已关闭;状态通过行内下拉框直接修改'],
+    ['3', '优先级枚举', '优先级枚举值:1=普通,2=加急,3=特急;通过PriorityDialogForm选择调整'],
+    ['4', '工单关闭', '支持批量勾选后一次性关闭,前端按钮在未选行时置灰;关闭操作不可逆(后端校验工单状态、数量约束)'],
+    ['5', '排程算法', '生产排程按钮触发后端排程服务,综合考虑产线工作日历、工序顺序、资源产能约束'],
+    ['6', '排产异常管理', '异常记录(ScheduleExceptionMaster表)支持独立CRUD,与排程工单通过工单编号关联;记录操作类型和执行时间'],
+    ['7', '工单编码唯一性', '工单编号全局唯一,由上游S1系统生成后传入'],
+    ['8', '防重复排程', '已排程且生产中的工单不可重复排程;排程前检查工单状态'],
+  ],
+  improvements: [
+    '实现在线生产排程管理,替代线下Excel排程方式。',
+    '工单优先级在线调整(加急/特急),支持快速响应插单和异常。',
+    '排产异常记录可追溯,形成完整的排产数据维护日志。',
+    '工单关闭归口统一,与S1下达形成闭环联动。',
+    '工艺路线同步机制确保排程与设计数据一致。',
+  ],
+  permissions: [
+    ['1', '计划员/调度', '查看工单排程列表;执行生产排程;同步物料需求;调整优先级;工单关闭;管理排产异常记录'],
+    ['2', '车间主管', '查看工单排程列表和排产异常记录'],
+  ],
+  interfaces: [
+    ['1', '工单排程列表API', 'GET /api/WorkOrder/scheduling/list 读取WorkOrdMaster表', '实时(页面加载)'],
+    ['2', '生产排程API', 'POST /api/WorkOrder/scheduling/production-schedule 触发后端排程计算', '手动触发(工具栏)'],
+    ['3', '同步物料需求API', 'POST /api/WorkOrder/scheduling/sync-material 触发MRP需求计算', '手动触发(工具栏)'],
+    ['4', '工单关闭API', 'POST /api/WorkOrder/scheduling/batch-close 批量关闭工单', '手动触发(工具栏)'],
+    ['5', '排产异常CRUD API', 'GET/POST/PUT/DELETE /api/ScheduleException/* 排产异常记录管理', '实时(CRUD)'],
+    ['6', '同步工艺路线API', 'POST /api/WorkOrder/scheduling/sync-routing 从产品设计同步工艺到工单', '手动触发(行操作)'],
+    ['7', '工单状态同步MES', '排程结果和工单状态变更同步至MES系统', '实时'],
+  ],
+  reports: [
+    ['1', '工单排程达成率', '统计生产排程与工单下达的时间差和排程覆盖率', '按周/按月'],
+    ['2', '排产异常统计', '按异常类型、物料、产线统计排产异常次数和分布', '按日/周/月'],
+  ],
+};
+const ch2 = () => buildChapter('2', '生产排程', _ch2cfg);
+
+// ── Chapter 3: 作业计划 ──
+const _ch3cfg = {
+  targets: [
+    '在工单排程完成后,生成面向车间产线的可执行日计划,将排产结果精确到每天、每道工序、每台设备。',
+    '管理产线工作日历(班次定义),为排程和日计划提供可用工时基础数据。',
+    '管理产线休息时间,定义每日固定停休时段,排程时自动避开。',
+    '管理产线节假日(休假/调班),确保排程不占用法定休息日。',
+    '管理产线加班时段(ResourceOccupancyTime),为临时加产提供延长工时支持。',
+    '通过日计划下达操作,将确认的日计划下发至S6 MES执行层。',
+  ],
+  flowNote: '流程路径:可执行日计划列表 → 筛选(计划日期≥今天默认) → 勾选多行 → 点击下达 → 日计划状态更新。同时维护:产线工作日历 → 休息时间 → 节假日 → 加班(基础数据CRUD)。',
+  flowSteps: [
+    { label: '可执行日计划筛选', role: '计划员/调度' },
+    { label: '勾选待下达日计划行', role: '计划员/调度' },
+    { label: '批量下达(工具栏)', role: '计划员/调度' },
+    { label: '产线日历+休息+节假日维护', role: '基础数据管理员' },
+    { label: '加班时段录入', role: '计划员/调度' },
+  ],
+  activities: [
+    ['10', '日计划列表查询', '计划员/调度', '通过生产指令、物料编码、生产批次、工单状态、日计划下达状态、工作中心、设备类型、工序、计划日期等条件筛选', '筛选条件', '日计划列表'],
+    ['20', '日计划下达', '计划员/调度', '勾选多行可执行日计划后点击"下达"按钮(批量操作),将日计划下发至MES执行层', '选中的日计划行', '已下达日计划'],
+    ['30', '产线工作日历管理', '基础数据管理员', '新增/编辑/删除产线工作日历记录,定义每条产线每周每天的班次开始时间和时长(班次1+班次2)', '产线信息', '工作日历记录'],
+    ['40', '产线休息时间管理', '基础数据管理员', '新增/编辑/删除产线休息时间记录,定义每个产线的固定休息开始时间点和休息时长(分钟)', '产线信息', '休息时间记录'],
+    ['50', '产线节假日管理', '基础数据管理员', '新增/编辑/删除节假日记录,类型可选"休假"或"调班";排程时自动避开休假日期', '节假日定义', '节假日记录'],
+    ['60', '产线加班管理', '计划员/调度', '新增/编辑/删除产线加班时段记录,定义加班起止时间、有效工作时长和休息时长', '加班需求', '加班记录'],
+  ],
+  rules: [
+    ['1', '日计划默认筛选', '默认计划日期≥今天、工单未关闭;可通过"计划日期≥"条件调至更早日期查看历史数据'],
+    ['2', '日计划下达为批量操作', '支持勾选多行后一次性下达;未选行时下达按钮置灰'],
+    ['3', '工作中心下拉筛选', '支持从数据源远程搜索和筛选工作中心'],
+    ['4', '产线工作日历字段', '星期(WeekDayName)、生产线(ProdLine)、描述、班次1开始/时长(小时)、班次2开始/时长(小时)'],
+    ['5', '产线休息时间字段', '生产线(关联LineMaster)、产线描述、休息开始时间点、休息时长(分钟)、备注'],
+    ['6', '节假日类型', '类型包含"休假"和"调班"两种,排程时自动过滤休假日期'],
+    ['7', '加班记录字段', '产线(Resource)、开始/结束时间、有效工作时长(分钟)、休息时长(分钟)、类型、描述'],
+    ['8', '基础数据关联', '产线工作日历和休息时间均关联LineMaster产线主数据;下拉选择时显示「Line|Describe」格式'],
+  ],
+  improvements: [
+    '可执行日计划实现在线可视化,精确到每道工序和设备的日产能分配。',
+    '产线工作日历在线管理,替代线下纸质排班表。',
+    '休息时间、节假日、加班统一纳入系统,排程自动考虑产能约束。',
+    '日计划下达与MES联动,信息在线流转替代人工传递。',
+  ],
+  permissions: [
+    ['1', '计划员/调度', '查看可执行日计划列表;批量下达;查看产线日历、休息、节假日、加班信息'],
+    ['2', '基础数据管理员', '新增/编辑/删除产线工作日历、休息时间、节假日、加班记录'],
+    ['3', '车间主管', '查看可执行日计划列表和产线基础数据'],
+  ],
+  interfaces: [
+    ['1', '可执行日计划列表API', 'GET /api/ScheduleDailyPlan/list 读取日计划数据', '实时(页面加载)'],
+    ['2', '日计划下达API', 'POST /api/ScheduleDailyPlan/release 批量下达日计划', '手动触发(工具栏)'],
+    ['3', '产线工作日历API', 'GET/POST/PUT/DELETE /api/ShopCalendarWorkCtr/*', '实时(CRUD)'],
+    ['4', '产线休息时间API', 'GET/POST/PUT/DELETE /api/QualityLineRestDetail/*', '实时(CRUD)'],
+    ['5', '产线节假日API', 'GET/POST/PUT/DELETE /api/HolidayMaster/*', '实时(CRUD)'],
+    ['6', '产线加班API', 'GET/POST/PUT/DELETE /api/ResourceOccupancyTime/*', '实时(CRUD)'],
+    ['7', '产线下拉查询API', 'GET /api/LineMaster/list 获取产线列表(供工作日历等下拉选项使用)', '实时(页面加载/下拉触发)'],
+  ],
+  reports: [
+    ['1', '日计划执行率', '统计每日计划下达后的实际执行完成比率', '按日/周/月'],
+    ['2', '产线利用率统计', '按产线统计总有效工时与计划工时的对比', '按周/按月'],
+  ],
+};
+const ch3 = () => buildChapter('3', '作业计划', _ch3cfg);
+
+// ── Chapter 4: 制造协同看板与KPI ──
+const _ch4cfg = {
+  targets: [
+    '为管理层和业务人员提供S2模块的核心KPI可视化看板。',
+    '通过工单执行进度看板展示每张工单的全流程关键进度,包括工单数量、入库数量、未入库数量、领料状态和进度百分比。',
+    '通过指标看板(DynamicModuleDashboard, module-code="S2")展示L1层级核心指标。',
+    '将S2模块的排程满足率、排程周期、在制库存周转天数和排程人效纳入KPI考核体系。',
+  ],
+  flowNote: '流程路径:工单执行进度看板 → 筛选查询 → 查看工单进度条和领料状态 → 定位于制造协同指标看板 → L1核心指标卡片 → 趋势分析。',
+  flowSteps: [
+    { label: '进入工单执行进度看板', role: '管理层/计划员' },
+    { label: '筛选查询工单', role: '计划员' },
+    { label: '查看进度条和领料状态', role: '计划员' },
+    { label: '进入KPI指标看板', role: '管理层' },
+    { label: '分析L1核心指标', role: '管理层' },
+  ],
+  activities: [
+    ['10', '查看工单进度看板', '管理层/计划员', '进入工单执行进度看板页面,按工单编号、物料编码、计划开工日期筛选', '筛选条件', '工单进度列表'],
+    ['20', '分析工单进度', '计划员', '查看进度百分条(完成数量/工单数量);关注领料状态和未入库数量', '工单进度数据', '进度分析'],
+    ['30', '查看看板KPI', '管理层', '进入S2制造协同指标看板,查看L1核心KPI卡片', '—', 'L1指标展示'],
+    ['40', '指标下钻分析', '管理层', '从L1指标下钻到L2分解指标,分析趋势变化', 'L1指标', '趋势图表'],
+  ],
+  rules: [
+    ['1', '进度看板数据来源', '工单进度数据来自WorkOrdMaster,关联工艺默认产线和MES领料状态'],
+    ['2', '进度计算规则', '进度百分比 = 入库数量(QtyCompleted) / 工单数量(QtyOrded) × 100%'],
+    ['3', '单据类型', '单据类型枚举:s=销售工单、p=计划工单;通过typed列区分'],
+    ['4', '单据状态', '状态枚举:w=投产、r=下达、c=关闭;看板中为只读展示'],
+    ['5', 'L1核心指标(S2)', '订单排程满足率(S2-L1-A)、订单排程周期(S2-L1-B)、在制库存周转天数(S2-L1-C)、订单排程人效(S2-L1-D)'],
+    ['6', '看板组件架构', '使用DynamicModuleDashboard动态看板组件,module-code="S2"'],
+    ['7', '指标存储', '指标定义写入ado_smart_ops_kpi_master;计算结果写入ado_s9_kpi_value_* 按天粒度存储'],
+    ['8', '领料状态', '领料状态(materialSituation)关联MES/WMS领料数据,反映工单物料准备的实时状况'],
+  ],
+  improvements: [
+    '工单进度看板实现在线可视化,替代人工跟单和纸质报表方式。',
+    '领料状态和进度条直观展示,管理层一目了然。',
+    'KPI自动化计算替代人工统计,排程满足率和周期指标实时更新。',
+    '看板与数据中台对接,确保数据口径一致。',
+  ],
+  permissions: [
+    ['1', '管理层', '查看S2看板全部指标和工单进度看板'],
+    ['2', '计划员/调度', '查看工单进度看板;按条件筛选分析'],
+  ],
+  interfaces: [
+    ['1', '工单进度看板API', 'GET /api/WorkOrderProgressDashboard/list', '实时(页面加载)'],
+    ['2', 'S2看板聚合API', 'GET /api/AidopKanban/module-detail?moduleCode=S2', '实时(页面加载)'],
+    ['3', 'KPI数据源', 'ado_s9_kpi_value_l1~l4_day 按天粒度存储', 'MDP定时刷新'],
+  ],
+  reports: [],
+};
+const ch4 = () => buildChapter('4', '制造协同看板与KPI', _ch4cfg);
+
+// ── Chapter 5: 数据中台架构 ──
+function buildCh5() {
+  const ch = [];
+  ch.push(h1('5  数据中台架构'));
+
+  ch.push(h2('5.1  目标/宗旨'));
+  ch.push(p('S2制造协同模块采用Ai-DOP统一数据中台分层架构,将工单排程数据、产线基础数据、MES执行数据经过贴源(STG)、标准化(STD)、宽表构建(DWD)、指标计算(KPI)四层处理,最终服务于前端排程列表、日计划、进度看板和KPI考核。'));
+
+  ch.push(h2('5.2  分层设计'));
+  ch.push(table(
+    ['分层', '表前缀', '职责', '更新机制'],
+    [
+      ['贴源层(STG)', 'mdp_stg_*', '保留源系统原始数据,含raw_data/source_table/sync_batch_id', 'MDP同步覆盖写入'],
+      ['标准层(STD)', 'mdp_std_*', '统一字段口径,清洗脏数据,形成标准业务对象', 'MDP转换(取最新批次)'],
+      ['宽表层(DWD)', 'dwd_*', '面向页面和KPI的主题事实表,关联多源数据', 'MDP转换(取最新批次)'],
+      ['指标层(KPI)', 'ado_s9_kpi_value_*', '按天存储L1-L4指标值', 'MDP KPI计算(取最新批次)'],
+    ],
+    [1500, 2000, 4200, 2300]
+  ));
+
+  ch.push(h2('5.3  S2核心数据流'));
+  ch.push(bp('工单排程/日计划链路'));
+  ch.push(p('WorkOrdMaster / ScheduleDailyPlan / ScheduleExceptionMaster'));
+  ch.push(p('  → mdp_stg_production(贴源层)→ mdp_std_production(标准层)→ dwd_production_schedule(宽表)→ ado_s9_kpi_value_*(指标层)'));
+  ch.push(bp('产线基础数据链路'));
+  ch.push(p('ShopCalendarWorkCtr / QualityLineRestDetail / HolidayMaster / ResourceOccupancyTime'));
+  ch.push(p('  → mdp_stg_production(贴源层)→ mdp_std_production(标准层)→ dwd_line_calendar(宽表)→ 排程产能约束计算'));
+  ch.push(bp('MES执行反馈链路'));
+  ch.push(p('MES领料状态 / 完工入库数据 → mdp_stg_production → dwd_production_schedule(宽表)→ 工单进度看板'));
+
+  ch.push(h2('5.4  对象映射总表'));
+  ch.push(table(
+    ['业务对象', '运行表', '贴源层', '标准层/DWD', '消费端'],
+    [
+      ['工单排程', 'WorkOrdMaster', 'mdp_stg_production', 'mdp_std_production / dwd_production_schedule', '排程列表、日计划、KPI'],
+      ['可执行日计划', 'ScheduleDailyPlan', 'mdp_stg_production', 'mdp_std_production / dwd_production_schedule', '日计划列表'],
+      ['排产异常', 'ScheduleExceptionMaster', 'mdp_stg_production', 'mdp_std_production', '异常记录列表'],
+      ['产线工作日历', 'ShopCalendarWorkCtr', 'mdp_stg_production', 'mdp_std_production / dwd_line_calendar', '日历管理、排程约束'],
+      ['产线休息时间', 'QualityLineRestDetail', 'mdp_stg_production', 'mdp_std_production / dwd_line_calendar', '休息时间管理'],
+      ['产线节假日', 'HolidayMaster', 'mdp_stg_production', 'mdp_std_production / dwd_line_calendar', '节假日管理'],
+      ['产线加班', 'ResourceOccupancyTime', 'mdp_stg_production', 'mdp_std_production / dwd_line_calendar', '加班管理'],
+      ['工单进度', 'WorkOrdMaster+领料状态', 'mdp_stg_production', 'dwd_production_schedule', '进度看板'],
+    ],
+    [1100, 2000, 1800, 2600, 2500]
+  ));
+
+  ch.push(h2('5.5  MDP作业调度'));
+  ch.push(table(
+    ['配置项', '内容'],
+    [
+      ['作业编码', 'S2_MDP_SYNC_TRANSFORM'],
+      ['批次格式', 'S2_MDP_FULL_yyyyMMddHHmmss'],
+      ['手动刷新入口', 'POST /api/Production/linkageplan/refresh'],
+      ['监控入口', 'mdp_transform_run_log(作业日志)、mdp_sync_log(同步日志)、前端MDP监控页'],
+      ['执行方式', '手动触发 + 未来扩展定时调度'],
+    ],
+    [2200, 7800]
+  ));
+
+  ch.push(h2('5.6  变化和改进点'));
+  ch.push(bullet('建立统一数据中台分层架构,S2排程和日计划数据从运行时表标准化到中台。'));
+  ch.push(bullet('产线基础数据(日历、休息、节假日、加班)纳入中台,确保排程约束数据唯一可信。'));
+  ch.push(bullet('MES执行反馈数据接入中台,驱动工单进度看板实时更新。'));
+  ch.push(bullet('MDP作业可观测,支持日志查询和异常追溯。'));
+
+  return ch;
+}
+
+// ── Chapter 6: 权限管理汇总 ──
+function buildCh6() {
+  const ch = [];
+  ch.push(h1('6  权限管理汇总'));
+  ch.push(p('以下汇总S2制造协同模块各功能所涉及的角色和权限:'));
+  ch.push(table(
+    ['序号', '角色', '权限范围', '涉及模块'],
+    [
+      ['1', '计划员/调度', '查看工单排程列表;执行生产排程;同步物料需求;调整优先级(加急/特急);工单关闭;查看/下达可执行日计划;管理排产异常记录;查看工单进度看板', '生产排程、作业计划、制造协同看板'],
+      ['2', '基础数据管理员', '新增/编辑/删除产线工作日历、休息时间、节假日、加班时段', '作业计划(基础数据)'],
+      ['3', '车间主管', '查看工单排程列表、可执行日计划列表、产线基础数据和进度看板', '生产排程、作业计划、制造协同看板'],
+      ['4', '管理层', '查看S2看板全部KPI指标和工单进度看板', '制造协同看板'],
+      ['5', '系统管理员', '管理用户角色权限;监控MDP作业状态;数据修复操作', '系统配置'],
+    ],
+    [600, 1800, 5000, 2600]
+  ));
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 文档构建主流程
+// ═══════════════════════════════════════════════════
+async function buildDocument() {
+  // 预生成所有流程图 PNG
+  console.log('Generating flow chart images...');
+  flowImageCache['overview'] = await generateOverviewFlowPNG();
+
+  const allChapterConfigs = { '2': _ch2cfg, '3': _ch3cfg, '4': _ch4cfg };
+  for (const [num, cfg] of Object.entries(allChapterConfigs)) {
+    if (cfg.flowSteps) {
+      flowImageCache[num] = await generateFlowPNG(cfg.flowSteps);
+    }
+  }
+  console.log('Flow chart images generated.');
+
+  const sections = [];
+
+  // ── 封面 ──
+  sections.push(empty(), empty(), empty());
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: 'S2 制造协同模块', bold: true, size: 52, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 100 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '蓝图设计方案', bold: true, size: 52, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 600 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: 'Ai-DOP 智慧运营决策平台', size: 28, font: FONT, color: '4472C4' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 120 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '版本:V1.0    日期:2026年6月8日    作者:彭熙玉', size: 22, font: FONT, color: '808080' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 2400 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '【机密文档 · 仅限项目内部使用】', size: 21, font: FONT, color: 'C00000', italics: true })],
+    alignment: AlignmentType.CENTER,
+  }));
+
+  // ── 文档控制 ──
+  sections.push(pageBreak(), h1('文档控制'));
+  sections.push(h2('更改记录'));
+  sections.push(table(['日期', '姓名', '版本', '变更说明'],
+    [['2026/06/08', '彭熙玉', 'V1.0', '初版(基于DOP整体方案设计和S2模块实际实现)']],
+    [1800, 1200, 1000, 6000]));
+  sections.push(empty());
+  sections.push(h2('审核'));
+  sections.push(table(['姓名', '职位', '签字/日期'], [['', '', ''], ['', '', '']], [3000, 3000, 4000]));
+  sections.push(empty());
+  sections.push(h2('发布'));
+  sections.push(table(['编号', '名称', '地点'], [['', '', '']], [3000, 4000, 3000]));
+
+  // ── 目录 ──
+  sections.push(pageBreak(), h1('目录'));
+  sections.push(...buildTOC());
+  sections.push(pageBreak());
+
+  // ── 第1章: 总体业务方案 ──
+  sections.push(h1('1  总体业务方案'));
+  sections.push(h2('1.1  目标和宗旨'));
+  sections.push(bullet('S2制造协同模块是Ai-DOP平台"从排程到执行"的核心衔接层,承接S1下达的工单,完成生产排程、作业计划编制和制造进度监控。'));
+  sections.push(bullet('通过生产排程将工单按工序展开、确定优先级并计算产能约束,生成可执行的工序任务。'));
+  sections.push(bullet('通过作业计划将排程结果精细化到每日每道工序,并管理产线工作日历、休息时间、节假日和加班等基础产能数据。'));
+  sections.push(bullet('通过制造协同看板实时监控工单执行进度,驱动KPI考核和持续改善。'));
+  sections.push(h2('1.2  总体业务流程图'));
+  sections.push(p('S2制造协同模块承接S1下达的工单,完成生产排程和作业计划编制,并将结果下发至S6 MES执行层。下图展示了主线流程(排程到执行)、基础数据管理(支撑排程)和底层数据中台链路的整体关系:'));
+  sections.push(flowImageParagraph(flowImageCache['overview']));
+  sections.push(h2('1.3  方案设计'));
+  sections.push(p('S2模块作为Ai-DOP S0-S9模块化架构中的"制造协同"功能域,覆盖以下核心功能模块:'));
+  sections.push(bullet('生产排程:工单工序排程(优先级管理、物料同步、工艺同步、工单关闭)+ 排产异常记录管理'));
+  sections.push(bullet('作业计划:可执行日计划(批量下达)+ 产线工作日历/休息时间/节假日/加班(基础数据管理)'));
+  sections.push(bullet('制造协同看板:工单执行进度看板(进度条+领料状态)+ KPI指标看板(L1/L2层级)'));
+  sections.push(p('S2模块与S1(工单下达)、S6(生产执行/MES)紧密衔接:从S1接收已下达工单,排程后生成日计划下发给S6,并接收MES领料和入库反馈更新进度看板。基础数据(产线日历等)为排程算法提供可用产能约束,确保排程结果的可行性和准确性。'));
+
+  // ── 第2~4章: 各功能模块 ──
+  sections.push(pageBreak()); sections.push(h1('2  生产排程')); sections.push(...ch2());
+  sections.push(pageBreak()); sections.push(h1('3  作业计划')); sections.push(...ch3());
+  sections.push(pageBreak()); sections.push(h1('4  制造协同看板与KPI')); sections.push(...ch4());
+
+  // ── 第5~6章 ──
+  sections.push(pageBreak()); sections.push(...buildCh5());
+  sections.push(pageBreak()); sections.push(...buildCh6());
+
+  return sections;
+}
+
+// ═══════════════════════════════════════════════════
+// 生成文档
+// ═══════════════════════════════════════════════════
+async function main() {
+  const doc = new Document({
+    styles: { default: { document: { run: { font: FONT, size: 21 } } } },
+    sections: [{
+      properties: { page: { margin: { top: convertInchesToTwip(0.8), bottom: convertInchesToTwip(0.8), left: convertInchesToTwip(1.0), right: convertInchesToTwip(1.0) } } },
+      children: await buildDocument(),
+    }],
+  });
+
+  const buffer = await Packer.toBuffer(doc);
+  const outPath = 'd:\\DEMONET\\doc\\S2制造协同模块蓝图设计方案.docx';
+  const fallbackPath = 'd:\\DEMONET\\doc\\S2制造协同模块蓝图设计方案_V2.docx';
+  const tempPath = 'd:\\DEMONET\\doc\\S2_blueprint_temp.docx';
+  fs.writeFileSync(tempPath, buffer);
+
+  try { fs.unlinkSync(outPath); } catch(e) { /* 被占用则跳过 */ }
+  try { fs.renameSync(tempPath, outPath); console.log(`Renamed to: ${outPath}`); }
+    catch(e) {
+      try { fs.unlinkSync(fallbackPath); } catch(_) {}
+      try { fs.renameSync(tempPath, fallbackPath); console.log(`Fallback to: ${fallbackPath}`); }
+        catch(e2) { console.log(`Generated: ${tempPath} (target locked)`); }
+    }
+  console.log(`Size: ${(buffer.length / 1024).toFixed(1)} KB`);
+}
+main().catch(err => { console.error('Error:', err); process.exit(1); });

+ 253 - 0
doc/generate_s2_req_docx.js

@@ -0,0 +1,253 @@
+/**
+ * Generate S2 制造协同业务需求描述.docx
+ * 严格按照 S1 产销协同业务需求描述.docx 的格式:
+ *   标题 → 文档信息 → 目录 → (一)模块功能说明+业务描述 → ...
+ */
+const fs = require('fs');
+const {
+  Document, Packer, Paragraph, TextRun, HeadingLevel,
+  AlignmentType, PageBreak, TableOfContents, convertInchesToTwip,
+} = require('docx');
+
+const FONT = '微软雅黑';
+
+// ── 辅助函数 ──
+function empty() { return new Paragraph({ spacing: { after: 80 }, children: [] }); }
+function pageBreak() { return new Paragraph({ children: [new PageBreak()] }); }
+
+function normalPara(text, opts = {}) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT, ...opts })],
+    spacing: { after: 100, line: 360 },
+    indent: opts.indent ? { left: convertInchesToTwip(opts.indent) } : undefined,
+  });
+}
+
+// H2: (一) 生产排程
+function sectionHeading(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 400, after: 200 },
+  });
+}
+
+// H3: 功能说明 / 业务描述
+function subHeading(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 22, font: FONT })],
+    heading: HeadingLevel.HEADING_3,
+    spacing: { before: 200, after: 100 },
+  });
+}
+
+// Title paragraph
+function titlePara(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 36, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER,
+    spacing: { after: 300 },
+  });
+}
+
+function docInfoPara(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT })],
+    spacing: { after: 40 },
+    bullet: { level: 0 },
+  });
+}
+
+// ═══════════════════════════════════════════════════
+// 各模块业务需求描述数据
+// ═══════════════════════════════════════════════════
+
+const CN_NUM = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一'];
+
+const modules = [
+  {
+    title: '生产排程',
+    funcDesc: [
+      '生产排程模块承接S1(产销协同)下达的生产工单,实现在线工单工序排程管理,是制造协同的核心调度层。',
+      '工单工序排程列表以WorkOrdMaster为数据基础,展示工单的优先级、物料、数量、齐套状态和执行进度,支持多条件筛选查询。',
+      '通过工具栏"生产排程"按钮统一触发排程计算,系统综合考虑产线工作日历、工序顺序和资源产能约束,自动生成排产结果。',
+      '支持工单优先级在线调整(加急/特急),快速响应插单和生产异常;支持同步物料需求(MRP联动)和同步工艺路线(从产品设计获取)。',
+      '提供工单关闭(批量操作)、工单执行追踪、工单物料明细和工序明细查看等辅助功能,形成完整的排程闭环。',
+      '排产异常记录模块独立管理排产数据维护过程中的异常信息(ScheduleExceptionMaster表),支持新增、编辑、删除操作,异常可追溯。',
+    ],
+    bizDesc: [
+      '计划员/调度人员登录DOP系统,进入"S2制造协同→生产排程→工单工序排程"菜单,查看工单排程列表。',
+      '通过工单编号、生产批号、物料编码、开工日期、生产车间、状态等条件筛选目标工单。',
+      '点击工具栏"生产排程"按钮,触发排程算法计算(基于产线日历、工序产能约束),系统自动生成或更新排产结果。',
+      '点击工具栏"同步物料需求"按钮,生成MRP物料需求计划,确保排产物料齐套。',
+      '如需调整工单优先级,在操作列点击"加急"(提升为优先级2)或"特急"(提升为优先级3),弹出表单选择原因说明后确认。',
+      '点击操作列"同步工艺路线",从S1产品设计中获取最新工艺路线并应用到当前工单的工序明细中。',
+      '工具栏支持批量"工单关闭":勾选多行已完成的工单后点击关闭按钮,工单状态变更为"已关闭",关闭操作不可逆。',
+      '在"生产排程→排产异常记录"页面中,可新增排产数据维护异常记录(填写工单编号、物料编码、操作类型和执行时间),与工单排程数据关联追溯。',
+    ],
+  },
+  {
+    title: '作业计划',
+    funcDesc: [
+      '作业计划模块在工单排程完成后,生成面向车间产线的可执行日计划,将排产结果精确到每天、每道工序、每台设备。',
+      '可执行日计划列表以排程结果为基础,默认展示"计划日期≥今天"且工单未关闭的日计划数据;支持通过生产指令、物料编码、生产批次、工单状态、工作中心、设备类型、工序等多条件筛选。',
+      '日计划采用批量下达模式:勾选多行日计划后点击"下达"按钮,将确认的日计划下发至S6 MES执行层,驱动车间实际生产。',
+      '产线工作日历管理:定义每条产线每周每天的工作班次(班次1和班次2的开始时间与工作时长),为排程和日计划提供可用工时基础数据。',
+      '产线休息时间管理:定义每个产线的固定休息开始时间点和休息时长(分钟),排程时自动避开停休时段。',
+      '产线节假日管理:维护节假日和调班日期(类型可选"休假"或"调班"),排程时自动过滤休假日期,确保不占用法定休息日。',
+      '产线加班管理:定义临时加班时段(ResourceOccupancyTime),记录加班起止时间、有效工作时长和休息时长,为紧急加产提供延长工时支持。',
+    ],
+    bizDesc: [
+      '计划员/调度人员进入"S2制造协同→作业计划→可执行日计划"菜单,系统默认加载当天及未来的日计划数据。',
+      '通过生产指令、物料编码、生产批次、工单状态、日计划下达状态、工作中心、设备类型、工序等多维条件筛选待处理的日计划。',
+      '勾选确认无误的日计划行,点击工具栏"下达"按钮完成批量下达;下达后日计划状态更新,MES系统即可接收并执行。',
+      '基础数据管理员进入"产线工作日历管理"菜单,可新增/编辑/删除各产线的工作日定义:设置星期一至星期日每天的班次开始时间和持续时长(支持两个班次)。',
+      '进入"产线休息时间管理"菜单,新增/编辑/删除各产线的固定休息时段:选择产线(下拉搜索LineMaster产线主数据),设置休息开始时间点和休息时长。',
+      '进入"产线节假日管理"菜单,新增/编辑/删除节假日记录:选择节假日名称、日期和类型(休假/调班);调班日期用于特殊情况下将休息日调整为工作日。',
+      '进入"产线加班管理"菜单,新增/编辑/删除加班记录:选择产线、设定加班起止时间、系统自动计算有效工作时长和休息时长。',
+      '所有基础数据(日历、休息、节假日、加班)修改后自动作为排程算法的产能约束,无需手动同步。',
+    ],
+  },
+  {
+    title: '制造协同看板与KPI',
+    funcDesc: [
+      '制造协同看板为管理层和业务人员提供S2模块的核心KPI可视化监控和工单执行进度追踪。',
+      '工单执行进度看板以列表形式展示每张工单的全流程关键进度,包括工单数量、入库数量、未入库数量、领料状态和进度百分比(进度条)。',
+      '进度条以百分比直观展示(入库数量÷工单数量×100%),领料状态关联MES/WMS领料数据,反映物料准备的实时状况。',
+      '工单单据类型区分"销售工单(s)"和"计划工单(p)",单据状态区分为"投产(w)""下达(r)""关闭(c)"。',
+      '通过DynamicModuleDashboard动态看板组件(module-code="S2")展示L1层级核心KPI指标:订单排程满足率(S2-L1-A)、订单排程周期(S2-L1-B)、在制库存周转天数(S2-L1-C)、订单排程人效(S2-L1-D)。',
+      'KPI指标数据按天粒度存储于ado_s9_kpi_value_*表中,通过MDP作业定时或手动刷新,确保与前端看板数据口径一致。',
+    ],
+    bizDesc: [
+      '管理层/计划员进入"S2制造协同→制造协同看板→工单执行进度看板"菜单,查看所有工单的执行进度概览。',
+      '通过工单编号、物料编码、计划开工日期等条件筛选特定范围的工单。',
+      '在列表中查看每张工单的进度条(绿色),直观了解完工比例;同时关注领料状态字段,判断物料是否已到位。',
+      '对于进度滞后或领料异常的工单,可返回"工单工序排程"页面进行优先级调整或排程重算。',
+      '进入S2指标看板页面(DynamicModuleDashboard),查看L1核心KPI卡片:排程满足率反映排程计划的达成水平,排程周期反映从工单下达到排程完成的平均耗时。',
+      '在制库存周转天数和排程人效指标为管理层提供制造协同效率的量化参考。',
+      'KPI数据支持按日/周/月周期切换查看趋势变化;支持从L1下钻到L2分解指标进行深层分析。',
+    ],
+  },
+  {
+    title: '数据中台架构',
+    funcDesc: [
+      'S2制造协同模块采用Ai-DOP统一数据中台分层架构(STG→STD→DWD→KPI),将工单排程数据、产线基础数据和MES执行反馈数据标准化处理后服务于前端页面和KPI考核。',
+      '贴源层(STG/mdp_stg_production)保留WorkOrdMaster、ScheduleDailyPlan、产线基础数据等运行表的原始数据快照,含sync_batch_id批次标识。',
+      '标准层(STD/mdp_std_production)统一字段口径、清洗脏数据,形成标准化的排程、日计划和产线日历业务对象。',
+      '宽表层(DWD)构建dwd_production_schedule(排程主题宽表)和dwd_line_calendar(产线日历宽表),关联工单、工序、产线、物料等多维度数据。',
+      '指标层(KPI/ado_s9_kpi_value_*)按天存储S2的L1-L4指标值,包括订单排程满足率、排程周期、在制库存周转天数和排程人效。',
+      'MDP作业(S2_MDP_SYNC_TRANSFORM)负责S2数据的全链路同步转换,支持手动触发和未来扩展定时调度,作业执行日志可观测、异常可追溯。',
+    ],
+    bizDesc: [
+      'S2模块的三个核心数据链路通过MDP作业自动同步至数据中台:',
+      '工单排程/日计划链路:WorkOrdMaster、ScheduleDailyPlan等运行表→mdp_stg_production贴源层→mdp_std_production标准层→dwd_production_schedule宽表→KPI指标计算。',
+      '产线基础数据链路:ShopCalendarWorkCtr、QualityLineRestDetail、HolidayMaster、ResourceOccupancyTime等运行表→mdp_stg_production贴源层→dwd_line_calendar宽表→排程产能约束计算。',
+      'MES执行反馈链路:MES领料状态和完工入库数据→mdp_stg_production贴源层→dwd_production_schedule宽表→工单进度看板实时展示。',
+      '系统管理员通过MDP监控页面查看S2_MDP_SYNC_TRANSFORM作业的执行状态、处理行数和耗时,通过mdp_transform_run_log和mdp_sync_log定位异常。',
+      '排程列表、日计划列表和进度看板统一消费DWD层数据,确保各页面数据口径一致。',
+    ],
+  },
+  {
+    title: '权限管理汇总',
+    funcDesc: [
+      'S2制造协同模块涉及以下核心角色和权限:',
+      '计划员/调度:查看工单排程列表;执行生产排程操作;同步物料需求;调整工单优先级(加急/特急);执行工单关闭;查看/下达可执行日计划;查看和管理排产异常记录;查看工单进度看板和KPI指标看板。',
+      '基础数据管理员:新增/编辑/删除产线工作日历(班次定义);新增/编辑/删除产线休息时间;新增/编辑/删除产线节假日(休假/调班);新增/编辑/删除产线加班时段。',
+      '车间主管:查看工单排程列表、可执行日计划列表和产线基础数据;查看工单进度看板。',
+      '管理层:查看S2制造协同看板全部KPI指标(排程满足率、排程周期、在制库存周转天数、排程人效);查看工单执行进度看板。',
+      '系统管理员:管理用户角色权限配置;监控MDP作业(S2_MDP_SYNC_TRANSFORM)执行状态;执行数据修复操作。',
+    ],
+  },
+];
+
+// ═══════════════════════════════════════════════════
+// 生成文档
+// ═══════════════════════════════════════════════════
+function buildDocument() {
+  const sections = [];
+
+  // ── 封面/标题 ──
+  sections.push(empty(), empty(), empty(), empty());
+  sections.push(titlePara('S2制造协同业务需求描述'));
+  sections.push(empty());
+
+  // ── 文档信息 ──
+  const infoItems = [
+    '文档作者:彭熙玉',
+    '创建日期:2026-06-09',
+    '更新日期:2026-06-09',
+    '文档编号:DOP-S2-REQ-001',
+    '当前版本:V1.0',
+  ];
+  infoItems.forEach(item => sections.push(docInfoPara(item)));
+  sections.push(empty(), empty());
+
+  // ── 目录 ──
+  sections.push(pageBreak());
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '目录', bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 200, after: 300 },
+  }));
+  sections.push(new TableOfContents('目录', {
+    headingStyleRange: '2-3',
+    hyperlink: true,
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '(如目录未显示,请在 Word 中右键此处 → 更新域)', size: 18, font: FONT, italics: true, color: '888888' })],
+    spacing: { before: 40, after: 200 },
+  }));
+
+  // ── 各功能模块 ──
+  for (let i = 0; i < modules.length; i++) {
+    const mod = modules[i];
+    sections.push(pageBreak());
+
+    // 章节标题:(一) 生产排程
+    sections.push(sectionHeading(`(${CN_NUM[i + 1]})${mod.title}`));
+
+    // 功能说明
+    sections.push(subHeading('功能说明'));
+    (mod.funcDesc || []).forEach(desc => sections.push(normalPara(desc)));
+
+    // 业务描述
+    if (mod.bizDesc && mod.bizDesc.length) {
+      sections.push(subHeading('业务描述'));
+      mod.bizDesc.forEach(desc => sections.push(normalPara(desc)));
+    }
+  }
+
+  return sections;
+}
+
+// ═══════════════════════════════════════════════════
+// 主流程
+// ═══════════════════════════════════════════════════
+async function main() {
+  const sections = buildDocument();
+
+  const doc = new Document({
+    styles: { default: { document: { run: { font: FONT, size: 21 } } } },
+    sections: [{
+      properties: { page: { margin: { top: convertInchesToTwip(0.8), bottom: convertInchesToTwip(0.8), left: convertInchesToTwip(1.2), right: convertInchesToTwip(1.2) } } },
+      children: sections,
+    }],
+  });
+
+  const buffer = await Packer.toBuffer(doc);
+  const outPath = 'd:\\DEMONET\\doc\\S2制造协同业务需求描述.docx';
+  const fallbackPath = 'd:\\DEMONET\\doc\\S2制造协同业务需求描述_V2.docx';
+  const tempPath = 'd:\\DEMONET\\doc\\S2_req_temp.docx';
+  fs.writeFileSync(tempPath, buffer);
+
+  try { fs.unlinkSync(outPath); } catch(e) {}
+  try { fs.renameSync(tempPath, outPath); console.log(`Renamed to: ${outPath}`); }
+    catch(e) {
+      try { fs.unlinkSync(fallbackPath); } catch(_) {}
+      try { fs.renameSync(tempPath, fallbackPath); console.log(`Fallback to: ${fallbackPath}`); }
+        catch(e2) { console.log(`Generated: ${tempPath} (target locked)`); }
+    }
+  console.log(`Size: ${(buffer.length / 1024).toFixed(1)} KB`);
+}
+
+main().catch(err => { console.error('Error:', err); process.exit(1); });

+ 1242 - 0
doc/generate_s3_blueprint_docx.js

@@ -0,0 +1,1242 @@
+/**
+ * Generate S3 供应协同模块蓝图设计方案.docx
+ * 参照 generate_s1_blueprint_docx.js 模板格式
+ *   章节结构: 封面 → 文档控制 → TOC → Ch1 总体方案 → Ch2~12 功能域(各8子节) → Ch13 数据中台 → Ch14 权限汇总
+ */
+const fs = require('fs');
+const sharp = require('sharp');
+const {
+  Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
+  HeadingLevel, AlignmentType, BorderStyle, WidthType, ShadingType,
+  convertInchesToTwip, PageBreak, ImageRun, TabStopType, TabStopPosition,
+  TableOfContents
+} = require('docx');
+
+// ═══════════════════════════════════════════════════
+// 通用样式与辅助函数 (from S1 template)
+// ═══════════════════════════════════════════════════
+const FONT = '微软雅黑';
+const thinBorder = {
+  top: { style: BorderStyle.SINGLE, size: 1 },
+  bottom: { style: BorderStyle.SINGLE, size: 1 },
+  left: { style: BorderStyle.SINGLE, size: 1 },
+  right: { style: BorderStyle.SINGLE, size: 1 },
+};
+const headerShading = { fill: '4472C4', type: ShadingType.CLEAR };
+
+function p(text, opts = {}) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT, ...opts })],
+    spacing: { after: 120, line: 360 },
+  });
+}
+function bp(text) { return p(text, { bold: true }); }
+function empty() { return new Paragraph({ spacing: { after: 80 }, children: [] }); }
+function pageBreak() { return new Paragraph({ children: [new PageBreak()] }); }
+
+function h1(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 32, font: FONT })],
+    heading: HeadingLevel.HEADING_1,
+    spacing: { before: 400, after: 200 },
+  });
+}
+function h2(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 300, after: 150 },
+  });
+}
+function h3(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 24, font: FONT })],
+    heading: HeadingLevel.HEADING_3,
+    spacing: { before: 200, after: 100 },
+  });
+}
+function bullet(text, indent = 0) {
+  return new Paragraph({
+    children: [new TextRun({ text: `  ${text}`, size: 21, font: FONT })],
+    spacing: { after: 60, line: 340 },
+    indent: { left: 300 + indent * 400 },
+  });
+}
+
+// Table helpers
+function hdrCell(text, w = 1800) {
+  return new TableCell({
+    children: [new Paragraph({ children: [new TextRun({ text, bold: true, size: 20, font: FONT, color: 'FFFFFF' })], alignment: AlignmentType.CENTER })],
+    width: { size: w, type: WidthType.DXA }, borders: thinBorder, shading: headerShading,
+  });
+}
+function tc(text, w = 1800, opts = {}) {
+  return new TableCell({
+    children: [new Paragraph({ children: [new TextRun({ text: String(text ?? ''), size: 20, font: FONT })], alignment: opts.center ? AlignmentType.CENTER : AlignmentType.LEFT })],
+    width: { size: w, type: WidthType.DXA }, borders: thinBorder,
+  });
+}
+function table(headers, rows, widths) {
+  return new Table({
+    rows: [
+      new TableRow({ children: headers.map((h, i) => hdrCell(h, widths ? widths[i] : 1800)), tableHeader: true }),
+      ...rows.map(r => new TableRow({ children: r.map((c, i) => tc(c, widths ? widths[i] : 1800)) })),
+    ],
+    width: { size: 100, type: WidthType.PERCENTAGE },
+  });
+}
+
+// ── 活动流程表(6列) ──
+function activityTable(rows) {
+  const w = [600, 1400, 1100, 3800, 1400, 1700];
+  return table(['编号', '活动名称', '执行角色', '活动描述', '输入', '输出'], rows, w);
+}
+// ── 业务规则表(3列) ──
+function ruleTable(rows) {
+  return table(['序列号', '业务情形', '描述/方案'], rows, [900, 2800, 6300]);
+}
+// ── 接口表(4列) ──
+function interfaceTable(rows) {
+  return table(['序号', '接口名称', '接口说明', '频次及触发方式'], rows, [600, 2400, 4200, 2800]);
+}
+// ── 报表表(4列) ──
+function reportTable(rows) {
+  return table(['序号', '名称', '描述', '方案'], rows, [600, 2600, 3600, 3200]);
+}
+// ── 权限表(3列) ──
+function permTable(rows) {
+  return table(['序号', '岗位名称', '对应系统权限'], rows, [600, 2000, 7400]);
+}
+
+// ═══════════════════════════════════════════════════
+// 目录 (TOC)
+// ═══════════════════════════════════════════════════
+const TOC2_TAB = 2880;
+const TOC3_TAB = 3600;
+const TOC_FONT_SIZE = 20;
+
+function tocChapter(num, title) {
+  return new Paragraph({
+    children: [
+      new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: title, bold: true, size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
+    ],
+    tabStops: [
+      { type: TabStopType.LEFT, position: TOC2_TAB },
+      { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
+    ],
+    spacing: { before: 80, after: 40 },
+  });
+}
+function tocSub(num, title) {
+  return new Paragraph({
+    children: [
+      new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: title, size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
+    ],
+    tabStops: [
+      { type: TabStopType.LEFT, position: TOC3_TAB },
+      { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
+    ],
+    spacing: { before: 30, after: 30 },
+  });
+}
+
+function buildTOC() {
+  const ch = [];
+  ch.push(new TableOfContents('目录', { headingStyleRange: '1-2', hyperlink: true }));
+  ch.push(new Paragraph({
+    children: [new TextRun({ text: '(如目录未显示,请在 Word 中右键此处 → 更新域)', size: 18, font: FONT, italics: true, color: '888888' })],
+    spacing: { before: 40, after: 200 },
+  }));
+
+  ch.push(tocChapter('', '文档控制'));
+  ch.push(tocSub('', '更改记录'));
+  ch.push(tocSub('', '审核'));
+  ch.push(tocSub('', '发布'));
+  ch.push(tocChapter('', '目录'));
+
+  // 第1章
+  ch.push(tocChapter('1', '总体业务方案'));
+  ch.push(tocSub('1.1', '目标和宗旨'));
+  ch.push(tocSub('1.2', '总体业务流程图'));
+  ch.push(tocSub('1.3', '方案设计'));
+
+  // 第2~12章(8子章节)
+  const chSubs = [
+    '目标/宗旨', '业务流程图', '业务流程说明', '业务流程规则',
+    '变化和改进点', '权限管理需求', '系统接口集成', '报表需求',
+  ];
+  const chTitles = [
+    '物料需求计划', '物料交货计划', '交货单异常记录',
+    '要货令', '物料采购申请', '物料采购订单',
+    '委外加工订单', '工序外协订单',
+    '供应协同看板', '工单物料齐套上线看板', 'MDP 运行监控',
+  ];
+  for (let i = 0; i < chTitles.length; i++) {
+    const n = i + 2;
+    ch.push(tocChapter(String(n), chTitles[i]));
+    for (let j = 0; j < chSubs.length; j++) {
+      ch.push(tocSub(`${n}.${j + 1}`, chSubs[j]));
+    }
+  }
+
+  // 第13章
+  ch.push(tocChapter('13', '数据中台架构'));
+  ['目标/宗旨', '分层设计', 'S3核心数据流', '对象映射总表', 'MDP作业调度'].forEach((title, i) => {
+    ch.push(tocSub(`13.${i + 1}`, title));
+  });
+
+  // 第14章
+  ch.push(tocChapter('14', '权限管理汇总'));
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 流程图生成 (S1 style: 纵向左角色+右节点)
+// ═══════════════════════════════════════════════════
+const FLOW_W = 680, BOX_W = 220, BOX_H = 42, GAP_Y = 28;
+const ROLE_X = 30, BOX_X = 130, MARGIN_Y = 40;
+const flowImageCache = {};
+
+function flowImageParagraph(pngBuffer) {
+  return new Paragraph({
+    children: [new ImageRun({ data: pngBuffer, transformation: { width: 540, height: 360 }, type: 'png' })],
+    alignment: AlignmentType.CENTER,
+    spacing: { before: 120, after: 120 },
+  });
+}
+
+async function generateFlowPNG(steps) {
+  const N = steps.length;
+  const svgH = MARGIN_Y * 2 + N * (BOX_H + GAP_Y) - GAP_Y;
+  const centerX = BOX_X + BOX_W / 2;
+
+  let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${FLOW_W}" height="${svgH}">`;
+  svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
+
+  steps.forEach((s, i) => {
+    const y = MARGIN_Y + i * (BOX_H + GAP_Y);
+    svg += `<text x="${ROLE_X}" y="${y + BOX_H / 2 + 5}" font-family="Microsoft YaHei" font-size="12" fill="#666" text-anchor="start">${s.role || ''}</text>`;
+    const isStart = i === 0, isEnd = i === N - 1;
+    const fill = isStart ? '#1565C0' : (isEnd ? '#2E7D32' : '#E3F2FD');
+    const stroke = isStart ? '#0D47A1' : (isEnd ? '#1B5E20' : '#1565C0');
+    const textColor = (isStart || isEnd) ? '#FFFFFF' : '#333333';
+    svg += `<rect x="${BOX_X}" y="${y}" width="${BOX_W}" height="${BOX_H}" rx="6" fill="${fill}" stroke="${stroke}" stroke-width="1.5"/>`;
+    svg += `<text x="${centerX}" y="${y + BOX_H / 2 + 5}" font-family="Microsoft YaHei" font-size="13" fill="${textColor}" text-anchor="middle">${s.label}</text>`;
+    if (i < N - 1) {
+      const ay = y + BOX_H + 4, by2 = ay + GAP_Y - 8;
+      svg += `<line x1="${centerX}" y1="${ay}" x2="${centerX}" y2="${by2}" stroke="#888" stroke-width="1.5"/>`;
+      svg += `<polygon points="${centerX - 5},${by2 - 10} ${centerX + 5},${by2 - 10} ${centerX},${by2}" fill="#888"/>`;
+    }
+  });
+
+  svg += '</svg>';
+  return sharp(Buffer.from(svg)).png().toBuffer();
+}
+
+// 第1章专用:S3 总体业务流程图(三区: 物料计划 → 采购执行 → 协同看板)
+async function generateOverviewFlowPNG() {
+  const W = 780, H = 580;
+  let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}">`;
+  svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
+  svg += `<text x="${W/2}" y="28" font-family="Microsoft YaHei" font-size="15" font-weight="bold" fill="#1F4E79" text-anchor="middle">S3 供应协同 — 总体业务流程</text>`;
+
+  // 三区背景
+  const zones = [
+    { y: 42, h: 170, color: '#E8F0FE', label: '物料计划域(需求→计划→异常)', lx: 10, ly: 60 },
+    { y: 220, h: 200, color: '#FFF3E0', label: '采购管理域(申请→合并生成要货令/采购订单/委外加工→工单外协)', lx: 10, ly: 238 },
+    { y: 428, h: 120, color: '#E8F5E9', label: '供应协同看板域(KPI监控→齐套→数据中台)', lx: 10, ly: 446 },
+  ];
+  zones.forEach(z => {
+    svg += `<rect x="8" y="${z.y}" width="${W-16}" height="${z.h}" rx="6" fill="${z.color}" stroke="#ccc" stroke-width="0.5" stroke-dasharray="4,3"/>`;
+    svg += `<text x="${z.lx}" y="${z.ly}" font-family="Microsoft YaHei" font-size="12" font-weight="bold" fill="#555">${z.label}</text>`;
+  });
+
+  // 物料计划域节点
+  const mpNodes = [
+    { x: 30, y: 85, w: 130, label: 'MDP数据同步\n(工单/库存/在途)', color: '#1565C0' },
+    { x: 190, y: 85, w: 130, label: 'MRP净需求计算', color: '#1565C0' },
+    { x: 350, y: 85, w: 120, label: '发布物料需求', color: '#1565C0' },
+    { x: 500, y: 85, w: 120, label: '交货计划跟踪', color: '#2E7D32' },
+    { x: 350, y: 145, w: 120, label: '交货异常记录', color: '#F57C00' },
+  ];
+  mpNodes.forEach(n => {
+    const fill = n.color === '#F57C00' ? '#FFF3E0' : n.color;
+    const stroke = n.color === '#F57C00' ? '#E65100' : n.color;
+    const tc = (n.color === '#1565C0' || n.color === '#2E7D32') ? '#fff' : '#333';
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="36" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + 18;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="${tc}" text-anchor="middle">${l}</text>`;
+    });
+  });
+  // MP arrows
+  svg += `<line x1="160" y1="103" x2="190" y2="103" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="186,98 186,108 193,103" fill="#666"/>`;
+  svg += `<line x1="320" y1="103" x2="350" y2="103" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="346,98 346,108 353,103" fill="#666"/>`;
+  svg += `<line x1="470" y1="103" x2="500" y2="103" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="496,98 496,108 503,103" fill="#666"/>`;
+  svg += `<line x1="470" y1="121" x2="410" y2="145" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="414,140 414,150 407,145" fill="#666"/>`;
+
+  // 采购管理域节点
+  // Row 1: 物料采购申请(起点—根据物料类型自动合并生成对应订单)
+  const applyNode = { x: 230, y: 255, w: 320, h: 44, label: '物料采购申请\n(根据物料类型自动合并生成要货令/采购订单/委外加工订单)', color: '#1565C0' };
+  (() => {
+    const n = applyNode;
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${n.color}" stroke="${n.color}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + n.h / 2;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="#fff" text-anchor="middle">${l}</text>`;
+    });
+  })();
+
+  // Row 2: 三路分支(要货令 / 采购订单 / 委外加工订单)
+  const peBranchNodes = [
+    { x: 30, y: 318, w: 160, h: 40, label: '要货令\n(DO类型)', color: '#E65100' },
+    { x: 220, y: 318, w: 160, h: 40, label: '采购订单\n(PO类型)', color: '#E65100' },
+    { x: 410, y: 318, w: 160, h: 40, label: '委外加工订单\n(PW类型)', color: '#6A1B9A' },
+  ];
+  peBranchNodes.forEach(n => {
+    const isPurple = n.color === '#6A1B9A';
+    const fill = isPurple ? '#F3E5F5' : n.color;
+    const stroke = isPurple ? '#6A1B9A' : n.color;
+    const tc = isPurple ? '#333' : '#fff';
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + n.h / 2;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="${tc}" text-anchor="middle">${l}</text>`;
+    });
+  });
+
+  // Row 3: 工单下达→工序外协订单(独立分支)
+  const peExtNodes = [
+    { x: 140, y: 378, w: 130, h: 40, label: '工单下达', color: '#1565C0' },
+    { x: 330, y: 378, w: 180, h: 40, label: '工序外协订单\n(工单工序关联)', color: '#6A1B9A' },
+  ];
+  peExtNodes.forEach(n => {
+    const isPurple = n.color === '#6A1B9A';
+    const fill = isPurple ? '#F3E5F5' : n.color;
+    const stroke = isPurple ? '#6A1B9A' : n.color;
+    const tc = isPurple ? '#333' : '#fff';
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + n.h / 2;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="${tc}" text-anchor="middle">${l}</text>`;
+    });
+  });
+
+  // PE arrows
+  // 申请 → 三路分支
+  const applyCx = applyNode.x + applyNode.w / 2;
+  const applyBot = applyNode.y + applyNode.h;
+  peBranchNodes.forEach(n => {
+    const nx = n.x + n.w / 2;
+    svg += `<line x1="${applyCx}" y1="${applyBot}" x2="${nx}" y2="${n.y}" stroke="#666" stroke-width="1.2"/>`;
+    svg += `<polygon points="${nx-5},${n.y-10} ${nx+5},${n.y-10} ${nx},${n.y}" fill="#666"/>`;
+  });
+  // 工单下达 → 工序外协
+  svg += `<line x1="270" y1="398" x2="330" y2="398" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="326,393 326,403 333,398" fill="#666"/>`;
+  // 分隔标注
+  svg += `<text x="40" y="373" font-family="Microsoft YaHei" font-size="9" fill="#888" text-anchor="start">↓ 工单下达时自动生成工序外协订单 ↓</text>`;
+  // 三路分支水平关联虚线
+  svg += `<line x1="190" y1="338" x2="220" y2="338" stroke="#999" stroke-width="0.8" stroke-dasharray="4,3"/>`;
+  svg += `<line x1="380" y1="338" x2="410" y2="338" stroke="#999" stroke-width="0.8" stroke-dasharray="4,3"/>`;
+
+  // 供应协同看板域节点
+  const kbNodes = [
+    { x: 30, y: 465, w: 140, label: '4维KPI卡片\n+趋势箭头', color: '#2E7D32' },
+    { x: 210, y: 465, w: 140, label: '工单齐套看板\n(30+字段)', color: '#2E7D32' },
+    { x: 390, y: 465, w: 140, label: 'MDP运行监控\n(任务状态)', color: '#0D47A1' },
+    { x: 570, y: 465, w: 140, label: '策略模拟\nWhat-If分析', color: '#B71C1C' },
+  ];
+  kbNodes.forEach(n => {
+    const fill = n.color === '#B71C1C' ? '#FFEBEE' : n.color;
+    const stroke = n.color === '#B71C1C' ? '#C62828' : n.color;
+    const tc = (n.color === '#2E7D32' || n.color === '#0D47A1') ? '#fff' : '#333';
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="48" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + 24;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="${tc}" text-anchor="middle">${l}</text>`;
+    });
+  });
+  svg += `<line x1="170" y1="489" x2="210" y2="489" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="206,484 206,494 213,489" fill="#666"/>`;
+  svg += `<line x1="350" y1="489" x2="390" y2="489" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="386,484 386,494 393,489" fill="#666"/>`;
+  svg += `<line x1="530" y1="489" x2="570" y2="489" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="566,484 566,494 573,489" fill="#666"/>`;
+
+  // 串联标注
+  svg += `<text x="150" y="212" font-family="Microsoft YaHei" font-size="10" fill="#888" text-anchor="middle">↓ 已发布需求驱动采购申请(按物料类型自动合并) ↓</text>`;
+  svg += `<text x="150" y="424" font-family="Microsoft YaHei" font-size="10" fill="#888" text-anchor="middle">↓ 采购订单数据进入DWD宽表供看板消费 ↓</text>`;
+
+  // 数据标签
+  svg += `<text x="50" y="545" font-family="Microsoft YaHei" font-size="9" fill="#999">数据链路: 外部系统(ERP/MES/SRM) → MDP入站 → ODS(STG) → STD → DWD宽表 → KPI指标层 → 看板消费</text>`;
+
+  svg += '</svg>';
+  return sharp(Buffer.from(svg)).png().toBuffer();
+}
+
+// ═══════════════════════════════════════════════════
+// 章节构建函数:8 子章节
+// ═══════════════════════════════════════════════════
+function buildChapter(num, title, cfg) {
+  const ch = [];
+  const pre = `${num}.`;
+
+  // §X.1 目标/宗旨
+  ch.push(h2(`${pre}1  目标/宗旨`));
+  (cfg.targets || []).forEach(t => ch.push(bullet(t)));
+
+  // §X.2 业务流程图
+  ch.push(h2(`${pre}2  业务流程图`));
+  const img = cfg.flowImage || (cfg.flowSteps ? flowImageCache[num] : null);
+  if (img) {
+    ch.push(flowImageParagraph(img));
+  } else {
+    ch.push(p('(见系统操作流程图 / 附件 Visio 流程图)'));
+  }
+  if (cfg.flowNote) ch.push(p(cfg.flowNote));
+
+  // §X.3 业务流程说明
+  ch.push(h2(`${pre}3  业务流程说明`));
+  if (cfg.activities && cfg.activities.length) {
+    ch.push(activityTable(cfg.activities));
+  } else {
+    ch.push(p('参照系统页面操作流程,主要操作步骤包含:'));
+    (cfg.activityDesc || []).forEach(a => ch.push(bullet(a)));
+  }
+
+  // §X.4 业务流程规则
+  ch.push(h2(`${pre}4  业务流程规则`));
+  if (cfg.rules && cfg.rules.length) {
+    ch.push(ruleTable(cfg.rules));
+  } else {
+    ch.push(p('无特殊业务规则'));
+  }
+
+  // §X.5 变化和改进点
+  ch.push(h2(`${pre}5  变化和改进点`));
+  (cfg.improvements || []).forEach(i => ch.push(bullet(i)));
+
+  // §X.6 权限管理需求
+  ch.push(h2(`${pre}6  权限管理需求`));
+  if (cfg.permissions && cfg.permissions.length) {
+    ch.push(permTable(cfg.permissions));
+  } else {
+    ch.push(p('参照第 14 章 权限管理汇总'));
+  }
+
+  // §X.7 系统接口集成
+  ch.push(h2(`${pre}7  系统接口集成`));
+  if (cfg.interfaces && cfg.interfaces.length) {
+    ch.push(interfaceTable(cfg.interfaces));
+  } else {
+    ch.push(p('无外部系统接口;页面数据通过 Admin.NET 内置 API 获取'));
+  }
+
+  // §X.8 报表需求
+  ch.push(h2(`${pre}8  报表需求`));
+  if (cfg.reports && cfg.reports.length) {
+    ch.push(reportTable(cfg.reports));
+  } else {
+    ch.push(p('无独立报表需求,相关数据通过看板和列表页面查看'));
+  }
+
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 章节数据配置
+// ═══════════════════════════════════════════════════
+
+// ── Chapter 2: 物料需求计划 ──
+const _ch2cfg = {
+  targets: [
+    '实现物料需求计划的在线管理,通过MDP同步外部系统工单需求、库存和采购在途数据。',
+    '利用MRP逻辑自动计算物料净需求,生成可供发布的物料需求计划,为采购管理和交货计划提供数据基础。',
+    '核心KPI为物料净需求的准确性和计划发布的及时性。',
+  ],
+  flowNote: '流程路径:MDP数据同步(工单/库存/在途) → MRP净需求计算 → 勾选发布/全部发布 → 手动调整 → 驱动采购申请/交货计划。',
+  flowSteps: [
+    { label: 'MDP数据同步(工单/库存/在途)', role: '物料计划员' },
+    { label: 'MRP净需求计算', role: '物料计划员' },
+    { label: '勾选发布/全部发布', role: '物料计划员' },
+    { label: '手动新增/编辑/删除', role: '物料计划员' },
+    { label: '驱动采购申请/交货计划', role: '系统自动' },
+  ],
+  activities: [
+    ['10', '数据同步', '系统(MDP)', 'MDP定时从ERP同步工单需求、库存和采购在途数据写入ODS层', '外部系统数据', 'ODS数据'],
+    ['20', 'MRP净需求计算', '系统(DWD)', 'DWD层清洗关联,计算To-be-Scheduled Qty = MES Qty - Loc Qty - Scheduled Qty', 'ODS工单/库存/在途数据', '物料净需求结果'],
+    ['30', '计划查询筛选', '物料计划员', '按物料编码、物料名称、需求日期等维度过滤查询,支持分页和列设置', '查询条件', '物料需求列表'],
+    ['40', '发布管理', '物料计划员', '支持勾选定向发布或一键全部发布,发布后状态变为"已发布",可取消发布回退', '未发布计划', '已发布/取消发布计划'],
+    ['50', '手动CRUD', '物料计划员', '支持手动新增、编辑、删除物料需求记录,满足特殊场景灵活调整', '手动录入数据', '更新后计划'],
+  ],
+  rules: [
+    ['1', '净需求计算公式', 'To-be-Scheduled Qty = MES Qty - Loc Qty - Scheduled Qty,结果为负时净需求为零'],
+    ['2', '发布校验', '发布前不做业务规则校验,计划员自行确认数据准确性后执行;仅已发布状态可取消发布'],
+    ['3', '删除限制', '删除操作需二次确认,防止误操作'],
+    ['4', '数据隔离', '不同租户数据通过租户ID隔离,同一租户内计划员共享视图'],
+  ],
+  improvements: [
+    '原ERP物料需求计算为离线批处理,S3通过MDP实时同步将计算频率从"每日一次"提升为"近实时"。',
+    '新增勾选发布和全部发布两种模式,替代原系统单一全量发布方式,提高操作灵活性。',
+    '新增取消发布功能,填补原系统缺乏"发布回退"能力的空白。',
+    '新增列设置功能,不同角色可按需配置显示的字段组合。',
+  ],
+  permissions: [
+    ['1', '物料计划员', '查看和增删改查权限;发布/取消发布操作权限'],
+    ['2', '采购员/供应链经理', '仅查看权限(只读)'],
+  ],
+  interfaces: [
+    ['1', 'MDP入站-工单需求', '从ERP同步工单需求数据(work_order_demand)', '定时(日)'],
+    ['2', 'MDP入站-库存快照', '从库存系统同步期初库存(inventory snapshot)', '定时(日)'],
+    ['3', 'MDP入站-在途采购', '从SRM同步采购在途数据', '定时(日)'],
+    ['4', 'MDP出站-需求发布', '发布后物料需求计划同步至SRM驱动采购申请', '发布时触发'],
+  ],
+  reports: [
+    ['1', '物料需求计划明细报表', '按日期范围导出指定物料的需求计算明细', 'Excel/CSV格式'],
+    ['2', '需求计划发布统计报表', '按时间段统计发布数量、发布及时率', '按周/月'],
+    ['3', '需求满足率趋势报表', '按周/月展示物料净需求满足趋势', '趋势图表'],
+  ],
+};
+const ch2 = () => buildChapter('2', '物料需求计划', _ch2cfg);
+
+// ── Chapter 3: 物料交货计划 ──
+const _ch3cfg = {
+  targets: [
+    '基于已发布物料需求计划生成交货计划,并驱动供应商端交货计划同步。',
+    '通过交货状态同步实现供应商与计划端的双向信息互通,提升交货管理的协同效率。',
+  ],
+  flowNote: '流程路径:已发布物料需求计划 → 交货计划生成 → 驱动供应商交货计划 → 交货状态同步。',
+  flowSteps: [
+    { label: '已发布物料需求计划', role: '物料计划员' },
+    { label: '交货计划生成', role: '物料计划员' },
+    { label: '驱动供应商交货计划', role: '物料计划员' },
+    { label: '交货状态同步', role: '系统自动' },
+  ],
+  activities: [
+    ['10', '需求发布', '物料计划员', '基于MRP净需求计算结果确认并发布物料需求计划,作为交货计划的输入来源', 'MRP净需求结果', '已发布物料需求计划'],
+    ['20', '计划生成', '系统(MDP)', '基于已发布物料需求计划自动生成交货计划,含物料、供应商、计划交期等核心信息', '已发布需求', '交货计划记录'],
+    ['30', '驱动供应商', '物料计划员/系统', '交货计划同步至供应商端,驱动供应商按计划安排生产和交货', '交货计划', '供应商交货计划'],
+    ['40', '状态同步', '系统(MDP)', '供应商交货状态回传至计划端,实现双向信息同步更新', '供应商交货状态', '同步后交货计划'],
+  ],
+  rules: [
+    ['1', '生成条件', '仅基于已发布物料需求计划生成交货计划,未发布需求不进入交货计划流程'],
+    ['2', '供应商驱动', '交货计划生成后自动同步至供应商端,驱动供应商按计划安排交货'],
+    ['3', '状态同步规则', '供应商交货状态变更时自动回传至计划端,确保计划与执行数据一致'],
+    ['4', '交货单号唯一性', '每个交货计划行对应唯一dsnum,用于与供应商端交货单关联匹配'],
+  ],
+  improvements: [
+    '从原ERP中交货计划与采购订单混合管理升级为独立专项页面,聚焦交货协同视角。',
+    '新增供应商交货计划驱动机制,替代原系统单向信息传递方式,实现双向协同。',
+    '新增交货状态自动同步功能,供应商交货反馈实时回传至计划端。',
+  ],
+  permissions: [
+    ['1', '物料计划员', '查看和操作权限'],
+    ['2', '采购员/仓库管理员', '仅查看权限'],
+    ['3', '供应链经理', '查看和导出权限'],
+  ],
+  interfaces: [
+    ['1', 'MDP入站-需求发布', '从物料需求计划读取已发布需求', '发布时触发'],
+    ['2', 'MDP出站-交货计划', '交货计划同步至供应商端驱动生产和交货', '计划生成时'],
+    ['3', 'MDP入站-交货状态', '供应商交货状态变更回传至计划端', '状态变更时'],
+    ['4', '核心数据表', 'srm_polist_ds(采购订单交付计划)', '实时关联'],
+  ],
+  reports: [
+    ['1', '交货计划执行报表', '按供应商/物料统计交货计划生成与执行情况', '按周/月'],
+    ['2', '供应商交货准时率报表', '按时间段统计各供应商交货准时率', '供应商维度'],
+    ['3', '交货状态同步报表', '监控交货状态同步成功率和延迟情况', '实时监控'],
+  ],
+};
+const ch3 = () => buildChapter('3', '物料交货计划', _ch3cfg);
+
+// ── Chapter 4: 交货单异常记录 ──
+const _ch4cfg = {
+  targets: [
+    '提供交货过程中产生的异常记录的只读查询视图,帮助物料计划员和质量管理人员快速掌握交货逾期、短缺、质量等异常情况。',
+    '异常数据来源于系统自动比对的交货计划与实际交货差异,为供应链风险管理和供应商绩效评估提供数据支撑。',
+  ],
+  flowNote: '流程路径:MDP同步交货数据 → 系统自动比对计划vs实际 → 差异超阈值自动标记异常 → 异常记录列表展示 → 多维筛选查询 → 明细查看/导出。',
+  flowSteps: [
+    { label: 'MDP同步交货数据', role: '系统(MDP)' },
+    { label: '系统自动比对计划vs实际差异', role: '系统自动' },
+    { label: '超阈值自动标记异常记录', role: '系统自动' },
+    { label: '多维筛选查询(单号/供应商/类型/日期)', role: '物料计划员' },
+    { label: '明细查看/导出', role: '物料计划员' },
+  ],
+  activities: [
+    ['10', '异常自动标记', '系统(MDP)', '基于交货计划与实际交货数据自动比对,逾期超3天或实收<计划80%时自动生成异常标记', '交货执行数据', '异常记录'],
+    ['20', '异常记录查询', '计划员/质量员', '按交货单号、供应商、异常类型、计划日期范围多维度筛选查询', '筛选条件', '异常记录列表'],
+    ['30', '明细查看', '计划员/质量员', '查看每条异常记录的详细信息:来源交货单、物料、计划/实际数量、异常原因', '异常记录', '异常详情'],
+    ['40', '数据导出', '计划员', '将当前查询结果导出为CSV/Excel文件', '查询结果', '导出文件'],
+  ],
+  rules: [
+    ['1', '自动标记条件', '逾期超过3个工作日未到货,或实收数量<计划数量80%时,系统自动标记为异常记录'],
+    ['2', '数据来源', '异常记录数据来源于MDP同步的交货计划和实际交货数据自动比对结果'],
+    ['3', '只读视图', '该页面为只读查询视图,不支持手动新增、编辑或删除异常记录,确保数据客观性'],
+    ['4', '可追溯性', '每条异常记录可关联到原始交货计划记录,追溯异常来源和影响范围'],
+  ],
+  improvements: [
+    '从原系统"纸质异常报告+Excel人工比对"升级为系统自动识别和在线查询,减少人工发现异常的延迟和遗漏。',
+    '新增自动触发规则,系统根据阈值自动标记异常,替代原系统完全依赖人工发现的方式。',
+    '异常数据与供应商绩效评估模块联动,为供应商评级提供量化依据。',
+  ],
+  permissions: [
+    ['1', '物料计划员', '查看和导出权限'],
+    ['2', '质量管理员', '查看权限(质量追溯)'],
+    ['3', '供应链经理', '查看和导出权限'],
+  ],
+  interfaces: [
+    ['1', '交货差异比对', 'MDP从交货计划与收货数据自动比对计划vs实际差异', '定时(日)'],
+    ['2', '核心数据表', 'DeliveryExceptionMaster(交货异常记录视图)', '只读查询'],
+  ],
+  reports: [
+    ['1', '交货异常统计报表', '按供应商/异常类型/时间段统计异常数量及趋势', '按周/月'],
+    ['2', '供应商异常率排名报表', '按异常发生率对供应商排名,用于供应商绩效评估', '供应商维度'],
+  ],
+};
+const ch4 = () => buildChapter('4', '交货单异常记录', _ch4cfg);
+
+// ── Chapter 5: 要货令 ──
+const _ch5cfg = {
+  targets: [
+    '承接物料采购申请驱动的要货指令(Demand Order)在线管理,作为采购执行的具体载体。',
+    '通过供应选择和物料选择确保要货令的精准下达,建立从申请到执行的衔接。',
+  ],
+  flowNote: '流程路径:物料采购申请驱动 → 要货令创建 → 供应选择 → 物料选择 → 订单保存。',
+  flowSteps: [
+    { label: '物料采购申请驱动', role: '采购员' },
+    { label: '要货令创建', role: '采购员' },
+    { label: '供应选择', role: '采购员' },
+    { label: '物料选择', role: '采购员' },
+    { label: '订单保存', role: '采购员' },
+  ],
+  activities: [
+    ['10', '申请驱动', '采购员', '接收已批准物料采购申请驱动信号,启动要货令创建流程', '已批准采购申请', '要货令创建入口'],
+    ['20', '要货令创建', '采购员', '填写要货令核心信息:单号、采购组、部门、日期、合同编号等', '采购申请驱动', '要货令主记录'],
+    ['30', '供应选择', '采购员', '从货源清单动态加载下拉选择供应商和采购组,确保数据实时性', '货源清单', '选定供应商/采购组'],
+    ['40', '物料选择', '采购员', '选择要货物料,填写需求数量、交货日期等明细信息', '物料列表', '要货令明细行'],
+    ['50', '订单保存', '采购员', '确认无误后保存要货令,生成DO类型采购订单记录', '要货令数据', '已保存要货令(PurOrdMaster/DO)'],
+  ],
+  rules: [
+    ['1', '驱动来源', '要货令由已批准物料采购申请驱动生成,确保需求来源可追溯'],
+    ['2', '必填校验', '供应商、物料、需求数量为必填字段,提交时进行非空校验'],
+    ['3', '供应校验', '选择的供应商须在有效货源清单中,确保货源合规'],
+    ['4', '单号唯一性', '要货令单号需保证唯一性,删除操作需二次确认'],
+  ],
+  improvements: [
+    '从原采购申请/要货令混合管理升级为采购申请驱动→要货令执行的清晰链路。',
+    '新增供应选择独立步骤,从货源清单动态加载,确保供应商资质合规。',
+    '新增物料选择独立步骤,支持明细行级精准管理。',
+  ],
+  permissions: [
+    ['1', '采购员', '增删改查全部权限'],
+    ['2', '供应链经理', '查看权限'],
+  ],
+  interfaces: [
+    ['1', 'MDP入站-采购申请', '从物料采购申请读取已批准申请作为驱动来源', '申请批准时触发'],
+    ['2', 'MDP入站-供应商数据', '供应商和采购组数据从SRM系统同步', '定时(日)'],
+    ['3', 'MDP出站-要货令', '保存后要货令数据同步至ERP', '保存时触发'],
+    ['4', '核心数据表', 'PurOrdMaster(采购订单主表,ReqBy=DO类型)', '实时'],
+  ],
+  reports: [
+    ['1', '要货令执行报表', '按时间段统计要货令数量和供应商分布', '按周/月'],
+    ['2', '供应商要货令统计', '按供应商维度汇总要货令下发情况', '供应商维度'],
+  ],
+};
+const ch5 = () => buildChapter('5', '要货令', _ch5cfg);
+
+// ── Chapter 6: 物料采购申请 ──
+const _ch6cfg = {
+  targets: [
+    '承接物料交货计划和MRP检查输出的采购需求,通过规范的申请-审批流程确保采购需求的合理性和合规性。',
+    '批准后根据物料类型自动驱动生成对应的采购订单(PO)、要货令(DO)或委外加工订单(PW),作为采购执行的依据。',
+  ],
+  flowNote: '流程路径:物料交货计划/MRP检查 → 采购申请创建 → 审核/批准 → 驱动采购订单/要货令/委外加工订单生成。',
+  flowSteps: [
+    { label: '物料交货计划/MRP检查驱动', role: '采购员' },
+    { label: '采购申请创建/编辑', role: '采购员' },
+    { label: '审核/批准', role: '审批人' },
+    { label: '驱动采购订单/要货令/委外加工', role: '系统自动' },
+  ],
+  activities: [
+    ['10', '申请创建', '采购员', '基于物料交货计划或MRP检查结果新增采购申请,填写申请单号、物料、供应商、需求数量、需求日期', '物料交货计划/MRP检查结果', '采购申请单'],
+    ['20', '查询编辑', '采购员', '按申请单号、物料、供应商、状态多维度筛选查询,支持编辑未提交的申请', '筛选条件', '申请列表'],
+    ['30', '审批流流转', '审批人', '申请单提交后进入审批流,经指定审批人审核后状态更新为"已批准"', '已提交申请', '审核/批准结果'],
+    ['40', '订单驱动', '系统', '已批准申请根据物料类型自动合并生成对应订单:DO物料→要货令,PO物料→采购订单,PW物料→委外加工订单', '已批准申请', '采购订单/要货令/委外加工订单'],
+  ],
+  rules: [
+    ['1', '审批流规则', '申请单提交后进入审批流,需经指定审批人审核后方可进入"已批准"状态'],
+    ['2', '必填校验', '物料编码、供应商、需求数量、需求日期为必填,需求数量须>0'],
+    ['3', '来源校验', '申请创建时需校验物料交货计划或MRP检查记录已发布且未关闭'],
+    ['4', '批准后保护', '已批准申请单不可编辑或删除,仅可查看,保护审批结果严肃性'],
+    ['5', '订单驱动规则', '批准后系统根据物料类型自动合并生成对应订单:DO物料→要货令,PO物料→采购订单,PW物料→委外加工订单'],
+  ],
+  improvements: [
+    '从原系统依赖邮件/纸质审批升级为系统化审批流,审批过程可追溯、可审计。',
+    '新增与物料交货计划和MRP检查的关联追溯,确保需求来源可查。',
+    '新增状态驱动的操作权限控制(不同状态下可执行不同操作)。',
+    '新增批准后自动按物料类型分流驱动三类订单(采购订单/要货令/委外加工订单),替代原系统手动分类下达方式。',
+  ],
+  permissions: [
+    ['1', '采购员', '增删改查和提交权限'],
+    ['2', '审批人(采购经理)', '审核/批准权限'],
+    ['3', '物料计划员', '仅查看权限'],
+  ],
+  interfaces: [
+    ['1', 'MDP入站-交货计划', '从物料交货计划读取可关联的交货计划行项', '实时'],
+    ['2', 'MDP入站-MRP检查', '从MRP检查结果读取可关联的需求行项', '实时'],
+    ['3', 'MDP出站-批准申请', '批准后采购申请数据同步至ERP,根据物料类型自动分流驱动订单生成', '批准时触发'],
+  ],
+  reports: [
+    ['1', '采购申请审批效率报表', '统计申请从提交到批准的平均审批时长', '按周/月'],
+    ['2', '采购申请转化率报表', '统计申请转化为各类型订单(PO/DO/PW)的比例', '按周/月'],
+    ['3', '申请金额汇总报表', '按部门/采购组统计采购申请金额', '按周/月'],
+  ],
+};
+const ch6 = () => buildChapter('6', '物料采购申请', _ch6cfg);
+
+// ── Chapter 7: 物料采购订单 ──
+const _ch7cfg = {
+  targets: [
+    '管理已下达至供应商的正式采购订单,跟踪订单从下达到收货完成的全生命周期。',
+    '确保采购执行的透明度和可控性,为S4交货管理提供数据基础。',
+  ],
+  flowNote: '流程路径:采购订单生成 → 供应商确认/交期回复 → 发货关联 → 订单状态跟踪 → 收货完成/订单关闭。',
+  flowSteps: [
+    { label: '采购订单生成', role: '采购员' },
+    { label: '供应商确认/交期回复', role: '采购员' },
+    { label: '发货关联', role: '采购员' },
+    { label: '订单状态跟踪', role: '采购员' },
+    { label: '收货完成/订单关闭', role: '系统自动' },
+  ],
+  activities: [
+    ['10', '订单生成', '采购员/系统', '基于已批准采购申请自动或手动生成采购订单,填写供应商、物料、数量、交期等核心信息', '已批准采购申请', '采购订单'],
+    ['20', '供应商确认', '采购员', '供应商在线确认订单并回复预计交期,确认信息回写至订单', '供应商反馈', '确认后订单'],
+    ['30', '发货关联', '采购员', '关联发货单数据,展示发货数量、日期与订单的对应关系', '发货数据', '发货关联视图'],
+    ['40', '状态跟踪', '采购员', '跟踪订单从"已下达→部分收货→已完成→已关闭"完整生命周期状态', '订单数据', '状态视图'],
+    ['50', '收货关闭', '系统', '收货数据累计达订单数量时自动更新为"已完成",已完成订单可手动关闭', '收货数据', '订单关闭'],
+  ],
+  rules: [
+    ['1', '订单号唯一性', '每个采购订单具有唯一订单号,自动分配或手动输入'],
+    ['2', '供应商校验', '生成订单时校验供应商是否在有效货源清单中,确保货源合规'],
+    ['3', '来源追溯', '订单来源于已批准的采购申请,确保需求可追溯'],
+    ['4', '状态自动更新', '收货数据累计达订单数量时状态自动更新为"已完成"'],
+    ['5', '订单关闭', '已完成/已取消订单可手动关闭,关闭后不可再收货'],
+  ],
+  improvements: [
+    '从原ERP分散的采购订单管理整合为统一页面,支持跨供应商、跨物料一站式查询。',
+    '新增发货关联视图,替代原系统手动比对发货与订单的低效方式。',
+    '新增订单状态自动流转机制,减少人工状态更新的延迟和错误。',
+  ],
+  permissions: [
+    ['1', '采购员', '增删改查权限'],
+    ['2', '物料计划员/供应链经理', '仅查看权限'],
+    ['3', '仓库管理员', '查看权限(安排收货计划)'],
+  ],
+  interfaces: [
+    ['1', 'MDP入站-供应商确认', '从SRM同步供应商订单确认和交期回复数据', '定时(日)'],
+    ['2', 'MDP入站-发货数据', '从WMS/SRM同步发货单数据用于发货关联', '定时(日)'],
+    ['3', 'MDP出站-采购订单', '采购订单数据同步至ERP触发收货和付款流程', '订单变更时'],
+    ['4', '核心数据表', 'vscm_jhjh(采购订单供应商视角)+ PurOrdMaster', '实时'],
+  ],
+  reports: [
+    ['1', '采购订单执行进度报表', '按订单/供应商展示下达数量、收货数量、完成百分比', '按周/月'],
+    ['2', '采购订单逾期报表', '对超过计划交货日期未完成的订单生成逾期清单', '实时监控'],
+    ['3', '采购金额汇总报表', '按时间段/采购组/供应商汇总采购金额', '按月/季'],
+  ],
+};
+const ch7 = () => buildChapter('7', '物料采购订单', _ch7cfg);
+
+// ── Chapter 8: 委外加工订单 ──
+const _ch8cfg = {
+  targets: [
+    '管理委托外部供应商进行物料加工的采购订单(PurOrdMaster/PW类型)。',
+    '支持订单明细的增删改查和物料选择,确保委外加工业务的规范化和可追溯性。',
+  ],
+  flowNote: '流程路径:委外加工订单创建 → 委外商选择 → 货源清单物料选择 → 明细行管理 → 订单保存。',
+  flowSteps: [
+    { label: '委外加工订单创建', role: '外协管理员' },
+    { label: '委外商选择', role: '外协管理员' },
+    { label: '货源清单物料选择', role: '外协管理员' },
+    { label: '明细行管理', role: '外协管理员' },
+    { label: '订单保存', role: '外协管理员' },
+  ],
+  activities: [
+    ['10', '订单创建', '外协管理员', '新增委外加工订单,填写订单基本信息', '委外需求', '委外订单记录'],
+    ['20', '委外商选择', '外协管理员', '从货源清单选择委外加工供应商', '货源清单', '选定委外商'],
+    ['30', '货源物料选择', '外协管理员', '从货源清单(selectSourceList)选择委外加工物料', '货源清单', '选中物料'],
+    ['40', '明细行管理', '外协管理员', '管理每个明细行的物料、数量、单价、交货日期等', '选中物料', '订单明细行'],
+    ['50', '订单保存', '外协管理员', '确认无误后保存委外加工订单,生成PW类型订单记录', '委外订单数据', '已保存委外订单(PurOrdMaster/PW)'],
+  ],
+  rules: [
+    ['1', '订单类型区分', '委外加工订单类型为PW,与普通采购订单PO类型区分管理'],
+    ['2', '货源校验', '选择委外商和委外物料时需校验其在货源清单中存在且状态有效'],
+    ['3', '必填字段', '委外商、物料、加工数量、计划交付日期为必填项'],
+    ['4', '明细行数量', '每个委外加工订单至少需包含一个明细行,不允许空订单'],
+  ],
+  improvements: [
+    '从原ERP与普通采购订单混合管理升级为独立页面,支持PW类型专项管理。',
+    '新增货源清单校验,确保委外业务物料选择合规。',
+    '新增订单明细子页面增删改查,替代原系统单层管理方式。',
+  ],
+  permissions: [
+    ['1', '外协管理员', '增删改查全部权限'],
+    ['2', '采购员/供应链经理', '查看权限'],
+  ],
+  interfaces: [
+    ['1', 'MDP入站-货源清单', '从SRM同步货源清单数据(sourcing_item表)', '定时(日)'],
+    ['2', 'MDP出站-委外订单', '委外加工订单数据同步至ERP,与生产工单关联', '订单变更时'],
+    ['3', '核心数据表', 'PurOrdMaster(订单类型PW)', '实时'],
+  ],
+  reports: [
+    ['1', '委外加工订单执行报表', '按委外商/物料统计订单下达和完成情况', '按周/月'],
+    ['2', '委外加工成本分析', '按时间段/委外商统计委外加工总成本', '按月/季'],
+  ],
+};
+const ch8 = () => buildChapter('8', '委外加工订单', _ch8cfg);
+
+// ── Chapter 9: 工序外协订单 ──
+const _ch9cfg = {
+  targets: [
+    '管理生产过程中特定工序委托外部供应商完成的采购订单。',
+    '支持工单-工序-外协订单三级关联,确保工序外协业务的精细化管理。',
+  ],
+  flowNote: '流程路径:选择生产工单 → 选择外协工序 → 创建外协订单 → 供应商/数量校验 → 订单保存/工单工序关联。',
+  flowSteps: [
+    { label: '选择生产工单', role: '外协管理员' },
+    { label: '选择外协工序', role: '外协管理员' },
+    { label: '创建外协订单', role: '外协管理员' },
+    { label: '供应商/数量校验', role: '系统自动' },
+    { label: '订单保存/工单工序关联', role: '系统自动' },
+  ],
+  activities: [
+    ['10', '选择工单', '外协管理员', '从生产工单列表选择需要外协的生产工单', '生产工单', '选中工单'],
+    ['20', '选择工序', '外协管理员', '通过selectProcessWorkOrder组件从工单选择需要外协的工序', '选中工单', '选中工序'],
+    ['30', '创建订单', '外协管理员', '基于选中的工单和工序创建外协订单,填写供应商、数量、交期等', '选中工序', '外协订单'],
+    ['40', '供应商/数量校验', '系统', '校验外协商在货源清单中有效、外协数量不超过工序计划生产数量', '外协订单', '校验结果'],
+    ['50', '保存关联', '外协管理员/系统', '保存订单并与生产工单工序建立关联,工单状态联动', '校验通过订单', '已保存关联订单'],
+  ],
+  rules: [
+    ['1', '工单校验', '选择的工序必须属于有效生产工单(状态不为已关闭或已取消)'],
+    ['2', '工序唯一性', '同一工单同一工序不可重复创建外协订单,防止重复委外'],
+    ['3', '供应商校验', '外协商必须在货源清单中有效,确保外协供应商资质'],
+    ['4', '数量约束', '外协数量不可超过工单该工序的计划生产数量'],
+  ],
+  improvements: [
+    '从原系统手工Excel管理升级为系统化管理,支持工单-工序-外协订单三级关联。',
+    '新增供应商/数量校验环节,确保外协业务合规。',
+    '新增订单保存与工单工序自动关联,实现工单状态联动。',
+  ],
+  permissions: [
+    ['1', '外协管理员', '增删改查全部权限'],
+    ['2', '生产计划员', '查看权限'],
+  ],
+  interfaces: [
+    ['1', 'MDP入站-工单工序', '从MES同步生产工单和工序数据', '定时(日)'],
+    ['2', 'MDP出站-外协订单', '工序外协订单数据同步至ERP', '订单变更时'],
+    ['3', '核心数据表', 'PurOrdMaster(PW类型,关联工单和工序信息)', '实时'],
+  ],
+  reports: [
+    ['1', '工序外协执行报表', '按工单/工序/外协商统计外协订单执行情况', '按周/月'],
+    ['2', '外协成本分析', '按工序类型统计外协成本', '按月/季'],
+  ],
+};
+const ch9 = () => buildChapter('9', '工序外协订单', _ch9cfg);
+
+// ── Chapter 10: 供应协同看板 ──
+const _ch10cfg = {
+  targets: [
+    '为管理层和业务人员提供S3模块的核心KPI可视化看板。',
+    '通过三大L1核心指标(工单按需准时齐套率、工单齐套周期、物料库存周转天数)全面监控供应健康度。',
+    '支持策略模拟What-If分析和运营指标建模配置。',
+  ],
+  flowNote: '流程路径:MDP数据同步/运营指标建模 → 4维KPI卡片展示 → 趋势箭头分析 → 多维筛选联动 → 分支看板(需求计划/交货计划) → 策略模拟/导出。',
+  flowSteps: [
+    { label: 'MDP数据同步/运营指标建模', role: '供应链经理' },
+    { label: '4维KPI卡片+趋势箭头展示', role: '供应链经理' },
+    { label: '多维筛选联动/分支看板视图', role: '供应链经理' },
+    { label: 'MDP同步任务监控', role: 'IT运维人员' },
+    { label: '策略模拟What-If/报表导出', role: '供应链经理' },
+  ],
+  activities: [
+    ['10', '数据加载', '系统', '页面初始化从后端加载homeS3最新KPI计算数据', 'KPI数据库', 'KPI展示数据'],
+    ['20', 'KPI卡片展示', '供应链经理', '4个KPI卡片:物料计划周期、物料计划满足率、物料计划人数、物料库存周转', 'KPI数据', '趋势+标签卡片'],
+    ['30', '基础查询', '供应链经理', '按日期、产品、订单号、产线、物料、供应商维度筛选', '筛选条件', '筛选后数据'],
+    ['40', '分支视图', '供应链经理', '分支一:MRP维度(BOM需求满足趋势+统计表);分支二:MDP维度(交货执行进度+满足率)', '看板数据', '分支详情'],
+    ['50', '操作功能', '供应链经理', '策略模拟(What-If分析)、导出报表、运营指标建模链接跳转', '看板数据', '模拟结果/导出文件'],
+  ],
+  rules: [
+    ['1', '指标等级判定', '齐套率低于目标标"严重"(红),齐套周期超目标标"警告"(橙),库存周转达标标"优秀"(绿)'],
+    ['2', '趋势计算', '趋势箭头基于同比/环比计算,上升为绿色箭头,下降为红色箭头'],
+    ['3', '数据刷新', '页面不自动刷新,需手动点击刷新按钮获取最新数据'],
+    ['4', '筛选联动', '基础查询条件变更时,KPI卡片和分支视图同步更新'],
+  ],
+  improvements: [
+    '从原系统零散Excel报表升级为实时可视化看板,支持交互式数据探索。',
+    '新增策略模拟功能(What-If),替代原系统依赖人工计算的分析方式。',
+    '新增指标建模配置入口,支持自定义KPI口径,替代硬编码指标定义方式。',
+  ],
+  permissions: [
+    ['1', '供应链经理', '查看、导出和策略模拟权限'],
+    ['2', '物料计划员/采购员/仓管员', '查看权限'],
+    ['3', '工厂厂长/总经理', '查看权限'],
+  ],
+  interfaces: [
+    ['1', '看板聚合API', 'GET /api/AidopKanban/module-detail?moduleCode=S3', '实时(页面加载)'],
+    ['2', 'KPI数据源', 'homeS3数据对象 (demandKitRatePct, kitCycleDays, materialInventoryTurnoverDays)', 'MDP定时刷新'],
+    ['3', '运营指标建模', 'KPI口径和计算公式通过运营指标建模模块配置', '配置驱动'],
+  ],
+  reports: [
+    ['1', '供应协同月度报告', '含三大L1指标月度趋势和同比分析', '按月度'],
+    ['2', '策略模拟分析报告', '记录策略模拟的输入参数和输出结果', '按需'],
+  ],
+};
+const ch10 = () => buildChapter('10', '供应协同看板', _ch10cfg);
+
+// ── Chapter 11: 工单物料齐套上线看板 ──
+const _ch11cfg = {
+  targets: [
+    '以工单为维度,展示每个工单所需物料的齐套状态(需求、发料、欠料、备料、在检、在途、承诺数量)。',
+    '帮助生产计划和物料计划人员精准识别缺料工单和责任供应商。',
+  ],
+  flowNote: '流程路径:MDP数据同步(工单/BOM/库存/在途) → MRP齐套计算 → 30+字段齐套信息展示 → 缺料定位/供应商维度分析 → 缺料预警/策略建议。',
+  flowSteps: [
+    { label: 'MDP同步(工单/BOM/库存/在途)', role: '物料计划员' },
+    { label: 'MRP齐套计算/手动刷新', role: '物料计划员' },
+    { label: '30+字段齐套信息展示', role: '物料计划员' },
+    { label: '欠料定位/供应商维度分析', role: '物料计划员' },
+    { label: '缺料预警/策略建议输出', role: '系统自动' },
+  ],
+  activities: [
+    ['10', '数据加载', '系统', '从WorkOrdDetailTotalKB宽表加载工单物料齐套数据', 'MDP宽表', '齐套数据'],
+    ['20', '多维筛选', '物料计划员', '按订单号、工单号、产品编码、物料编码、供应商名称筛选', '筛选条件', '筛选后列表'],
+    ['30', '齐套信息展示', '物料计划员', '展示30+字段:序号、订单/工单号、BOM版本、物料描述、需求/发料/欠料/备料/在检/在途数量', '齐套数据', '齐套明细'],
+    ['40', '缺料定位', '物料计划员', '欠料数量(shortage)>0标记为"未齐套",定位影响供应商', '齐套明细', '缺料清单'],
+    ['50', '列设置/排序', '物料计划员', '自定义显示/隐藏列,多字段升序/降序排序,分页10/20/50', '视图配置', '个性化视图'],
+  ],
+  rules: [
+    ['1', '齐套判定', '欠料数量(shortage)>0时该物料行视为"未齐套"'],
+    ['2', '欠料计算', 'shortage = qtyRequired - qtyRec,发料不足差额即为欠料'],
+    ['3', '刷新触发', '调用refreshWorkOrderMaterialReadiness接口重新执行MRP齐套计算'],
+    ['4', '分页显示', '按分页加载,支持10/20/50条每页,优化大数据量页面性能'],
+  ],
+  improvements: [
+    '从原系统手工Excel跟踪齐套状态升级为实时在线看板,数据来源于MDP实时计算宽表。',
+    '新增30+字段完整视图,替代原系统仅有"物料/需求数量"两个维度的简单跟踪。',
+    '新增供应商维度齐套分析(供应商编码/名称/采购组/采购员),快速定位责任供应商。',
+    '新增列设置和排序功能,支持不同角色个性化视图配置。',
+  ],
+  permissions: [
+    ['1', '物料计划员/生产计划员', '查看和刷新权限'],
+    ['2', '采购员', '查看权限(了解管辖供应商物料齐套情况)'],
+    ['3', '车间主任/供应链经理', '查看权限'],
+  ],
+  interfaces: [
+    ['1', 'MDP入站-齐套数据', '从ERP/MES/SRM同步工单需求、BOM、库存、在途、发货数据,DWD关联计算入宽表', '定时(日)'],
+    ['2', '齐套刷新API', 'refreshWorkOrderMaterialReadiness触发MRP齐套重算', '手动触发'],
+    ['3', '核心数据表', 'WorkOrdDetailTotalKB(工单物料欠料宽表)', '实时'],
+  ],
+  reports: [
+    ['1', '工单齐套日报', '每日按工单汇总齐套率和欠料明细', '日报'],
+    ['2', '供应商齐套分析', '按供应商统计供货工单齐套率,用于供应商绩效评估', '按周/月'],
+    ['3', '欠料预警报表', '对欠料超阈值工单生成预警清单', '实时推送'],
+  ],
+};
+const ch11 = () => buildChapter('11', '工单物料齐套上线看板', _ch11cfg);
+
+// ── Chapter 12: MDP 运行监控 ──
+const _ch12cfg = {
+  targets: [
+    '提供S3相关MDP数据同步与转换任务的运行状态可视化。',
+    '帮助IT运维人员和业务管理员及时发现和定位数据同步失败、转换异常等问题,确保数据准确性和时效性。',
+  ],
+  flowNote: '流程路径:MDP任务调度引擎运行 → 同步日志记录 → 任务状态列表展示 → 异常告警 → 运维响应处理。',
+  flowSteps: [
+    { label: 'MDP任务调度引擎运行', role: '系统(MDP)' },
+    { label: '同步日志记录(成功/失败/运行中)', role: '系统(MDP)' },
+    { label: 'S3模块任务列表展示', role: 'IT运维人员' },
+    { label: '失败任务红色高亮告警', role: '系统' },
+    { label: '运维响应处理/数据修复', role: 'IT运维人员' },
+  ],
+  activities: [
+    ['10', '任务列表展示', 'IT运维', '展示S3相关MDP同步任务列表:任务名、运行状态、开始/结束时间、记录数、错误信息', 'MDP日志', '任务列表'],
+    ['20', '状态筛选', 'IT运维', '按任务状态(全部/成功/失败/运行中)过滤', '筛选条件', '筛选后列表'],
+    ['30', '异常告警', '系统', '失败任务红色高亮标识和告警提示,便于及时响应', '任务状态', '告警视图'],
+    ['40', '统一视图', 'IT运维', '与平台级MDP监控互补,S3页面聚焦本模块任务实例', 'S3 MDP任务', '模块监控视图'],
+  ],
+  rules: [
+    ['1', '状态判定', '任务执行返回码非零判定为失败,零为成功'],
+    ['2', '记录数展示', '展示最近一次成功执行记录数;如最近失败则展示失败前最后成功记录数'],
+    ['3', '刷新频率', '页面不自动刷新,需手动刷新获取最新任务状态'],
+    ['4', '数据来源', '任务执行日志从MDP任务调度引擎日志表读取'],
+  ],
+  improvements: [
+    '从原系统无统一监控(依赖DBA手动检查数据库日志)升级为可视化监控页面。',
+    '新增S3模块专属任务视图,与平台级监控形成"总-分"监控体系。',
+    '新增错误信息直接展示,替代原系统需要连接数据库查询日志的低效方式。',
+  ],
+  permissions: [
+    ['1', 'IT运维人员', '查看权限'],
+    ['2', '供应链经理/物料计划员', '查看权限(了解数据同步状态)'],
+  ],
+  interfaces: [
+    ['1', 'MDP日志接口', '从MDP任务调度引擎读取同步任务日志(sync_logs表)', '实时'],
+    ['2', '核心数据表', 'mdp_sync_task_log(MDP同步任务日志表)', '实时'],
+  ],
+  reports: [
+    ['1', 'MDP任务运行日报', '每日汇总S3相关MDP任务成功率和平均耗时', '日报'],
+    ['2', '任务失败趋势报表', '按周/月统计任务失败趋势,评估数据同步稳定性', '按周/月'],
+  ],
+};
+const ch12 = () => buildChapter('12', 'MDP 运行监控', _ch12cfg);
+
+// ═══════════════════════════════════════════════════
+// Chapter 13: 数据中台架构
+// ═══════════════════════════════════════════════════
+function buildCh13() {
+  const ch = [];
+  ch.push(h1('13  数据中台架构'));
+  
+  ch.push(h2('13.1  目标/宗旨'));
+  ch.push(p('S3供应协同模块采用Ai-DOP统一数据中台分层架构,将业务运行数据经过贴源(STG)、标准化(STD)、宽表构建(DWD)、指标计算(KPI)四层处理,最终服务于前端页面查询、看板展示和KPI考核。'));
+  
+  ch.push(h2('13.2  分层设计'));
+  ch.push(table(
+    ['分层', '表前缀', '职责', '更新机制'],
+    [
+      ['贴源层(STG)', 'mdp_stg_*', '保留源系统原始数据', 'MDP同步覆盖写入'],
+      ['标准层(STD)', 'mdp_std_*', '统一字段口径,清洗脏数据', 'MDP转换(最新批次)'],
+      ['宽表层(DWD)', 'dwd_*', '面向页面和KPI主题事实表', 'MDP转换(最新批次)'],
+      ['指标层(KPI)', 'ado_s9_kpi_value_*', '按天存储L1-L4指标值', 'MDP KPI计算'],
+    ],
+    [1500, 2000, 4200, 2300]
+  ));
+
+  ch.push(h2('13.3  S3核心数据流'));
+  ch.push(bp('物料需求/计划链路'));
+  ch.push(p('work_order_demand / inventory_snapshot / srm_in_transit → mdp_stg_demand → mdp_std_demand → dwd_demand_schedule → ado_s9_kpi_value_*'));
+  ch.push(bp('采购订单/交货链路'));
+  ch.push(p('PurOrdMaster / srm_polist_ds / vscm_jhjh → mdp_stg_po → mdp_std_po → dwd_po_delivery → ado_s9_kpi_value_*'));
+  ch.push(bp('齐套/看板链路'));
+  ch.push(p('WorkOrdDetailTotalKB / homeS3 → DWD直接消费 → 前端看板和KPI卡片 → ado_s9_kpi_value_*'));
+
+  ch.push(h2('13.4  对象映射总表'));
+  ch.push(table(
+    ['业务对象', '运行表', '贴源层', '标准层/DWD', '消费端'],
+    [
+      ['物料需求计划', 'ic_demandschedule', 'mdp_stg_demand', 'mdp_std_demand / dwd_demand_schedule', '需求页、交货计划'],
+      ['交货计划', 'srm_polist_ds', 'mdp_stg_delivery', 'mdp_std_delivery / dwd_delivery_schedule', '交货页、异常记录'],
+      ['要货令(PurOrd DO)', 'PurOrdMaster', 'mdp_stg_po', 'mdp_std_po', '要货令页'],
+      ['采购订单', 'PurOrdMaster / vscm_jhjh', 'mdp_stg_po', 'mdp_std_po / dwd_po_delivery', '采购订单页、看板'],
+      ['委外加工(PW)', 'PurOrdMaster', 'mdp_stg_po', 'mdp_std_po', '委外页'],
+      ['工序外协', 'PurOrdMaster (PW+工单)', 'mdp_stg_po', 'mdp_std_po', '工序外协页'],
+      ['工单物料齐套', 'WorkOrdDetailTotalKB', '—', 'DWD宽表直接消费', '齐套看板'],
+      ['供应协同KPI', 'homeS3数据对象', '—', 'KPI计算直接消费', '供应看板'],
+    ],
+    [1100, 2200, 1600, 2400, 2700]
+  ));
+
+  ch.push(h2('13.5  MDP作业调度'));
+  ch.push(table(
+    ['配置项', '内容'],
+    [
+      ['作业编码', 'S3_MDP_SYNC_TRANSFORM'],
+      ['批次格式', 'S3_MDP_FULL_yyyyMMddHHmmss'],
+      ['手动刷新入口', '各页面刷新按钮 / 计划联动手动触发'],
+      ['监控入口', 'mdp_transform_run_log / mdp_sync_log / MDP运行监控页'],
+      ['执行方式', '定时调度 + 手动触发'],
+    ],
+    [2200, 7800]
+  ));
+
+  ch.push(h2('13.6  变化和改进点'));
+  ch.push(bullet('建立统一数据中台分层架构,S3业务数据从"各自为政"收敛到标准数据链路。'));
+  ch.push(bullet('页面和看板统一从DWD/KPI层消费,确保数据口径一致。'));
+  ch.push(bullet('MDP作业可观测,支持日志查询和异常追溯。'));
+  ch.push(bullet('新增手动刷新机制,业务人员可按需触发即时数据同步。'));
+
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// Chapter 14: 权限管理汇总
+// ═══════════════════════════════════════════════════
+function buildCh14() {
+  const ch = [];
+  ch.push(h1('14  权限管理汇总'));
+  ch.push(p('以下汇总S3供应协同模块各功能所涉及的角色和权限:'));
+  ch.push(table(
+    ['序号', '角色', '权限范围', '涉及模块'],
+    [
+      ['1', '物料计划员', '查看和操作物料需求计划、交货计划;查看交货异常记录;发布/取消发布;刷新齐套看板', '物料需求计划、交货计划、异常记录、齐套看板'],
+      ['2', '采购员', '增删改查要货令、采购申请、采购订单;查看交货计划和交货异常', '要货令、采购申请、采购订单'],
+      ['3', '外协管理员', '增删改查委外加工订单和工序外协订单', '委外加工、工序外协'],
+      ['4', '供应链经理', '查看所有模块;查看供应协同看板和管理KPI;导出报表;策略模拟', '全模块(查看+看板操作)'],
+      ['5', '审批人(采购经理)', '审核采购申请的审批流', '物料采购申请'],
+      ['6', 'IT运维人员', '查看MDP运行监控;监控同步任务状态', 'MDP运行监控'],
+      ['7', '质量管理员', '查看交货异常记录(质量追溯)', '交货单异常记录'],
+      ['8', '仓库管理员', '查看采购订单和交货计划(安排收货)', '采购订单、交货计划'],
+      ['9', '生产计划员/车间主任', '查看工序外协订单和齐套看板', '工序外协、齐套看板'],
+      ['10', '工厂厂长/总经理', '查看供应协同看板全部指标', '供应协同看板'],
+    ],
+    [600, 1600, 5000, 2800]
+  ));
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 文档构建主流程
+// ═══════════════════════════════════════════════════
+async function buildDocument() {
+  console.log('Generating flow chart images...');
+  flowImageCache['overview'] = await generateOverviewFlowPNG();
+
+  const allChapterConfigs = {
+    '2': _ch2cfg, '3': _ch3cfg, '4': _ch4cfg, '5': _ch5cfg,
+    '6': _ch6cfg, '7': _ch7cfg, '8': _ch8cfg, '9': _ch9cfg,
+    '10': _ch10cfg, '11': _ch11cfg, '12': _ch12cfg,
+  };
+  for (const [num, cfg] of Object.entries(allChapterConfigs)) {
+    if (cfg.flowSteps) {
+      flowImageCache[num] = await generateFlowPNG(cfg.flowSteps);
+    }
+  }
+  console.log('Flow chart images generated.');
+
+  const sections = [];
+
+  // ── 封面 ──
+  sections.push(empty(), empty(), empty());
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: 'S3 供应协同模块', bold: true, size: 52, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 100 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '蓝图设计方案', bold: true, size: 52, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 600 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: 'Ai-DOP 智慧运营决策平台', size: 28, font: FONT, color: '4472C4' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 120 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '版本:V1.0    日期:2026年5月27日    作者:AI Agent', size: 22, font: FONT, color: '808080' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 2400 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '【机密文档 · 仅限项目内部使用】', size: 21, font: FONT, color: 'C00000', italics: true })],
+    alignment: AlignmentType.CENTER,
+  }));
+
+  // ── 文档控制 ──
+  sections.push(pageBreak(), h1('文档控制'));
+  sections.push(h2('更改记录'));
+  sections.push(table(['日期', '姓名', '版本', '变更说明'],
+    [['2026/05/27', 'AI Agent', 'V1.0', '初版(基于S3模块系统实现)']],
+    [1800, 1200, 1000, 6000]));
+  sections.push(empty());
+  sections.push(h2('审核'));
+  sections.push(table(['姓名', '职位', '签字/日期'], [['', '', ''], ['', '', '']], [3000, 3000, 4000]));
+  sections.push(empty());
+  sections.push(h2('发布'));
+  sections.push(table(['编号', '名称', '地点'], [['', '', '']], [3000, 4000, 3000]));
+
+  // ── 目录 ──
+  sections.push(pageBreak(), h1('目录'));
+  sections.push(...buildTOC());
+  sections.push(pageBreak());
+
+  // ── 第1章: 总体业务方案 ──
+  sections.push(h1('1  总体业务方案'));
+  sections.push(h2('1.1  目标和宗旨'));
+  sections.push(bullet('S3供应协同模块是Ai-DOP平台的核心模块,聚焦物料供需平衡与供应商协同管理。'));
+  sections.push(bullet('承接S1产销协同模块的订单需求和S2制造协同模块的生产计划,通过物料需求计划(MRP)、采购管理和供应看板三大核心能力,实现从需求识别到交付闭环的全流程数字化管控。'));
+  sections.push(bullet('核心KPI:工单按需准时齐套率(S3-L1-A)、工单齐套周期(S3-L1-B)、物料库存周转天数(S3-L1-C)。'));
+  sections.push(h2('1.2  总体业务流程图'));
+  sections.push(p('S3供应协同模块覆盖从"物料需求计算"到"供应协同监控"的完整链路,下图展示了物料计划域、采购管理域和供应协同看板域三大功能域的整体关系:'));
+  sections.push(flowImageParagraph(flowImageCache['overview']));
+  sections.push(h2('1.3  方案设计'));
+  sections.push(p('S3模块采用"数据中台驱动 + 业务操作闭环"的架构模式,覆盖以下核心功能模块:'));
+  sections.push(bullet('物料需求计划:MDP同步+MRP净需求计算,支持发布管理和手动CRUD'));
+  sections.push(bullet('物料交货计划:交货执行跟踪、批量操作、差异自动标记'));
+  sections.push(bullet('交货单异常记录:只读查询视图,系统自动比对标记异常,支持多维筛选和导出'));
+  sections.push(bullet('要货令:采购流程起点,4状态生命周期管理'));
+  sections.push(bullet('物料采购申请:规范申请-审批流程,确保采购需求合规'));
+  sections.push(bullet('物料采购订单:订单全生命周期跟踪,交货关联和状态自动流转'));
+  sections.push(bullet('委外加工订单:PW类型专项管理,货源清单校验'));
+  sections.push(bullet('工序外协订单:工序级外协精细化管理,工单关联'));
+  sections.push(bullet('供应协同看板:4维KPI卡片+趋势箭头+分支看板+策略模拟'));
+  sections.push(bullet('工单物料齐套上线看板:30+字段齐套展示,供应商维度欠料分析'));
+  sections.push(bullet('MDP运行监控:S3模块专属同步任务可视化监控'));
+  sections.push(p('前端采用Vue 3 + Element Plus + TypeScript技术栈,列表页使用通用AidopDemoShell容器组件,看板页使用KPI卡片+趋势图表组合布局。后端基于Admin.NET框架分层架构实现。S3模块关键L1指标通过运营指标建模支持自定义口径配置。'));
+
+  // ── 第2~12章: 各功能模块 ──
+  sections.push(pageBreak()); sections.push(h1('2  物料需求计划')); sections.push(...ch2());
+  sections.push(pageBreak()); sections.push(h1('3  物料交货计划')); sections.push(...ch3());
+  sections.push(pageBreak()); sections.push(h1('4  交货单异常记录')); sections.push(...ch4());
+  sections.push(pageBreak()); sections.push(h1('5  要货令')); sections.push(...ch5());
+  sections.push(pageBreak()); sections.push(h1('6  物料采购申请')); sections.push(...ch6());
+  sections.push(pageBreak()); sections.push(h1('7  物料采购订单')); sections.push(...ch7());
+  sections.push(pageBreak()); sections.push(h1('8  委外加工订单')); sections.push(...ch8());
+  sections.push(pageBreak()); sections.push(h1('9  工序外协订单')); sections.push(...ch9());
+  sections.push(pageBreak()); sections.push(h1('10  供应协同看板')); sections.push(...ch10());
+  sections.push(pageBreak()); sections.push(h1('11  工单物料齐套上线看板')); sections.push(...ch11());
+  sections.push(pageBreak()); sections.push(h1('12  MDP 运行监控')); sections.push(...ch12());
+
+  // ── 第13~14章 ──
+  sections.push(pageBreak()); sections.push(...buildCh13());
+  sections.push(pageBreak()); sections.push(...buildCh14());
+
+  return sections;
+}
+
+// ═══════════════════════════════════════════════════
+// 生成文档
+// ═══════════════════════════════════════════════════
+async function main() {
+  const doc = new Document({
+    styles: { default: { document: { run: { font: FONT, size: 21 } } } },
+    sections: [{
+      properties: { page: { margin: { top: convertInchesToTwip(0.8), bottom: convertInchesToTwip(0.8), left: convertInchesToTwip(1.0), right: convertInchesToTwip(1.0) } } },
+      children: await buildDocument(),
+    }],
+  });
+
+  const buffer = await Packer.toBuffer(doc);
+  const outPath = 'd:\\DEMONET\\doc\\S3供应协同模块蓝图设计方案.docx';
+  const fallbackPath = 'd:\\DEMONET\\doc\\S3供应协同模块蓝图设计方案_V2.docx';
+  const tempPath = 'd:\\DEMONET\\doc\\S3_blueprint_temp.docx';
+  fs.writeFileSync(tempPath, buffer);
+  try { fs.unlinkSync(outPath); } catch(e) {}
+  try { fs.renameSync(tempPath, outPath); console.log(`Renamed to: ${outPath}`); }
+    catch(e) {
+      try { fs.unlinkSync(fallbackPath); } catch(_) {}
+      try { fs.renameSync(tempPath, fallbackPath); console.log(`Fallback to: ${fallbackPath}`); }
+        catch(e2) { console.log(`Generated: ${tempPath} (target locked)`); }
+    }
+  console.log(`Size: ${(buffer.length / 1024).toFixed(1)} KB`);
+}
+main().catch(err => { console.error('Error:', err); process.exit(1); });
+

+ 377 - 0
doc/generate_s3_req_docx.js

@@ -0,0 +1,377 @@
+/**
+ * Generate S3 供应协同业务需求描述.docx
+ * 严格按照 S1 产销协同业务需求描述.docx 的格式:
+ *   标题 → 文档信息 → 目录 → (一)模块功能说明+业务描述 → ...
+ */
+const fs = require('fs');
+const {
+  Document, Packer, Paragraph, TextRun, HeadingLevel,
+  AlignmentType, PageBreak, TableOfContents, convertInchesToTwip,
+} = require('docx');
+
+const FONT = '微软雅黑';
+
+// ── 辅助函数 ──
+function empty() { return new Paragraph({ spacing: { after: 80 }, children: [] }); }
+function pageBreak() { return new Paragraph({ children: [new PageBreak()] }); }
+
+function normalPara(text, opts = {}) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT, ...opts })],
+    spacing: { after: 100, line: 360 },
+    indent: opts.indent ? { left: convertInchesToTwip(opts.indent) } : undefined,
+  });
+}
+
+// H2: (一) 物料需求计划
+function sectionHeading(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 400, after: 200 },
+  });
+}
+
+// H3: 功能说明 / 业务描述
+function subHeading(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 22, font: FONT })],
+    heading: HeadingLevel.HEADING_3,
+    spacing: { before: 200, after: 100 },
+  });
+}
+
+// Title paragraph
+function titlePara(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 36, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER,
+    spacing: { after: 300 },
+  });
+}
+
+function docInfoPara(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT })],
+    spacing: { after: 40 },
+    bullet: { level: 0 },
+  });
+}
+
+// ═══════════════════════════════════════════════════
+// 各模块业务需求描述数据
+// ═══════════════════════════════════════════════════
+
+const CN_NUM = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二', '十三'];
+
+const modules = [
+  {
+    title: '物料需求计划',
+    funcDesc: [
+      '物料需求计划模块实现物料需求的在线管理,通过MDP同步外部系统工单需求、库存和采购在途数据。',
+      '利用MRP逻辑自动计算物料净需求(To-be-Scheduled Qty = MES Qty - Loc Qty - Scheduled Qty),生成可供发布的物料需求计划。',
+      '支持勾选发布和全部发布两种模式,发布后数据驱动下游物料交货计划和物料采购申请。',
+      '支持手动新增、编辑和删除物料需求记录,满足特殊场景下的灵活调整需求。',
+      '数据通过MDP作业(S3_MDP_SYNC_TRANSFORM)从ERP定时同步,计算结果存入DWD宽表层供看板消费。',
+    ],
+    bizDesc: [
+      '物料计划员登录DOP系统,进入"S3供应协同→物料需求计划"菜单,查看物料需求列表。',
+      '系统自动展示MRP净需求计算结果:物料编码、物料名称、MES需求量、库存量、在途量和待排产净需求。',
+      '通过物料编码、物料名称、需求日期等维度进行过滤查询,支持分页和列设置自定义显示字段。',
+      '点击工具栏"勾选发布"按钮,将选中的物料需求计划行发布为"已发布"状态;也可点击"全部发布"一键发布所有未发布记录。',
+      '已发布的计划可点击"取消发布"回退为未发布状态,重新参与后续计算和发布。',
+      '对于特殊场景,可手动新增物料需求记录、编辑已有记录或删除记录(删除操作需二次确认)。',
+    ],
+  },
+  {
+    title: '物料交货计划',
+    funcDesc: [
+      '物料交货计划模块基于已发布的物料需求计划自动生成交货计划,并驱动供应商端交货计划同步。',
+      '交货计划包含物料编码、供应商、计划交期等核心信息,作为供应商生产和交货的依据。',
+      '供应商交货状态变更后自动回传至计划端,实现供应商与计划端的双向信息同步。',
+      '每个交货计划行对应唯一的交货单号(dsnum),用于与供应商端交货单关联匹配。',
+    ],
+    bizDesc: [
+      '物料计划员进入"S3供应协同→物料交货计划"菜单,查看基于已发布物料需求计划自动生成的交货计划列表。',
+      '在列表中按物料编码、供应商、计划交期等条件筛选查询,查看各交货计划的执行情况。',
+      '交货计划生成后自动同步至供应商端,驱动供应商按计划安排生产和交货。',
+      '供应商交货状态变更时自动回传至计划端,交货计划列表实时反映最新的交货状态。',
+      '物料计划员可根据交货状态跟踪结果,对未完成的交货计划进行催办和协调。',
+    ],
+  },
+  {
+    title: '交货单异常记录',
+    funcDesc: [
+      '交货单异常记录模块提供交货过程中产生的异常记录的只读查询视图,帮助物料计划员和质量管理人员快速掌握交货异常情况。',
+      '系统自动比对交货计划与实际交货数据,逾期超过3个工作日或实收数量低于计划数量80%时自动标记为异常记录。',
+      '异常数据来源于MDP同步的交货计划和实际交货数据自动比对结果,支持按交货单号、供应商、异常类型、日期范围等多维筛选。',
+      '该页面为只读查询视图,不支持手动增删改操作,确保异常数据的客观性和可追溯性。',
+    ],
+    bizDesc: [
+      '物料计划员进入"S3供应协同→交货单异常记录"菜单,查看系统自动识别的交货异常记录列表。',
+      '通过交货单号、供应商名称、异常类型(逾期/短缺/质量)、计划日期范围等条件筛选查询。',
+      '在列表中查看每条异常记录的基本信息:关联交货单、物料编码、计划数量、实际数量、异常原因。',
+      '点击异常记录行查看明细信息,追溯异常来源和对生产计划的影响范围。',
+      '支持将当前查询结果导出为CSV/Excel文件,用于离线分析和供应商绩效评估。',
+      '质量管理员可通过该页面查看与质量相关的交货异常记录,进行质量追溯分析。',
+    ],
+  },
+  {
+    title: '要货令',
+    funcDesc: [
+      '要货令模块承接已批准物料采购申请驱动的要货指令(Demand Order)在线管理,作为采购执行的具体载体。',
+      '业务流程为:物料采购申请驱动 → 要货令创建 → 供应选择 → 物料选择 → 订单保存。',
+      '供应选择环节从货源清单动态加载下拉选择供应商和采购组,确保供应商资质合规。',
+      '保存后生成DO类型采购订单记录(PurOrdMaster/DO),数据同步至ERP系统。',
+    ],
+    bizDesc: [
+      '采购员进入"S3供应协同→要货令"菜单,接收已批准物料采购申请的驱动信号,启动要货令创建流程。',
+      '在要货令表单中填写核心信息:单号、采购组、部门、日期、合同编号等。',
+      '从货源清单动态加载下拉选择供应商和采购组,确保数据实时性和货源合规。',
+      '选择要货物料,填写需求数量、交货日期等明细信息。',
+      '确认无误后保存要货令,系统自动生成DO类型采购订单记录,数据同步至ERP。',
+      '已保存的要货令可在列表中查看、编辑和删除;删除操作需二次确认。',
+    ],
+  },
+  {
+    title: '物料采购申请',
+    funcDesc: [
+      '物料采购申请模块承接物料交货计划和MRP检查输出的采购需求,通过规范的申请-审批流程确保采购需求的合理性和合规性。',
+      '申请单提交后进入审批流,经指定审批人审核后状态更新为"已批准",审批过程可追溯、可审计。',
+      '批准后系统根据物料类型自动合并生成对应订单:DO物料→要货令,PO物料→采购订单,PW物料→委外加工订单。',
+      '已批准申请单不可编辑或删除,仅可查看,保护审批结果严肃性。',
+    ],
+    bizDesc: [
+      '采购员进入"S3供应协同→物料采购申请"菜单,查看采购申请列表,支持按申请单号、物料、供应商、状态多维度筛选。',
+      '基于物料交货计划或MRP检查结果点击"新增"按钮,填写申请单号、物料编码、供应商、需求数量、需求日期等必填字段。',
+      '申请单保存后可提交审批,提交后进入审批流,状态变为"审核中"。',
+      '审批人在审批列表中查看待审申请,执行"批准"或"驳回"操作;批准后状态更新为"已批准"。',
+      '已批准的申请单由系统自动按物料类型合并生成对应订单:DO物料→要货令,PO物料→采购订单,PW物料→委外加工订单。',
+      '已批准的申请单不可编辑或删除,仅可查看;未提交的申请可编辑或删除。',
+    ],
+  },
+  {
+    title: '物料采购订单',
+    funcDesc: [
+      '物料采购订单模块管理已下达至供应商的正式采购订单,跟踪订单从下达到收货完成的全生命周期。',
+      '业务流程为:采购订单生成 → 供应商确认/交期回复 → 发货关联 → 订单状态跟踪 → 收货完成/订单关闭。',
+      '供应商在线确认订单并回复预计交期,发货关联展示发货数量、日期与订单的对应关系。',
+      '收货数据累计达订单数量时状态自动更新为"已完成",已完成订单可手动关闭。',
+    ],
+    bizDesc: [
+      '采购员进入"S3供应协同→物料采购订单"菜单,查看采购订单列表,支持按订单号、供应商、物料、状态等条件筛选。',
+      '基于已批准采购申请自动或手动生成采购订单,填写供应商、物料、数量、交期等核心信息。',
+      '供应商在线确认订单并回复预计交期,确认信息回写至订单,采购员可在列表中查看交期回复状态。',
+      '关联发货单数据,在发货关联视图中展示发货数量、日期与订单的对应关系。',
+      '跟踪订单从"已下达→部分收货→已完成→已关闭"的完整生命周期状态。',
+      '收货数据累计达订单数量时系统自动更新为"已完成";已完成或已取消的订单可手动关闭,关闭后不可再收货。',
+    ],
+  },
+  {
+    title: '委外加工订单',
+    funcDesc: [
+      '委外加工订单模块管理委托外部供应商进行物料加工的采购订单(PurOrdMaster/PW类型)。',
+      '业务流程为:委外加工订单创建 → 委外商选择 → 货源清单物料选择 → 明细行管理 → 订单保存。',
+      '委外商和物料选择均需校验货源清单有效性,确保委外业务物料选择合规。',
+      '每个委外加工订单至少需包含一个明细行,不允许空订单;明细行支持增删改查操作。',
+    ],
+    bizDesc: [
+      '外协管理员进入"S3供应协同→委外加工订单"菜单,查看委外加工订单列表。',
+      '点击"新增"按钮创建委外加工订单,填写订单基本信息(订单号、委外商等)。',
+      '从货源清单选择委外加工供应商,系统校验供应商在货源清单中存在且状态有效。',
+      '从货源清单选择委外加工物料,管理每个明细行的物料、数量、单价、交货日期等。',
+      '确认无误后保存订单,生成PW类型采购订单记录,数据同步至ERP并与生产工单关联。',
+      '已保存的订单可在列表中查看、编辑和删除;支持通过子页面管理明细行的增删改查。',
+    ],
+  },
+  {
+    title: '工序外协订单',
+    funcDesc: [
+      '工序外协订单模块管理生产过程中特定工序委托外部供应商完成的采购订单。',
+      '业务流程为:选择生产工单 → 选择外协工序 → 创建外协订单 → 供应商/数量校验 → 订单保存/工单工序关联。',
+      '支持工单-工序-外协订单三级关联,同一工单同一工序不可重复创建外协订单,防止重复委外。',
+      '外协数量不可超过工单该工序的计划生产数量,外协商必须在货源清单中有效。',
+    ],
+    bizDesc: [
+      '外协管理员进入"S3供应协同→工序外协订单"菜单,查看工序外协订单列表。',
+      '从生产工单列表选择需要外协的生产工单,系统展示该工单的基本信息。',
+      '通过组件从工单中选择需要外协的工序,系统校验工序属于有效工单(状态不为已关闭或已取消)。',
+      '基于选中的工单和工序创建外协订单,填写供应商、数量、交期等信息。',
+      '系统校验外协商在货源清单中有效、外协数量不超过工序计划生产数量,同一工单同一工序不可重复创建。',
+      '保存订单后自动与生产工单工序建立关联,工单状态联动更新。',
+    ],
+  },
+  {
+    title: '供应协同看板',
+    funcDesc: [
+      '供应协同看板为管理层和业务人员提供S3模块的核心KPI可视化监控面板。',
+      '通过4个KPI卡片展示核心指标:物料计划周期、物料计划满足率、物料计划人效、物料库存周转天数。',
+      '采用DynamicModuleDashboard动态看板组件(module-code="S3"),支持多维筛选联动和分支看板视图切换。',
+      '分支一为MRP维度(BOM需求满足趋势+统计表),分支二为MDP维度(交货执行进度+满足率)。',
+      '支持策略模拟What-If分析和运营指标建模配置,KPI数据通过MDP定时或手动刷新。',
+    ],
+    bizDesc: [
+      '供应链经理进入"S3供应协同→供应协同看板"菜单,系统自动加载4个KPI指标卡片,直观展示当前供应协同健康度。',
+      '通过基础查询条件(日期、产品、订单号、产线、物料、供应商)筛选,看板所有指标数据自动联动刷新。',
+      '查看趋势箭头(上升绿色/下降红色),基于同比/环比计算,辅助管理层快速识别改善或恶化趋势。',
+      '切换至分支一MRP维度看板,查看BOM需求满足趋势和统计表;切换至分支二维MDP看板,查看交货执行进度和满足率。',
+      '点击"策略模拟"按钮进行What-If分析,模拟调整参数查看对KPI的影响;点击"导出"生成分析报告。',
+      '点击"运营指标建模"链接跳转至指标配置页面,自定义KPI口径和计算规则。',
+    ],
+  },
+  {
+    title: '工单物料齐套上线看板',
+    funcDesc: [
+      '工单物料齐套上线看板以工单为维度,展示每个工单所需物料的齐套状态(需求、发料、欠料、备料、在检、在途、承诺数量)。',
+      '数据来源于WorkOrdDetailTotalKB宽表,通过MRP计算工单物料齐套情况,展示30+字段完整齐套信息。',
+      '帮助生产计划和物料计划人员精准识别缺料工单和责任供应商(供应商编码/名称/采购组/采购员)。',
+      '支持列设置自定义显示/隐藏列、多字段排序和分页(10/20/50条每页),优化大数据量页面性能。',
+    ],
+    bizDesc: [
+      '物料计划员进入"S3供应协同→工单物料齐套上线看板"菜单,查看工单物料齐套数据列表。',
+      '通过订单号、工单号、产品编码、物料编码、供应商名称等条件筛选查询。',
+      '在列表中查看30+字段的齐套信息:序号、订单/工单号、BOM版本、物料描述、需求数量、发料数量、欠料数量、备料数量、在检数量、在途数量等。',
+      '欠料数量>0的物料行自动标记为"未齐套",通过供应商编码/名称/采购组/采购员快速定位责任供应商。',
+      '点击"刷新"按钮调用refreshWorkOrderMaterialReadiness接口重新执行MRP齐套计算,获取最新数据。',
+      '自定义显示/隐藏列配置和多字段升序/降序排序,满足不同角色的个性化视图需求。',
+    ],
+  },
+  {
+    title: 'MDP 运行监控',
+    funcDesc: [
+      'MDP运行监控模块提供S3相关MDP数据同步与转换任务的运行状态可视化。',
+      '展示S3模块相关的MDP同步任务列表:任务名称、运行状态(成功/失败/运行中)、开始/结束时间、处理记录数和错误信息。',
+      '失败任务红色高亮标识和告警提示,帮助IT运维人员及时发现和定位数据同步失败、转换异常等问题。',
+      '与平台级MDP监控形成"总-分"监控体系:S3页面聚焦本模块任务实例,平台级监控覆盖所有模块。',
+    ],
+    bizDesc: [
+      'IT运维人员进入"S3供应协同→MDP运行监控"菜单,查看S3模块相关的MDP同步任务列表。',
+      '在列表中查看每个任务的运行状态:任务名称、最近一次执行状态(成功/失败/运行中)、开始时间、结束时间、处理记录数。',
+      '按任务状态(全部/成功/失败/运行中)过滤查看,快速定位异常任务。',
+      '失败任务以红色高亮标识,点击可查看错误信息详情,便于及时响应和修复。',
+      '供应链经理和物料计划员也可通过该页面了解数据同步状态,确认数据时效性。',
+    ],
+  },
+  {
+    title: '数据中台架构',
+    funcDesc: [
+      'S3供应协同模块采用Ai-DOP统一数据中台分层架构(STG→STD→DWD→KPI),确保前端页面、看板展示和KPI考核的数据口径一致。',
+      '贴源层(STG/mdp_stg_*)保留源系统原始数据;标准层(STD/mdp_std_*)统一字段口径并清洗脏数据;宽表层(DWD/dwd_*)构建面向页面和KPI的主题事实表;指标层(KPI/ado_s9_kpi_value_*)按天存储L1-L4指标值。',
+      'MDP作业(S3_MDP_SYNC_TRANSFORM)负责全链路同步转换,支持手动触发和定时调度,作业日志可观测、异常可追溯。',
+      '核心数据链路包括:物料需求/计划链路、采购订单/交货链路、齐套/看板链路。',
+    ],
+    bizDesc: [
+      'S3模块的业务数据通过MDP作业自动同步至数据中台各层,各页面统一消费DWD/KPI层数据。',
+      '物料需求/计划链路:work_order_demand/inventory_snapshot/srm_in_transit → mdp_stg_demand贴源 → mdp_std_demand标准 → dwd_demand_schedule宽表 → KPI计算。',
+      '采购订单/交货链路:PurOrdMaster/srm_polist_ds/vscm_jhjh → mdp_stg_po贴源 → mdp_std_po标准 → dwd_po_delivery宽表 → KPI计算。',
+      '齐套/看板链路:WorkOrdDetailTotalKB/homeS3 → DWD直接消费 → 前端看板和KPI卡片 → ado_s9_kpi_value_*。',
+      '各页面刷新按钮和计划联动手动触发可即时获取最新同步数据;系统管理员通过MDP监控页面查看作业执行状态和处理日志。',
+    ],
+  },
+  {
+    title: '权限管理汇总',
+    funcDesc: [
+      'S3供应协同模块涉及以下核心角色和权限:',
+      '物料计划员:查看和操作物料需求计划、交货计划;发布/取消发布;查看交货异常记录;刷新齐套看板。',
+      '采购员:增删改查要货令、采购申请、采购订单;查看交货计划和交货异常记录。',
+      '外协管理员:增删改查委外加工订单和工序外协订单。',
+      '供应链经理:查看所有模块;查看供应协同看板和管理KPI;导出报表;策略模拟。',
+      '审批人(采购经理):审核采购申请的审批流。',
+      'IT运维人员:查看MDP运行监控;监控同步任务状态。',
+      '质量管理员:查看交货异常记录(质量追溯)。',
+      '仓库管理员:查看采购订单和交货计划(安排收货)。',
+    ],
+  },
+];
+
+// ═══════════════════════════════════════════════════
+// 生成文档
+// ═══════════════════════════════════════════════════
+function buildDocument() {
+  const sections = [];
+
+  // ── 封面/标题 ──
+  sections.push(empty(), empty(), empty(), empty());
+  sections.push(titlePara('供应协同业务需求描述'));
+  sections.push(empty());
+
+  // ── 文档信息 ──
+  const infoItems = [
+    '文档作者:彭熙玉',
+    '创建日期:2026-06-10',
+    '更新日期:2026-06-10',
+    '文档编号:DOP-S3-REQ-001',
+    '当前版本:V1.0',
+  ];
+  infoItems.forEach(item => sections.push(docInfoPara(item)));
+  sections.push(empty(), empty());
+
+  // ── 目录 ──
+  sections.push(pageBreak());
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '目录', bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 200, after: 300 },
+  }));
+  sections.push(new TableOfContents('目录', {
+    headingStyleRange: '2-3',
+    hyperlink: true,
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '(如目录未显示,请在 Word 中右键此处 → 更新域)', size: 18, font: FONT, italics: true, color: '888888' })],
+    spacing: { before: 40, after: 200 },
+  }));
+
+  // ── 各功能模块 ──
+  for (let i = 0; i < modules.length; i++) {
+    const mod = modules[i];
+    sections.push(pageBreak());
+
+    // 章节标题:(一) 物料需求计划
+    sections.push(sectionHeading(`(${CN_NUM[i + 1]})${mod.title}`));
+
+    // 功能说明
+    sections.push(subHeading('功能说明'));
+    (mod.funcDesc || []).forEach(desc => sections.push(normalPara(desc)));
+
+    // 业务描述
+    if (mod.bizDesc && mod.bizDesc.length) {
+      sections.push(subHeading('业务描述'));
+      mod.bizDesc.forEach(desc => sections.push(normalPara(desc)));
+    }
+  }
+
+  return sections;
+}
+
+// ═══════════════════════════════════════════════════
+// 主流程
+// ═══════════════════════════════════════════════════
+async function main() {
+  const sections = buildDocument();
+
+  const doc = new Document({
+    styles: { default: { document: { run: { font: FONT, size: 21 } } } },
+    sections: [{
+      properties: { page: { margin: { top: convertInchesToTwip(0.8), bottom: convertInchesToTwip(0.8), left: convertInchesToTwip(1.2), right: convertInchesToTwip(1.2) } } },
+      children: sections,
+    }],
+  });
+
+  const buffer = await Packer.toBuffer(doc);
+  const outPath = 'd:\\DEMONET\\doc\\S3供应协同业务需求描述.docx';
+  const fallbackPath = 'd:\\DEMONET\\doc\\S3供应协同业务需求描述_V2.docx';
+  const tempPath = 'd:\\DEMONET\\doc\\S3_req_temp.docx';
+  fs.writeFileSync(tempPath, buffer);
+
+  try { fs.unlinkSync(outPath); } catch(e) {}
+  try { fs.renameSync(tempPath, outPath); console.log(`Renamed to: ${outPath}`); }
+    catch(e) {
+      try { fs.unlinkSync(fallbackPath); } catch(_) {}
+      try { fs.renameSync(tempPath, fallbackPath); console.log(`Fallback to: ${fallbackPath}`); }
+        catch(e2) { console.log(`Generated: ${tempPath} (target locked)`); }
+    }
+  console.log(`Size: ${(buffer.length / 1024).toFixed(1)} KB`);
+}
+
+main().catch(err => { console.error('Error:', err); process.exit(1); });

+ 902 - 0
doc/generate_s4_blueprint_docx.js

@@ -0,0 +1,902 @@
+/**
+ * Generate S4 采购执行模块蓝图设计方案.docx
+ * 参照 generate_s1_blueprint_docx.js 模板格式
+ *   章节结构: 封面 → 文档控制 → TOC → Ch1 总体方案 → Ch2~7 功能域(各8子节) → Ch8 数据中台 → Ch9 权限汇总
+ */
+const fs = require('fs');
+const sharp = require('sharp');
+const {
+  Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
+  HeadingLevel, AlignmentType, BorderStyle, WidthType, ShadingType,
+  convertInchesToTwip, PageBreak, ImageRun, TabStopType, TabStopPosition,
+  TableOfContents
+} = require('docx');
+
+// ═══════════════════════════════════════════════════
+// 通用样式与辅助函数 (from S1 template)
+// ═══════════════════════════════════════════════════
+const FONT = '微软雅黑';
+const thinBorder = {
+  top: { style: BorderStyle.SINGLE, size: 1 },
+  bottom: { style: BorderStyle.SINGLE, size: 1 },
+  left: { style: BorderStyle.SINGLE, size: 1 },
+  right: { style: BorderStyle.SINGLE, size: 1 },
+};
+const headerShading = { fill: '4472C4', type: ShadingType.CLEAR };
+
+function p(text, opts = {}) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT, ...opts })],
+    spacing: { after: 120, line: 360 },
+  });
+}
+function bp(text) { return p(text, { bold: true }); }
+function empty() { return new Paragraph({ spacing: { after: 80 }, children: [] }); }
+function pageBreak() { return new Paragraph({ children: [new PageBreak()] }); }
+
+function h1(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 32, font: FONT })],
+    heading: HeadingLevel.HEADING_1,
+    spacing: { before: 400, after: 200 },
+  });
+}
+function h2(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 300, after: 150 },
+  });
+}
+function h3(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 24, font: FONT })],
+    heading: HeadingLevel.HEADING_3,
+    spacing: { before: 200, after: 100 },
+  });
+}
+function bullet(text, indent = 0) {
+  return new Paragraph({
+    children: [new TextRun({ text: `  ${text}`, size: 21, font: FONT })],
+    spacing: { after: 60, line: 340 },
+    indent: { left: 300 + indent * 400 },
+  });
+}
+
+// Table helpers
+function hdrCell(text, w = 1800) {
+  return new TableCell({
+    children: [new Paragraph({ children: [new TextRun({ text, bold: true, size: 20, font: FONT, color: 'FFFFFF' })], alignment: AlignmentType.CENTER })],
+    width: { size: w, type: WidthType.DXA }, borders: thinBorder, shading: headerShading,
+  });
+}
+function tc(text, w = 1800, opts = {}) {
+  return new TableCell({
+    children: [new Paragraph({ children: [new TextRun({ text: String(text ?? ''), size: 20, font: FONT })], alignment: opts.center ? AlignmentType.CENTER : AlignmentType.LEFT })],
+    width: { size: w, type: WidthType.DXA }, borders: thinBorder,
+  });
+}
+function table(headers, rows, widths) {
+  return new Table({
+    rows: [
+      new TableRow({ children: headers.map((h, i) => hdrCell(h, widths ? widths[i] : 1800)), tableHeader: true }),
+      ...rows.map(r => new TableRow({ children: r.map((c, i) => tc(c, widths ? widths[i] : 1800)) })),
+    ],
+    width: { size: 100, type: WidthType.PERCENTAGE },
+  });
+}
+
+function activityTable(rows) {
+  return table(['编号', '活动名称', '执行角色', '活动描述', '输入', '输出'], rows, [600, 1400, 1100, 3800, 1400, 1700]);
+}
+function ruleTable(rows) {
+  return table(['序列号', '业务情形', '描述/方案'], rows, [900, 2800, 6300]);
+}
+function interfaceTable(rows) {
+  return table(['序号', '接口名称', '接口说明', '频次及触发方式'], rows, [600, 2400, 4200, 2800]);
+}
+function reportTable(rows) {
+  return table(['序号', '名称', '描述', '方案'], rows, [600, 2600, 3600, 3200]);
+}
+function permTable(rows) {
+  return table(['序号', '岗位名称', '对应系统权限'], rows, [600, 2000, 7400]);
+}
+
+// ═══════════════════════════════════════════════════
+// 目录 (TOC)
+// ═══════════════════════════════════════════════════
+const TOC2_TAB = 2880;
+const TOC3_TAB = 3600;
+const TOC_FONT_SIZE = 20;
+
+function tocChapter(num, title) {
+  return new Paragraph({
+    children: [
+      new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: title, bold: true, size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
+    ],
+    tabStops: [
+      { type: TabStopType.LEFT, position: TOC2_TAB },
+      { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
+    ],
+    spacing: { before: 80, after: 40 },
+  });
+}
+function tocSub(num, title) {
+  return new Paragraph({
+    children: [
+      new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: title, size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
+      new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
+    ],
+    tabStops: [
+      { type: TabStopType.LEFT, position: TOC3_TAB },
+      { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
+    ],
+    spacing: { before: 30, after: 30 },
+  });
+}
+
+function buildTOC() {
+  const ch = [];
+  ch.push(new TableOfContents('目录', { headingStyleRange: '1-2', hyperlink: true }));
+  ch.push(new Paragraph({
+    children: [new TextRun({ text: '(如目录未显示,请在 Word 中右键此处 → 更新域)', size: 18, font: FONT, italics: true, color: '888888' })],
+    spacing: { before: 40, after: 200 },
+  }));
+
+  ch.push(tocChapter('', '文档控制'));
+  ch.push(tocSub('', '更改记录'));
+  ch.push(tocSub('', '审核'));
+  ch.push(tocSub('', '发布'));
+  ch.push(tocChapter('', '目录'));
+
+  ch.push(tocChapter('1', '总体业务方案'));
+  ch.push(tocSub('1.1', '目标和宗旨'));
+  ch.push(tocSub('1.2', '总体业务流程图'));
+  ch.push(tocSub('1.3', '方案设计'));
+
+  const chSubs = [
+    '目标/宗旨', '业务流程图', '业务流程说明', '业务流程规则',
+    '变化和改进点', '权限管理需求', '系统接口集成', '报表需求',
+  ];
+  const chTitles = [
+    '供应商交货管理', '供应商发货单', '采购退货单', 'IQC退货查询',
+    '采购执行看板主页', '供应商欠料看板',
+  ];
+  for (let i = 0; i < chTitles.length; i++) {
+    const n = i + 2;
+    ch.push(tocChapter(String(n), chTitles[i]));
+    for (let j = 0; j < chSubs.length; j++) {
+      ch.push(tocSub(`${n}.${j + 1}`, chSubs[j]));
+    }
+  }
+
+  ch.push(tocChapter('8', '数据中台与MDP集成'));
+  ['目标/宗旨', '分层设计', 'S4核心数据流', '数据表清单', 'MDP作业调度'].forEach((title, i) => {
+    ch.push(tocSub(`8.${i + 1}`, title));
+  });
+
+  ch.push(tocChapter('9', '权限管理汇总'));
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 流程图生成 (S1 style: 纵向左角色+右节点)
+// ═══════════════════════════════════════════════════
+const FLOW_W = 680, BOX_W = 220, BOX_H = 42, GAP_Y = 28;
+const ROLE_X = 30, BOX_X = 130, MARGIN_Y = 40;
+const flowImageCache = {};
+
+function flowImageParagraph(pngBuffer) {
+  return new Paragraph({
+    children: [new ImageRun({ data: pngBuffer, transformation: { width: 540, height: 360 }, type: 'png' })],
+    alignment: AlignmentType.CENTER,
+    spacing: { before: 120, after: 120 },
+  });
+}
+
+async function generateFlowPNG(steps) {
+  const N = steps.length;
+  const svgH = MARGIN_Y * 2 + N * (BOX_H + GAP_Y) - GAP_Y;
+  const centerX = BOX_X + BOX_W / 2;
+
+  let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${FLOW_W}" height="${svgH}">`;
+  svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
+
+  steps.forEach((s, i) => {
+    const y = MARGIN_Y + i * (BOX_H + GAP_Y);
+    svg += `<text x="${ROLE_X}" y="${y + BOX_H / 2 + 5}" font-family="Microsoft YaHei" font-size="12" fill="#666" text-anchor="start">${s.role || ''}</text>`;
+    const isStart = i === 0, isEnd = i === N - 1;
+    const fill = isStart ? '#1565C0' : (isEnd ? '#2E7D32' : '#E3F2FD');
+    const stroke = isStart ? '#0D47A1' : (isEnd ? '#1B5E20' : '#1565C0');
+    const textColor = (isStart || isEnd) ? '#FFFFFF' : '#333333';
+    svg += `<rect x="${BOX_X}" y="${y}" width="${BOX_W}" height="${BOX_H}" rx="6" fill="${fill}" stroke="${stroke}" stroke-width="1.5"/>`;
+    svg += `<text x="${centerX}" y="${y + BOX_H / 2 + 5}" font-family="Microsoft YaHei" font-size="13" fill="${textColor}" text-anchor="middle">${s.label}</text>`;
+    if (i < N - 1) {
+      const ay = y + BOX_H + 4, by2 = ay + GAP_Y - 8;
+      svg += `<line x1="${centerX}" y1="${ay}" x2="${centerX}" y2="${by2}" stroke="#888" stroke-width="1.5"/>`;
+      svg += `<polygon points="${centerX - 5},${by2 - 10} ${centerX + 5},${by2 - 10} ${centerX},${by2}" fill="#888"/>`;
+    }
+  });
+
+  svg += '</svg>';
+  return sharp(Buffer.from(svg)).png().toBuffer();
+}
+
+// 第1章专用:S4 总体业务流程图(交货执行 → 退货闭环 → 看板监控)
+async function generateOverviewFlowPNG() {
+  const W = 780, H = 540;
+  let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}">`;
+  svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
+  svg += `<text x="${W/2}" y="28" font-family="Microsoft YaHei" font-size="15" font-weight="bold" fill="#1F4E79" text-anchor="middle">S4 采购执行 — 总体业务流程</text>`;
+
+  const zones = [
+    { y: 42, h: 160, color: '#E8F0FE', label: '交货执行域(计划发布→交货计划发布→交期回复→生成发货单→在途跟踪)', lx: 10, ly: 60 },
+    { y: 210, h: 130, color: '#FFF3E0', label: '退货闭环域(IQC检验→判定→退货出库)', lx: 10, ly: 228 },
+    { y: 348, h: 140, color: '#E8F5E9', label: '采购执行看板域(KPI监控→欠料预测→数据中台)', lx: 10, ly: 366 },
+  ];
+  zones.forEach(z => {
+    svg += `<rect x="8" y="${z.y}" width="${W-16}" height="${z.h}" rx="6" fill="${z.color}" stroke="#ccc" stroke-width="0.5" stroke-dasharray="4,3"/>`;
+    svg += `<text x="${z.lx}" y="${z.ly}" font-family="Microsoft YaHei" font-size="12" font-weight="bold" fill="#555">${z.label}</text>`;
+  });
+
+  // 交货执行域节点
+  const deNodes = [
+    { x: 30, y: 85, w: 130, label: '物料交货\n计划发布', color: '#1565C0' },
+    { x: 185, y: 85, w: 130, label: '供应商交货\n计划发布', color: '#1565C0' },
+    { x: 340, y: 85, w: 120, label: '供应商\n交期回复', color: '#1565C0' },
+    { x: 485, y: 85, w: 120, label: '供应商\n生成发货单', color: '#2E7D32' },
+    { x: 330, y: 145, w: 130, label: '在途/在检\n入库跟踪', color: '#2E7D32' },
+  ];
+  deNodes.forEach(n => {
+    const fill = n.color;
+    const stroke = n.color;
+    const tc = '#fff';
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="36" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + 18;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="${tc}" text-anchor="middle">${l}</text>`;
+    });
+  });
+  svg += `<line x1="160" y1="103" x2="183" y2="103" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="183,98 183,108 190,103" fill="#666"/>`;
+  svg += `<line x1="315" y1="103" x2="338" y2="103" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="338,98 338,108 345,103" fill="#666"/>`;
+  svg += `<line x1="460" y1="103" x2="483" y2="103" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="483,98 483,108 490,103" fill="#666"/>`;
+  svg += `<line x1="545" y1="121" x2="395" y2="145" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="399,140 399,150 392,145" fill="#666"/>`;
+
+  // 退货闭环域节点
+  const rnNodes = [
+    { x: 30, y: 250, w: 130, label: 'IQC来料检验', color: '#E65100' },
+    { x: 200, y: 250, w: 130, label: '不合格判定\n(退货/让步/挑选)', color: '#E65100' },
+    { x: 370, y: 250, w: 130, label: '退货单创建', color: '#B71C1C' },
+    { x: 540, y: 250, w: 130, label: '退货出库', color: '#B71C1C' },
+  ];
+  rnNodes.forEach(n => {
+    const isRed = n.color === '#B71C1C';
+    const fill = isRed ? '#FFEBEE' : n.color;
+    const stroke = isRed ? '#C62828' : n.color;
+    const tc = isRed ? '#333' : '#fff';
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="42" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + 21;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="${tc}" text-anchor="middle">${l}</text>`;
+    });
+  });
+  svg += `<line x1="160" y1="271" x2="200" y2="271" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="196,266 196,276 203,271" fill="#666"/>`;
+  svg += `<line x1="330" y1="271" x2="370" y2="271" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="366,266 366,276 373,271" fill="#666"/>`;
+  svg += `<line x1="500" y1="271" x2="540" y2="271" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="536,266 536,276 543,271" fill="#666"/>`;
+
+  // 看板域节点
+  const kbNodes = [
+    { x: 30, y: 395, w: 150, label: 'MDP数据同步\n(KPI指标计算引擎)', color: '#2E7D32' },
+    { x: 220, y: 395, w: 130, label: '采购执行看板\n(双面板布局)', color: '#2E7D32' },
+    { x: 390, y: 395, w: 130, label: '供应商欠料看板\n(15天滚动预测)', color: '#0D47A1' },
+    { x: 560, y: 395, w: 130, label: '导出报告\n+运营指标建模', color: '#B71C1C' },
+  ];
+  kbNodes.forEach(n => {
+    const isRed = n.color === '#B71C1C';
+    const fill = isRed ? '#FFEBEE' : n.color;
+    const stroke = isRed ? '#C62828' : n.color;
+    const tc = (n.color === '#2E7D32' || n.color === '#0D47A1') ? '#fff' : '#333';
+    svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="48" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
+    const lines = n.label.split('\n');
+    const cy = n.y + 24;
+    lines.forEach((l, li) => {
+      const off = (lines.length - 1) * -7 + li * 14;
+      svg += `<text x="${n.x + n.w/2}" y="${cy + off + 4}" font-family="Microsoft YaHei" font-size="10" fill="${tc}" text-anchor="middle">${l}</text>`;
+    });
+  });
+  svg += `<line x1="180" y1="419" x2="220" y2="419" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="216,414 216,424 223,419" fill="#666"/>`;
+  svg += `<line x1="350" y1="419" x2="390" y2="419" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="386,414 386,424 393,419" fill="#666"/>`;
+  svg += `<line x1="520" y1="419" x2="560" y2="419" stroke="#666" stroke-width="1.2"/>`;
+  svg += `<polygon points="556,414 556,424 563,419" fill="#666"/>`;
+
+  // 串联标注
+  svg += `<text x="150" y="202" font-family="Microsoft YaHei" font-size="10" fill="#888" text-anchor="middle">↓ 交货数据(含发货单)作为采购执行绩效数据源 ↓</text>`;
+  svg += `<text x="150" y="340" font-family="Microsoft YaHei" font-size="10" fill="#888" text-anchor="middle">↓ 退货单数据反馈至供应商绩效评估 ↓</text>`;
+
+  svg += `<text x="50" y="498" font-family="Microsoft YaHei" font-size="9" fill="#999">数据链路: ERP/SRM/MES → MDP入站 → ODS(STG) → STD → DWD宽表 → KPI计算 → 看板消费</text>`;
+
+  svg += '</svg>';
+  return sharp(Buffer.from(svg)).png().toBuffer();
+}
+
+// ═══════════════════════════════════════════════════
+// 章节构建函数:8 子章节
+// ═══════════════════════════════════════════════════
+function buildChapter(num, title, cfg) {
+  const ch = [];
+  const pre = `${num}.`;
+
+  ch.push(h2(`${pre}1  目标/宗旨`));
+  (cfg.targets || []).forEach(t => ch.push(bullet(t)));
+
+  ch.push(h2(`${pre}2  业务流程图`));
+  const img = cfg.flowImage || (cfg.flowSteps ? flowImageCache[num] : null);
+  if (img) {
+    ch.push(flowImageParagraph(img));
+  } else {
+    ch.push(p('(见系统操作流程图 / 附件 Visio 流程图)'));
+  }
+  if (cfg.flowNote) ch.push(p(cfg.flowNote));
+
+  ch.push(h2(`${pre}3  业务流程说明`));
+  if (cfg.activities && cfg.activities.length) {
+    ch.push(activityTable(cfg.activities));
+  } else {
+    ch.push(p('参照系统页面操作流程,主要操作步骤包含:'));
+    (cfg.activityDesc || []).forEach(a => ch.push(bullet(a)));
+  }
+
+  ch.push(h2(`${pre}4  业务流程规则`));
+  if (cfg.rules && cfg.rules.length) {
+    ch.push(ruleTable(cfg.rules));
+  } else {
+    ch.push(p('无特殊业务规则'));
+  }
+
+  ch.push(h2(`${pre}5  变化和改进点`));
+  (cfg.improvements || []).forEach(i => ch.push(bullet(i)));
+
+  ch.push(h2(`${pre}6  权限管理需求`));
+  if (cfg.permissions && cfg.permissions.length) {
+    ch.push(permTable(cfg.permissions));
+  } else {
+    ch.push(p('参照第 9 章 权限管理汇总'));
+  }
+
+  ch.push(h2(`${pre}7  系统接口集成`));
+  if (cfg.interfaces && cfg.interfaces.length) {
+    ch.push(interfaceTable(cfg.interfaces));
+  } else {
+    ch.push(p('无外部系统接口;页面数据通过 Admin.NET 内置 API 获取'));
+  }
+
+  ch.push(h2(`${pre}8  报表需求`));
+  if (cfg.reports && cfg.reports.length) {
+    ch.push(reportTable(cfg.reports));
+  } else {
+    ch.push(p('无独立报表需求,相关数据通过看板和列表页面查看'));
+  }
+
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 章节数据配置
+// ═══════════════════════════════════════════════════
+
+// ── Chapter 2: 供应商交货管理 ──
+const _ch2cfg = {
+  targets: [
+    '管理采购订单下达后的供应商交货执行过程,通过交货计划发布驱动供应商交期回复和发货单生成。',
+    '支持供应商进行交期回复、生成发货单,跟踪在途、在检、入库全链路状态。',
+  ],
+  flowNote: '流程路径:供应商交货计划发布 → 供应商交期回复 → 生成发货单 → 在途/在检/入库跟踪。',
+  flowSteps: [
+    { label: '供应商交货计划发布', role: '采购跟单员' },
+    { label: '供应商交期回复', role: '采购跟单员' },
+    { label: '生成发货单', role: '采购跟单员' },
+    { label: '在途/在检/入库跟踪', role: '系统自动' },
+  ],
+  activities: [
+    ['10', '计划发布', '采购跟单员', '从MDP同步的交货计划中勾选发布至供应商,要求所选属同一供应商', 'MDP交货计划', '已发布交货计划'],
+    ['20', '交期回复', '采购跟单员', '支持"按计划日期回复"(批量)和"回复交期"(手动选择)两种方式', '已发布记录', '交期已确认'],
+    ['30', '生成发货单', '采购跟单员', '基于勾选交货记录,在弹窗中嵌入发货单表单生成发货单', '交货记录', '发货单'],
+    ['40', '状态跟踪', '采购跟单员', '跟踪在途、在检、入库全链路状态,支持多维度筛选/排序/列设置', '发货数据', '状态视图'],
+  ],
+  rules: [
+    ['1', '计划发布规则', '勾选发布时所有选中记录必须属于同一供应商'],
+    ['2', '交期回复方式', '"按计划日期回复"为批量快速回复;"回复交期"为手动单日期回复'],
+    ['3', '发货单生成', '基于已回复交期的交货记录生成发货单,嵌入式弹窗表单完成'],
+    ['4', '状态跟踪', '系统自动同步在途/在检/入库状态,支持20个字段列设置'],
+  ],
+  improvements: [
+    '从原系统手工Excel跟踪升级为在线交货管理,支持多维度筛选和批量操作。',
+    '新增勾选发布和两种交期回复方式,替代原系统逐单操作低效方式。',
+    '新增嵌入式发货单生成,在单个页面内完成"发布→回复→发货单"全流程。',
+    '新增列设置功能,支持20个字段的自定义显示配置。',
+  ],
+  permissions: [
+    ['1', '采购跟单员', '增删改查/发布/交期回复/生成发货单/关闭/导出'],
+    ['2', '采购经理/仓库管理员', '查看权限'],
+  ],
+  interfaces: [
+    ['1', 'MDP入站-采购订单', '从ERP同步采购订单数据,经DWD层转换为srm_polist_ds', '定时(日)'],
+    ['2', 'MDP出站-交货发布', '发布后交货数据同步至SRM系统供供应商查看回复', '发布时触发'],
+    ['3', 'CSV导出', '前端调用导出API生成CSV文件', '手动触发'],
+  ],
+  reports: [
+    ['1', '交货执行报表', '按供应商/物料统计交货计划vs实际差异', '按周/月'],
+    ['2', '交货单状态统计', '按状态(已发布/已回复/已发货/已入库)汇总', '按日/周'],
+  ],
+};
+const ch2 = () => buildChapter('2', '供应商交货管理', _ch2cfg);
+
+// ── Chapter 3: 供应商发货单 ──
+const _ch3cfg = {
+  targets: [
+    '管理从交货管理生成的供应商发货单,支持标签生成、打印、附件管理和供应商发货跟踪。',
+    '覆盖从发货单生成到供应商发货、入库关联的全链路操作。',
+  ],
+  flowNote: '流程路径:交货管理生成发货单 → 生成标签 → 上传附件 → 打印标签 → 供应商发货 → 打印发货单 → 入库关联。',
+  flowSteps: [
+    { label: '交货管理生成发货单', role: '采购跟单员' },
+    { label: '生成标签', role: '采购跟单员' },
+    { label: '上传附件', role: '采购跟单员' },
+    { label: '打印标签', role: '采购跟单员' },
+    { label: '供应商发货', role: '供应商' },
+    { label: '打印发货单', role: '采购跟单员' },
+    { label: '入库关联', role: '系统自动' },
+  ],
+  activities: [
+    ['10', '生成发货单', '采购跟单员', '从交货管理页面勾选交货记录后一键生成发货单', '交货记录', '发货单'],
+    ['20', '生成标签', '采购跟单员', '基于发货单信息生成物流标签(含物料编码、数量、供应商等)', '发货单', '标签'],
+    ['30', '上传附件', '采购跟单员', '上传发货单相关附件(签收单、运输单、质检报告等)', '附件文件', '已关联附件'],
+    ['40', '打印标签', '采购跟单员', '打印物流标签,粘贴于货物外包装用于物流识别', '标签', '打印标签'],
+    ['50', '供应商发货', '供应商', '供应商按发货单要求进行实物发货', '发货单/标签', '已发货货物'],
+    ['60', '打印发货单', '采购跟单员', '打印正式发货单据,作为运输和收货凭证', '发货单', '纸质发货单'],
+    ['70', '入库关联', '系统', '收货数据入库后自动关联发货单并更新状态', '收货数据', '入库关联'],
+  ],
+  rules: [
+    ['1', '发货单来源', '发货单仅由交货管理的"生成发货单"操作产生,不允许手动独立创建'],
+    ['2', '标签规则', '标签基于发货单信息自动生成,含物料编码、数量、供应商等关键信息'],
+    ['3', '附件管理', '支持多格式附件上传,大小限制按系统配置'],
+    ['4', '打印控制', '标签和发货单均支持打印功能,打印前需校验发货单状态有效'],
+  ],
+  improvements: [
+    '从原系统纸质发货单管理升级为在线管理,新增标签生成和打印功能。',
+    '新增标签生成和打印环节,替代原系统手工标签制作。',
+    '新增供应商发货状态跟踪,实现从生成到入库的全链路可视化。',
+    '新增与入库数据的自动关联,发货单状态随入库数据同步更新。',
+  ],
+  permissions: [
+    ['1', '采购跟单员', '增删改查/生成标签/上传附件/打印标签/打印发货单'],
+    ['2', '仓库管理员', '查看权限'],
+  ],
+  interfaces: [
+    ['1', '发货单数据同步', 'SupplierShipment表数据与ERP/SRM双向同步', '定时(日)'],
+    ['2', '入库数据关联', '收货入库后自动关联发货单并更新状态', '入库时触发'],
+    ['3', '核心数据表', 'SupplierShipment(供应商发货单数据)', '实时'],
+  ],
+  reports: [
+    ['1', '发货单明细报表', '按供应商/时间段统计发货单生成、打印和发货数量', '按周/月'],
+  ],
+};
+const ch3 = () => buildChapter('3', '供应商发货单', _ch3cfg);
+
+// ── Chapter 4: 采购退货单 ──
+const _ch4cfg = {
+  targets: [
+    '处理来料检验不合格后的退货流程,管理采购退货单据的创建、出库和状态跟踪。',
+    '关联原始采购订单和不合格检验记录,确保退货流程的可追溯性。',
+  ],
+  flowNote: '流程路径:IQC检验不合格判定 → 退货单创建 → 退货出库 → 状态跟踪(新建→已出库→已完成)。',
+  flowSteps: [
+    { label: 'IQC检验不合格判定', role: '质量管理员' },
+    { label: '退货单创建', role: '质量管理员' },
+    { label: '退货出库', role: '仓库管理员' },
+    { label: '状态跟踪(新建→已出库→已完成)', role: '系统自动' },
+  ],
+  activities: [
+    ['10', '不合格判定', '质量管理员', 'IQC来料检验判定不合格,触发退货流程', 'IQC检验结果', '不合格判定'],
+    ['20', '退货单创建', '质量管理员', '基于不合格判定创建退货单,关联原始采购订单和检验记录', '不合格判定', '退货单'],
+    ['30', '退货出库', '仓库管理员', '执行退货出库操作,更新库存数据', '退货单', '出库记录'],
+    ['40', '状态跟踪', '系统', '跟踪退货单处理进度:新建→已出库→已完成', '退货单数据', '状态更新'],
+  ],
+  rules: [
+    ['1', '退货单来源', '退货单必须关联原始采购订单和不合格IQC检验记录'],
+    ['2', '状态流转', '新建→已出库→已完成顺序流转,不可跳转'],
+    ['3', '出库校验', '退货出库前需校验库存数量充足;出库后自动扣减库存'],
+    ['4', '不可删除', '已出库的退货单不可删除,确保退货记录的完整性'],
+  ],
+  improvements: [
+    '从原系统纸质退货流程升级为在线管理,与IQC检验结果自动联动。',
+    '新增退货单状态生命周期管理,替代原系统简单"退货/不退货"二元状态。',
+    '新增与采购订单的关联追溯,退货原因可反馈至供应商绩效评估。',
+  ],
+  permissions: [
+    ['1', '质量管理员', '增删改查/创建退货单'],
+    ['2', '仓库管理员', '查看/退货出库操作'],
+    ['3', '采购经理', '查看权限'],
+  ],
+  interfaces: [
+    ['1', 'IQC数据同步', '从IQC系统同步来料检验结果和不合格判定数据', '定时(日)'],
+    ['2', '库存同步', '退货出库后同步扣减ERP库存数据', '出库时触发'],
+    ['3', '核心数据表', 'PurchaseReturnOrder(采购退货单主表)', '实时'],
+  ],
+  reports: [
+    ['1', '退货统计报表', '按供应商/物料/退货原因统计退货数量和金额', '按周/月'],
+    ['2', '退货处理时效报表', '统计退货从创建到完成的平均处理时长', '按周/月'],
+  ],
+};
+const ch4 = () => buildChapter('4', '采购退货单', _ch4cfg);
+
+// ── Chapter 5: IQC退货查询 ──
+const _ch5cfg = {
+  targets: [
+    '提供只读的退货历史查询功能,面向质量管理人员查看IQC检验不合格后的退货处理记录。',
+    '展示退货详情,包括检验结果、不合格原因、处理方式等完整信息。',
+  ],
+  flowNote: '流程路径:IQC检验记录生成 → 不合格判定/退货关联 → 退货查询(检验单/物料/日期)。',
+  flowSteps: [
+    { label: 'IQC检验记录生成', role: '系统(MDP)' },
+    { label: '不合格判定/退货关联', role: '质量管理员' },
+    { label: '退货查询(检验单/物料/日期)', role: '质量管理员' },
+  ],
+  activities: [
+    ['10', '检验记录生成', '系统(MDP)', '从IQC系统同步检验记录,生成检验数据', 'IQC检验数据', '检验记录'],
+    ['20', '不合格判定/退货关联', '质量管理员', '对不合格检验结果进行判定并关联退货记录', '检验记录', '不合格判定/退货关联'],
+    ['30', '退货查询', '质量管理员', '按检验单号、物料编码、日期范围查询退货记录和详情', '筛选条件', '退货查询结果'],
+  ],
+  rules: [
+    ['1', '只读访问', '该页面为只读查询视图,不支持增删改操作,确保质量记录完整性'],
+    ['2', '数据来源', '退货记录来自IQC检验系统的不合格判定和退货关联数据'],
+    ['3', '可追溯性', '每条退货记录可关联到原始IQC检验单和采购订单'],
+  ],
+  improvements: [
+    '从原系统纸质翻查升级为在线只读查询,检索效率大幅提升。',
+    '新增检验结果、不合格原因、处理方式的结构化展示。',
+    '退货记录与采购订单和供应商绩效关联,支持供应商质量评级。',
+  ],
+  permissions: [
+    ['1', '质量管理员', '查看全部退货记录'],
+    ['2', '采购经理/仓库管理员', '查看权限'],
+  ],
+  interfaces: [
+    ['1', 'IQC数据同步', '从IQC系统同步检验结果和退货判定数据', '定时(日)'],
+    ['2', '核心数据表', 'IQCReturnQuery(IQC退货查询视图)', '实时'],
+  ],
+  reports: [
+    ['1', 'IQC退货分析报告', '按供应商/物料/不合格原因统计退货趋势', '按周/月'],
+  ],
+};
+const ch5 = () => buildChapter('5', 'IQC退货查询', _ch5cfg);
+
+// ── Chapter 6: 采购执行看板主页 ──
+const _ch6cfg = {
+  targets: [
+    '通过可视化看板展示S4采购执行四大L1核心指标,帮助管理层全面掌握采购执行健康度。',
+    '采用"双面板"设计——左侧物料采购执行(单品项) + 右侧供应商交付绩效(供应商)。',
+  ],
+  flowNote: '流程路径:MDP数据同步 → KPI指标计算引擎 → 双面板看板展示 → 多维筛选 → 导出报告/运营指标建模。',
+  flowSteps: [
+    { label: 'MDP数据同步/KPI指标计算', role: '系统(MDP)' },
+    { label: '双面板看板展示(物料+供应商)', role: '采购经理' },
+    { label: '多维筛选(日期/产品/采购单号)', role: '采购经理' },
+    { label: '达成等级标签(优秀/良好/关注)', role: '采购经理' },
+    { label: '导出报告/运营指标建模', role: '采购经理' },
+  ],
+  activities: [
+    ['10', '数据加载', '系统', '从DWD/KPI层加载四大L1指标:交货周期、满足率、人效、在途周转', 'KPI数据库', 'KPI展示数据'],
+    ['20', '左面板展示', '采购经理', '左侧"物料采购执行"按单品项展示KPI明细(指标名/公式/达成等级)', 'KPI数据', '物料维度面板'],
+    ['30', '右面板展示', '采购经理', '右侧"供应商交付绩效"按供应商维度展示交付表现', '供应商数据', '供应商维度面板'],
+    ['40', '基础查询', '采购经理', '按日期、产品、订单号、产线、供应商、采购单号、物料筛选', '筛选条件', '筛选后数据'],
+    ['50', '操作功能', '采购经理', '导出报告、运营指标建模链接跳转', '看板数据', '报告文件/建模页'],
+  ],
+  rules: [
+    ['1', '达成等级判定', '每个指标配有等级标签(优秀/良好/关注/预警),直观反映绩效水平'],
+    ['2', '数据刷新', '通过MDP定时刷新或手动触发,确保看板数据时效性'],
+    ['3', '指标口径对齐', '采购在途周转(S4-L1-D)与S3库存周转指标口径对齐'],
+    ['4', '双面板联动', '左面板筛选条件变更时右面板数据同步更新'],
+  ],
+  improvements: [
+    '从原系统零散Excel报表升级为可视化看板,左右双面板提供物料和供应商双视角分析。',
+    '新增达成等级标签(优秀/良好/关注/预警),替代原系统单一数值展示。',
+    '新增运营指标建模配置入口,支持自定义指标口径和计算规则。',
+  ],
+  permissions: [
+    ['1', '采购经理', '查看/导出/指标建模'],
+    ['2', '采购跟单员/质量管理员/仓管员', '查看权限'],
+  ],
+  interfaces: [
+    ['1', '看板聚合API', 'GET /api/AidopKanban/module-detail?moduleCode=S4', '实时(页面加载)'],
+    ['2', 'KPI数据源', 'S4-L1-A~D四大指标从DWD层计算产出', 'MDP定时刷新'],
+    ['3', '运营指标建模', 'KPI口径通过运营指标建模模块配置', '配置驱动'],
+  ],
+  reports: [
+    ['1', '采购执行月度报告', '含四大L1指标月度趋势和同比分析', '按月度'],
+    ['2', '采购执行分析报告', '导出看板当前筛选条件下的分析结果', '按需'],
+  ],
+};
+const ch6 = () => buildChapter('6', '采购执行看板主页', _ch6cfg);
+
+// ── Chapter 7: 供应商欠料看板 ──
+const _ch7cfg = {
+  targets: [
+    '提供未来15天(含当日D0)的供应商欠料滚动预测视图,帮助采购团队提前识别和应对缺料风险。',
+    '基于WorkOrdDetailTotalKB宽表通过MRP计算工单物料齐套情况,预测未来周期的供应缺口。',
+  ],
+  flowNote: '流程路径:根据工单需求/BOM/库存/在途 → 15天滚动欠料计算 → 动态列展示 → 手动刷新。',
+  flowSteps: [
+    { label: '根据工单需求/BOM/库存/在途', role: '系统(MDP)' },
+    { label: '15天滚动欠料计算', role: '系统' },
+    { label: '动态列展示', role: '采购跟单员' },
+    { label: '手动刷新', role: '采购跟单员' },
+  ],
+  activities: [
+    ['10', '数据加载', '系统', '从WorkOrdDetailTotalKB宽表加载工单需求/BOM/库存/在途数据', 'MDP宽表', '齐套数据'],
+    ['20', '欠料计算', '系统', '基于当前日期D0~D14共15天滚动欠料预测计算', '齐套数据', '欠料预测'],
+    ['30', '动态列展示', '系统/采购跟单员', '15天日期列自动计算生成,右侧展示每日欠料数量', '欠料预测', '动态列视图'],
+    ['40', '手动刷新', '采购跟单员', '点击刷新按钮执行实时缺料计算,页面不自动刷新', '刷新指令', '最新欠料数据'],
+  ],
+  rules: [
+    ['1', '时间窗口规则', '自动计算D0(今日)~D14共15天滚动欠料预测,日期列动态生成'],
+    ['2', '数据来源', '基于WorkOrdDetailTotalKB宽表,通过MRP计算工单物料齐套情况'],
+    ['3', '刷新触发', '支持手动刷新执行实时欠料计算,页面不自动刷新'],
+    ['4', '列设置', '支持自定义显示/隐藏15天日期列及基础信息列'],
+  ],
+  improvements: [
+    '从原系统无欠料预测能力升级为15天滚动预测视图,帮助采购提前应对。',
+    '新增动态日期列自动生成,日期变化时列自动滚动更新。',
+    '新增按供应商维度的欠料筛选,快速定位责任供应商。',
+  ],
+  permissions: [
+    ['1', '采购跟单员', '查看/刷新/筛选/排序'],
+    ['2', '采购经理', '查看/导出权限'],
+    ['3', '物料计划员/供应链经理', '查看权限'],
+  ],
+  interfaces: [
+    ['1', 'MDP入站-齐套数据', '从ERP/MES/SRM同步工单需求、BOM、库存、在途数据', '定时(日)'],
+    ['2', '欠料刷新API', '手动触发MRP欠料重新计算', '手动触发'],
+    ['3', '核心数据表', 'WorkOrdDetailTotalKB(工单物料欠料宽表)', '实时'],
+  ],
+  reports: [
+    ['1', '欠料预测日报', '每日输出D0~D14的欠料预测汇总', '日报'],
+    ['2', '供应商欠料趋势分析', '按供应商统计历史欠料趋势', '按周/月'],
+  ],
+};
+const ch7 = () => buildChapter('7', '供应商欠料看板', _ch7cfg);
+
+// ═══════════════════════════════════════════════════
+// Chapter 8: 数据中台与MDP集成
+// ═══════════════════════════════════════════════════
+function buildCh8() {
+  const ch = [];
+  ch.push(h1('8  数据中台与MDP集成'));
+  
+  ch.push(h2('8.1  目标/宗旨'));
+  ch.push(p('S4采购执行模块的数据基础依赖于MDP数据中台,负责从ERP、SRM和IQC等外部系统抽取、清洗和转换采购订单、交货计划、收货数据和检验记录,为供应商交货管理、退货管理和采购执行看板提供统一的数据视图。'));
+  
+  ch.push(h2('8.2  分层设计'));
+  ch.push(table(
+    ['分层', '表前缀', '职责', '更新机制'],
+    [
+      ['贴源层(STG)', 'mdp_stg_*', '保留源系统原始数据', 'MDP同步覆盖写入'],
+      ['标准层(STD)', 'mdp_std_*', '统一字段口径,清洗脏数据', 'MDP转换(最新批次)'],
+      ['宽表层(DWD)', 'dwd_*', '面向页面和KPI主题事实表', 'MDP转换(最新批次)'],
+      ['指标层(KPI)', 'ado_s9_kpi_value_*', '按天存储L1-L4指标值', 'MDP KPI计算'],
+    ],
+    [1500, 2000, 4200, 2300]
+  ));
+
+  ch.push(h2('8.3  S4核心数据流'));
+  ch.push(bp('交货管理链路'));
+  ch.push(p('ERP采购订单(purchase_order) → mdp_stg_po(贴源)→ mdp_std_po(标准)→ srm_polist_ds / SupplierShipment(DWD宽表)→ 交货页面'));
+  ch.push(bp('退货管理链路'));
+  ch.push(p('IQC检验结果(inspection_result) → mdp_stg_iqc(贴源)→ mdp_std_iqc(标准)→ PurchaseReturnOrder / IQCReturnQuery(DWD宽表)→ 退货页面'));
+  ch.push(bp('看板/KPI链路'));
+  ch.push(p('srm_polist_ds / ReceivingData / WorkOrdDetailTotalKB → DWD关联计算 → ado_s9_kpi_value_*(指标层)→ 看板消费'));
+
+  ch.push(h2('8.4  数据表清单'));
+  ch.push(table(
+    ['序号', '表名/视图', '数据层级', '说明'],
+    [
+      ['1', 'srm_polist_ds', 'DWD', '采购订单交付计划(供应商交货主表)'],
+      ['2', 'WorkOrdDetailTotalKB', 'DWD', '工单物料欠料宽表(含15天滚动预测)'],
+      ['3', 'SupplierShipment', 'DWD', '供应商发货单数据'],
+      ['4', 'PurchaseReturnOrder', 'DWD', '采购退货单主表'],
+      ['5', 'IQCReturnQuery', 'DWD', 'IQC退货查询视图'],
+      ['6', 'ReceivingData', 'DWD', '采购收货入库数据'],
+    ],
+    [600, 2200, 1200, 6000]
+  ));
+
+  ch.push(h2('8.5  MDP作业调度'));
+  ch.push(table(
+    ['配置项', '内容'],
+    [
+      ['作业编码', 'S4_MDP_SYNC_TRANSFORM'],
+      ['批次格式', 'S4_MDP_FULL_yyyyMMddHHmmss'],
+      ['数据源', 'ERP(purchase_order/receiving) / SRM(delivery_schedule/shipment) / IQC(inspection_result)'],
+      ['同步方式', '入站同步(ODS) → DWD转换 → 业务视图/KPI输出'],
+      ['监控入口', 'mdp_transform_run_log / mdp_sync_log / MDP运行监控页'],
+      ['执行方式', 'Cron定时调度 + 手动触发'],
+    ],
+    [2200, 7800]
+  ));
+
+  ch.push(h2('8.6  变化和改进点'));
+  ch.push(bullet('建立统一数据中台分层架构,S4业务数据从多源异构系统收敛到标准数据链路。'));
+  ch.push(bullet('页面和看板统一从DWD/KPI层消费,确保数据口径一致。'));
+  ch.push(bullet('MDP作业可观测,支持日志查询和异常追溯。'));
+
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// Chapter 9: 权限管理汇总
+// ═══════════════════════════════════════════════════
+function buildCh9() {
+  const ch = [];
+  ch.push(h1('9  权限管理汇总'));
+  ch.push(p('以下汇总S4采购执行模块各功能所涉及的角色和权限:'));
+  ch.push(table(
+    ['序号', '角色', '权限范围', '涉及模块'],
+    [
+      ['1', '采购跟单员', '增删改查交货管理、发货单;发布/交期回复/生成发货单/关闭/导出;查看欠料看板', '供应商交货管理、供应商发货单、供应商欠料看板'],
+      ['2', '采购经理', '查看所有模块;查看采购执行看板和管理KPI;导出报告;指标建模', '全模块(查看+看板操作)'],
+      ['3', '质量管理员', '增删改查采购退货单;查看IQC退货查询全部记录', '采购退货单、IQC退货查询'],
+      ['4', '仓库管理员', '查看交货管理、发货单、退货单;执行退货出库操作', '交货管理、发货单、采购退货单'],
+      ['5', '物料计划员', '查看供应商欠料看板(了解缺料预测)', '供应商欠料看板'],
+      ['6', '供应链经理', '查看采购执行看板和供应商欠料看板', '采购执行看板、欠料看板'],
+      ['7', 'IT运维人员', '查看MDP运行监控;监控同步任务状态', 'MDP运行监控'],
+      ['8', '工厂厂长/总经理', '查看采购执行看板全部指标', '采购执行看板'],
+    ],
+    [600, 1600, 5000, 2800]
+  ));
+  return ch;
+}
+
+// ═══════════════════════════════════════════════════
+// 文档构建主流程
+// ═══════════════════════════════════════════════════
+async function buildDocument() {
+  console.log('Generating flow chart images...');
+  flowImageCache['overview'] = await generateOverviewFlowPNG();
+
+  const allChapterConfigs = {
+    '2': _ch2cfg, '3': _ch3cfg, '4': _ch4cfg, '5': _ch5cfg,
+    '6': _ch6cfg, '7': _ch7cfg,
+  };
+  for (const [num, cfg] of Object.entries(allChapterConfigs)) {
+    if (cfg.flowSteps) {
+      flowImageCache[num] = await generateFlowPNG(cfg.flowSteps);
+    }
+  }
+  console.log('Flow chart images generated.');
+
+  const sections = [];
+
+  // ── 封面 ──
+  sections.push(empty(), empty(), empty());
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: 'S4 采购执行模块', bold: true, size: 52, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 100 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '蓝图设计方案', bold: true, size: 52, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 600 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: 'Ai-DOP 智慧运营决策平台', size: 28, font: FONT, color: '4472C4' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 120 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '版本:V1.0    日期:2026年5月27日    作者:AI Agent', size: 22, font: FONT, color: '808080' })],
+    alignment: AlignmentType.CENTER, spacing: { after: 2400 },
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '【机密文档 · 仅限项目内部使用】', size: 21, font: FONT, color: 'C00000', italics: true })],
+    alignment: AlignmentType.CENTER,
+  }));
+
+  // ── 文档控制 ──
+  sections.push(pageBreak(), h1('文档控制'));
+  sections.push(h2('更改记录'));
+  sections.push(table(['日期', '姓名', '版本', '变更说明'],
+    [['2026/05/27', 'AI Agent', 'V1.0', '初版(基于S4模块系统实现)']],
+    [1800, 1200, 1000, 6000]));
+  sections.push(empty());
+  sections.push(h2('审核'));
+  sections.push(table(['姓名', '职位', '签字/日期'], [['', '', ''], ['', '', '']], [3000, 3000, 4000]));
+  sections.push(empty());
+  sections.push(h2('发布'));
+  sections.push(table(['编号', '名称', '地点'], [['', '', '']], [3000, 4000, 3000]));
+
+  // ── 目录 ──
+  sections.push(pageBreak(), h1('目录'));
+  sections.push(...buildTOC());
+  sections.push(pageBreak());
+
+  // ── 第1章: 总体业务方案 ──
+  sections.push(h1('1  总体业务方案'));
+  sections.push(h2('1.1  目标和宗旨'));
+  sections.push(bullet('S4采购执行模块是Ai-DOP智慧运营平台的核心执行模块之一,聚焦于采购订单下达后的交货执行、退货处理和采购执行可视化。'));
+  sections.push(bullet('承接S3供应协同模块的采购订单输出,通过供应商交货管理、退货管理和采购执行看板三大核心能力,实现采购执行全流程的跟踪与管控。'));
+  sections.push(bullet('核心KPI:物料交货周期(S4-L1-A)、物料交货满足率(S4-L1-B)、物料采购人效(S4-L1-C)、采购在途周转(S4-L1-D)。'));
+  sections.push(bullet('注意:S4原本包含"采购管理"子模块(采购申请、订单、合同管理),该功能域已转移至S3供应协同模块管理。S4侧栏不再展示此入口。'));
+  sections.push(h2('1.2  总体业务流程图'));
+  sections.push(p('S4采购执行模块覆盖从"供应商交货"到"采购执行监控"的完整链路,下图展示了交货执行域、退货闭环域和采购执行看板域三大功能域的整体关系:'));
+  sections.push(flowImageParagraph(flowImageCache['overview']));
+  sections.push(h2('1.3  方案设计'));
+  sections.push(p('S4模块采用"数据中台驱动 + 业务执行闭环"的架构模式,覆盖以下核心功能模块:'));
+  sections.push(bullet('供应商交货管理:交货计划发布、交期回复、发货单生成、在途/在检/入库跟踪'));
+  sections.push(bullet('供应商发货单:发货单查询、编辑、附件管理'));
+  sections.push(bullet('采购退货单:退货单创建/审核/出库/状态跟踪,关联IQC检验'));
+  sections.push(bullet('IQC退货查询:只读退货历史查询,质量追溯'));
+  sections.push(bullet('采购执行看板主页:左右双面板KPI可视化(物料维度+供应商维度)'));
+  sections.push(bullet('供应商欠料看板:15天滚动欠料预测,动态日期列展示'));
+  sections.push(p('前端采用Vue 3 + Element Plus + TypeScript技术栈,交货管理使用AidopDemoShell容器组件+嵌入式发货单弹窗的组合布局,看板页使用KPI指标卡片+双面板对比布局。后端基于Admin.NET框架分层架构实现。数据通过MDP管道进行跨系统同步。'));
+
+  // ── 第2~7章: 各功能模块 ──
+  sections.push(pageBreak()); sections.push(h1('2  供应商交货管理')); sections.push(...ch2());
+  sections.push(pageBreak()); sections.push(h1('3  供应商发货单')); sections.push(...ch3());
+  sections.push(pageBreak()); sections.push(h1('4  采购退货单')); sections.push(...ch4());
+  sections.push(pageBreak()); sections.push(h1('5  IQC退货查询')); sections.push(...ch5());
+  sections.push(pageBreak()); sections.push(h1('6  采购执行看板主页')); sections.push(...ch6());
+  sections.push(pageBreak()); sections.push(h1('7  供应商欠料看板')); sections.push(...ch7());
+
+  // ── 第8~9章 ──
+  sections.push(pageBreak()); sections.push(...buildCh8());
+  sections.push(pageBreak()); sections.push(...buildCh9());
+
+  return sections;
+}
+
+// ═══════════════════════════════════════════════════
+// 生成文档
+// ═══════════════════════════════════════════════════
+async function main() {
+  const doc = new Document({
+    styles: { default: { document: { run: { font: FONT, size: 21 } } } },
+    sections: [{
+      properties: { page: { margin: { top: convertInchesToTwip(0.8), bottom: convertInchesToTwip(0.8), left: convertInchesToTwip(1.0), right: convertInchesToTwip(1.0) } } },
+      children: await buildDocument(),
+    }],
+  });
+
+  const buffer = await Packer.toBuffer(doc);
+  const outPath = 'd:\\DEMONET\\doc\\S4采购执行模块蓝图设计方案.docx';
+  const fallbackPath = 'd:\\DEMONET\\doc\\S4采购执行模块蓝图设计方案_V2.docx';
+  const tempPath = 'd:\\DEMONET\\doc\\S4_blueprint_temp.docx';
+  fs.writeFileSync(tempPath, buffer);
+  try { fs.unlinkSync(outPath); } catch(e) {}
+  try { fs.renameSync(tempPath, outPath); console.log(`Renamed to: ${outPath}`); }
+    catch(e) {
+      try { fs.unlinkSync(fallbackPath); } catch(_) {}
+      try { fs.renameSync(tempPath, fallbackPath); console.log(`Fallback to: ${fallbackPath}`); }
+        catch(e2) { console.log(`Generated: ${tempPath} (target locked)`); }
+    }
+  console.log(`Size: ${(buffer.length / 1024).toFixed(1)} KB`);
+}
+main().catch(err => { console.error('Error:', err); process.exit(1); });
+

+ 285 - 0
doc/generate_s4_req_docx.js

@@ -0,0 +1,285 @@
+/**
+ * Generate S4 采购执行业务需求描述.docx
+ * 严格按照 S1 产销协同业务需求描述.docx 的格式:
+ *   标题 → 文档信息 → 目录 → (一)模块功能说明+业务描述 → ...
+ */
+const fs = require('fs');
+const {
+  Document, Packer, Paragraph, TextRun, HeadingLevel,
+  AlignmentType, PageBreak, TableOfContents, convertInchesToTwip,
+} = require('docx');
+
+const FONT = '微软雅黑';
+
+// ── 辅助函数 ──
+function empty() { return new Paragraph({ spacing: { after: 80 }, children: [] }); }
+function pageBreak() { return new Paragraph({ children: [new PageBreak()] }); }
+
+function normalPara(text, opts = {}) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT, ...opts })],
+    spacing: { after: 100, line: 360 },
+    indent: opts.indent ? { left: convertInchesToTwip(opts.indent) } : undefined,
+  });
+}
+
+// H2: (一) 供应商交货管理
+function sectionHeading(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 400, after: 200 },
+  });
+}
+
+// H3: 功能说明 / 业务描述
+function subHeading(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 22, font: FONT })],
+    heading: HeadingLevel.HEADING_3,
+    spacing: { before: 200, after: 100 },
+  });
+}
+
+// Title paragraph
+function titlePara(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, bold: true, size: 36, font: FONT, color: '1F4E79' })],
+    alignment: AlignmentType.CENTER,
+    spacing: { after: 300 },
+  });
+}
+
+function docInfoPara(text) {
+  return new Paragraph({
+    children: [new TextRun({ text, size: 21, font: FONT })],
+    spacing: { after: 40 },
+    bullet: { level: 0 },
+  });
+}
+
+// ═══════════════════════════════════════════════════
+// 各模块业务需求描述数据
+// ═══════════════════════════════════════════════════
+
+const CN_NUM = ['', '一', '二', '三', '四', '五', '六', '七', '八'];
+
+const modules = [
+  {
+    title: '供应商交货管理',
+    funcDesc: [
+      '供应商交货管理模块管理采购订单下达后的供应商交货执行过程,通过交货计划发布驱动供应商交期回复和发货单生成。',
+      '采购跟单员可从MDP同步的交货计划中勾选发布至供应商,要求所选记录属于同一供应商,支持"按计划日期回复"(批量)和"回复交期"(手动)两种交期回复方式。',
+      '支持基于交货记录在弹窗中嵌入发货单表单,一键生成发货单,实现"发布→回复→发货单"全流程在单个页面内完成。',
+      '全链路状态跟踪覆盖在途、在检、入库各阶段,支持多维度筛选、排序和列设置(20个字段自定义显示)。',
+    ],
+    bizDesc: [
+      '采购跟单员登录DOP系统,进入"S4采购执行→供应商交货管理"菜单,查看交货计划列表。',
+      '从MDP同步的交货计划中勾选目标记录(须属同一供应商),点击"发布"按钮将交货计划发布至供应商。',
+      '供应商收到交货计划后进行交期回复:采购跟单员可选择"按计划日期回复"进行批量快速回复,或选择"回复交期"手动指定单个日期。',
+      '交期回复完成后,采购跟单员勾选已回复的交货记录,点击"生成发货单",在嵌入式弹窗中填写发货单信息并确认生成。',
+      '系统自动同步在途/在检/入库状态,采购跟单员可在列表中实时跟踪每条交货记录的当前状态。',
+      '列表支持按供应商、物料、日期范围等多条件筛选,支持CSV导出;交货数据通过MDP同步至SRM系统供供应商查看。',
+    ],
+  },
+  {
+    title: '供应商发货单',
+    funcDesc: [
+      '供应商发货单模块管理从交货管理生成的发货单,覆盖从发货单生成到供应商发货、入库关联的全链路操作。',
+      '发货单仅由交货管理的"生成发货单"操作产生,不支持手动独立创建,确保数据来源的一致性和可追溯性。',
+      '支持标签生成和打印功能,基于发货单信息自动生成物流标签(含物料编码、数量、供应商等关键信息),打印后粘贴于货物外包装用于物流识别。',
+      '发货单数据通过SupplierShipment表与ERP/SRM双向同步,入库数据自动关联发货单并更新状态。',
+    ],
+    bizDesc: [
+      '采购跟单员在交货管理页面勾选交货记录后一键生成发货单,发货单自动进入供应商发货单列表。',
+      '采购跟单员进入"S4采购执行→供应商发货单"菜单,查看发货单列表,支持按供应商、物料、日期等条件筛选。',
+      '基于发货单信息点击"生成标签"按钮,系统自动生成包含物料编码、数量、供应商等关键信息的物流标签。',
+      '上传发货单相关附件(签收单、运输单、质检报告等),支持多格式附件上传。',
+      '打印物流标签粘贴于货物外包装,供应商按发货单要求进行实物发货。',
+      '采购跟单员打印正式发货单据作为运输和收货凭证;收货数据入库后系统自动关联发货单并更新状态。',
+    ],
+  },
+  {
+    title: '采购退货单',
+    funcDesc: [
+      '采购退货单模块处理来料检验不合格后的退货流程,管理退货单据的创建、出库和状态跟踪。',
+      '退货单必须关联原始采购订单和不合格IQC检验记录,确保退货流程的可追溯性。',
+      '退货单状态生命周期管理:新建→已出库→已完成,顺序流转不可跳转,已出库的退货单不可删除。',
+      '退货出库时自动校验库存数量充足,出库后自动扣减库存并同步至ERP系统。',
+    ],
+    bizDesc: [
+      '质量管理员在IQC来料检验中判定不合格,触发退货流程。',
+      '质量管理员进入"S4采购执行→采购退货单"菜单,点击"新建退货单",基于不合格判定创建退货单,系统自动关联原始采购订单和检验记录。',
+      '退货单创建后状态为"新建",质量管理员填写退货原因、退货数量等信息后保存。',
+      '仓库管理员在退货单列表中执行"退货出库"操作,系统校验库存数量充足后执行出库,自动扣减库存数据,退货单状态更新为"已出库"。',
+      '退货处理完成后,退货单状态自动更新为"已完成";退货数据可关联至供应商绩效评估。',
+      '采购经理可在列表中查看所有退货单记录,支持按供应商、物料、退货原因、日期范围等条件筛选。',
+    ],
+  },
+  {
+    title: 'IQC退货查询',
+    funcDesc: [
+      'IQC退货查询模块提供只读的退货历史查询功能,面向质量管理人员查看IQC检验不合格后的退货处理记录。',
+      '展示退货详情,包括检验结果、不合格原因、处理方式等完整信息,支持按检验单号、物料编码、日期范围检索。',
+      '该页面为只读查询视图,不支持增删改操作,确保质量记录的完整性和不可篡改性。',
+      '每条退货记录可关联到原始IQC检验单和采购订单,实现完整的质量追溯链路。',
+    ],
+    bizDesc: [
+      '质量管理员进入"S4采购执行→IQC退货查询"菜单,查看IQC退货查询列表。',
+      '通过检验单号、物料编码、日期范围等条件筛选查询退货记录。',
+      '在列表中查看每条退货记录的详情:IQC检验结果、不合格原因、退货处理方式、退货数量等。',
+      '退货记录自动关联原始采购订单和供应商信息,支持供应商质量评级分析。',
+      '采购经理和仓库管理员可查看退货查询记录,了解退货处理进度和结果。',
+    ],
+  },
+  {
+    title: '采购执行看板主页',
+    funcDesc: [
+      '采购执行看板主页通过可视化看板展示S4采购执行四大L1核心指标:物料交货周期(S4-L1-A)、物料交货满足率(S4-L1-B)、物料采购人效(S4-L1-C)、采购在途周转(S4-L1-D)。',
+      '采用"双面板"设计——左侧"物料采购执行"按单品项展示KPI明细,右侧"供应商交付绩效"按供应商维度展示交付表现。',
+      '每个指标配有达成等级标签(优秀/良好/关注/预警),直观反映绩效水平,帮助管理层快速识别问题。',
+      '支持按日期、产品、订单号、产线、供应商、采购单号、物料等多维度筛选,左右面板联动更新;支持导出报告和运营指标建模。',
+    ],
+    bizDesc: [
+      '采购经理登录DOP系统,进入"S4采购执行→采购执行看板主页"菜单,系统自动加载四大L1核心KPI指标卡片。',
+      '左侧"物料采购执行"面板按单品项展示KPI明细,包含指标名称、计算公式和达成等级标签。',
+      '右侧"供应商交付绩效"面板按供应商维度展示交付表现,帮助识别优质和问题供应商。',
+      '通过日期、产品、订单号、产线、供应商等筛选条件进行多维分析,左面板筛选条件变更时右面板数据同步更新。',
+      '点击"导出报告"按钮将看板当前筛选条件下的分析结果导出为报告文件。',
+      'KPI数据通过MDP作业S4_MDP_SYNC_TRANSFORM定时或手动刷新,指标定义存储在ado_smart_ops_kpi_master,计算结果存储在ado_s9_kpi_value_l1~l4_day。',
+    ],
+  },
+  {
+    title: '供应商欠料看板',
+    funcDesc: [
+      '供应商欠料看板提供未来15天(含当日D0)的供应商欠料滚动预测视图,帮助采购团队提前识别和应对缺料风险。',
+      '基于WorkOrdDetailTotalKB宽表通过MRP计算工单物料齐套情况,预测未来D0~D14共15天各周期的供应缺口。',
+      '15天日期列自动计算生成,日期变化时列自动滚动更新;右侧展示每日欠料数量,直观反映缺料趋势。',
+      '支持手动刷新执行实时缺料计算,页面不自动刷新;支持按供应商维度筛选,快速定位责任供应商。',
+    ],
+    bizDesc: [
+      '采购跟单员进入"S4采购执行→供应商欠料看板"菜单,系统从WorkOrdDetailTotalKB宽表加载工单需求/BOM/库存/在途数据。',
+      '系统自动计算D0(今日)~D14共15天的滚动欠料预测,15天日期列自动生成显示。',
+      '在看板列表中查看各物料在未来15天内的每日欠料数量,重点关注欠料严重的物料和供应商。',
+      '通过供应商维度筛选,快速定位责任供应商的欠料情况;支持自定义显示/隐藏15天日期列及基础信息列。',
+      '点击"刷新"按钮执行实时欠料计算,获取最新的工单需求、库存和在途数据后重新计算欠料预测。',
+      '采购经理可查看和导出欠料预测日报,按供应商统计历史欠料趋势,为供应商管理决策提供数据支持。',
+    ],
+  },
+  {
+    title: '数据中台架构',
+    funcDesc: [
+      'S4采购执行模块的数据基础依赖于MDP数据中台,采用统一分层架构(STG→STD→DWD→KPI),负责从ERP、SRM和IQC等外部系统抽取、清洗和转换采购订单、交货计划、收货数据和检验记录。',
+      '贴源层(STG/mdp_stg_*)保留源系统原始数据;标准层(STD/mdp_std_*)统一字段口径并清洗脏数据;宽表层(DWD/dwd_*)构建面向页面和KPI的主题事实表;指标层(KPI/ado_s9_kpi_value_*)按天存储L1-L4指标值。',
+      'MDP作业(S4_MDP_SYNC_TRANSFORM)负责全链路同步转换,支持Cron定时调度和手动触发,作业日志可观测、异常可追溯。',
+      '核心数据表包括:srm_polist_ds(采购订单交付计划)、SupplierShipment(发货单)、PurchaseReturnOrder(退货单)、IQCReturnQuery(退货查询视图)、WorkOrdDetailTotalKB(欠料宽表)。',
+    ],
+    bizDesc: [
+      'S4模块的业务数据(交货计划、发货单、退货单、IQC检验记录等)通过MDP作业自动同步至数据中台各层。',
+      '交货管理链路:ERP采购订单→mdp_stg_po贴源→mdp_std_po标准→srm_polist_ds/SupplierShipment宽表→交货页面。',
+      '退货管理链路:IQC检验结果→mdp_stg_iqc贴源→mdp_std_iqc标准→PurchaseReturnOrder/IQCReturnQuery宽表→退货页面。',
+      '看板/KPI链路:srm_polist_ds/ReceivingData/WorkOrdDetailTotalKB→DWD关联计算→ado_s9_kpi_value_*指标层→看板消费。',
+      '页面查询统一消费DWD/KPI层数据,避免直接访问运行时表,确保数据口径一致和查询性能优化。',
+    ],
+  },
+  {
+    title: '权限管理汇总',
+    funcDesc: [
+      'S4采购执行模块涉及以下核心角色和权限:',
+      '采购跟单员:增删改查交货管理、发货单;发布/交期回复/生成发货单/关闭/导出;查看欠料看板。',
+      '采购经理:查看所有模块;查看采购执行看板和管理KPI;导出报告;指标建模。',
+      '质量管理员:增删改查采购退货单;查看IQC退货查询全部记录。',
+      '仓库管理员:查看交货管理、发货单、退货单;执行退货出库操作。',
+      '物料计划员:查看供应商欠料看板(了解缺料预测)。',
+      '供应链经理:查看采购执行看板和供应商欠料看板。',
+      'IT运维人员:查看MDP运行监控;监控同步任务状态。',
+      '工厂厂长/总经理:查看采购执行看板全部指标。',
+    ],
+  },
+];
+
+// ═══════════════════════════════════════════════════
+// 生成文档
+// ═══════════════════════════════════════════════════
+function buildDocument() {
+  const sections = [];
+
+  // ── 封面/标题 ──
+  sections.push(empty(), empty(), empty(), empty());
+  sections.push(titlePara('采购执行业务需求描述'));
+  sections.push(empty());
+
+  // ── 文档信息 ──
+  const infoItems = [
+    '文档作者:彭熙玉',
+    '创建日期:2026-06-08',
+    '更新日期:2026-06-08',
+    '文档编号:DOP-S4-REQ-001',
+    '当前版本:V1.0',
+  ];
+  infoItems.forEach(item => sections.push(docInfoPara(item)));
+  sections.push(empty(), empty());
+
+  // ── 目录 ──
+  sections.push(pageBreak());
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '目录', bold: true, size: 28, font: FONT })],
+    heading: HeadingLevel.HEADING_2,
+    spacing: { before: 200, after: 300 },
+  }));
+  sections.push(new TableOfContents('目录', {
+    headingStyleRange: '2-3',
+    hyperlink: true,
+  }));
+  sections.push(new Paragraph({
+    children: [new TextRun({ text: '(如目录未显示,请在 Word 中右键此处 → 更新域)', size: 18, font: FONT, italics: true, color: '888888' })],
+    spacing: { before: 40, after: 200 },
+  }));
+
+  // ── 各功能模块 ──
+  for (let i = 0; i < modules.length; i++) {
+    const mod = modules[i];
+    sections.push(pageBreak());
+
+    // 章节标题:(一) 供应商交货管理
+    sections.push(sectionHeading(`(${CN_NUM[i + 1]})${mod.title}`));
+
+    // 功能说明
+    sections.push(subHeading('功能说明'));
+    (mod.funcDesc || []).forEach(desc => sections.push(normalPara(desc)));
+
+    // 业务描述
+    if (mod.bizDesc && mod.bizDesc.length) {
+      sections.push(subHeading('业务描述'));
+      mod.bizDesc.forEach(desc => sections.push(normalPara(desc)));
+    }
+  }
+
+  return sections;
+}
+
+// ═══════════════════════════════════════════════════
+// 主流程
+// ═══════════════════════════════════════════════════
+async function main() {
+  const sections = buildDocument();
+
+  const doc = new Document({
+    styles: { default: { document: { run: { font: FONT, size: 21 } } } },
+    sections: [{
+      properties: { page: { margin: { top: convertInchesToTwip(0.8), bottom: convertInchesToTwip(0.8), left: convertInchesToTwip(1.2), right: convertInchesToTwip(1.2) } } },
+      children: sections,
+    }],
+  });
+
+  const buffer = await Packer.toBuffer(doc);
+  const outPath = 'd:\\DEMONET\\doc\\S4采购执行业务需求描述.docx';
+  const tempPath = 'd:\\DEMONET\\doc\\s4_req_temp.docx';
+  fs.writeFileSync(tempPath, buffer);
+  try { fs.unlinkSync(outPath); } catch(e) {}
+  try { fs.renameSync(tempPath, outPath); console.log(`Renamed to: ${outPath}`); }
+    catch(e) { console.log(`Generated: ${tempPath}`); }
+  console.log(`Size: ${(buffer.length / 1024).toFixed(1)} KB`);
+}
+
+main().catch(err => { console.error('Error:', err); process.exit(1); });

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.157</AssemblyVersion>
-    <FileVersion>1.0.157</FileVersion>
-    <Version>1.0.157</Version>
+    <AssemblyVersion>1.0.158</AssemblyVersion>
+    <FileVersion>1.0.158</FileVersion>
+    <Version>1.0.158</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 2 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Order/ContractReviewService.cs

@@ -297,7 +297,7 @@ public class ContractReviewService : IDynamicApiController, ITransient
     {
         var tenantId = _userManager.TenantId;
         var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
-        var conditions = new List<string> { "tenant_id = @TenantId" };
+        var conditions = new List<string> { "TenantId = @TenantId" };
 
         if (!string.IsNullOrWhiteSpace(keyword))
         {
@@ -306,7 +306,7 @@ public class ContractReviewService : IDynamicApiController, ITransient
         }
 
         var whereClause = "WHERE " + string.Join(" AND ", conditions);
-        var sql = $"SELECT Account, RealName FROM sys_user {whereClause} ORDER BY Account LIMIT 100";
+        var sql = $"SELECT Account, RealName FROM SysUser {whereClause} ORDER BY Account LIMIT 100";
 
         var rows = await _db.Ado.SqlQueryAsync<ResponsibleUserRow>(sql, pars);