generate_s1_blueprint_docx.js 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136
  1. /**
  2. * Generate S1 产销协同模块蓝图设计方案.docx
  3. * 严格按照原文档模板格式:每个功能章节含 8 个子章节
  4. * [目标/宗旨, 业务流程图, 业务流程说明, 业务流程规则, 变化和改进点, 权限管理需求, 系统接口集成, 报表需求]
  5. */
  6. const fs = require('fs');
  7. const sharp = require('sharp');
  8. const {
  9. Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
  10. HeadingLevel, AlignmentType, BorderStyle, WidthType, ShadingType,
  11. convertInchesToTwip, PageBreak, ImageRun, TabStopType, TabStopPosition,
  12. InternalHyperlink, TableOfContents
  13. } = require('docx');
  14. // ═══════════════════════════════════════════════════
  15. // 通用样式与辅助函数
  16. // ═══════════════════════════════════════════════════
  17. const FONT = '微软雅黑';
  18. const thinBorder = {
  19. top: { style: BorderStyle.SINGLE, size: 1 },
  20. bottom: { style: BorderStyle.SINGLE, size: 1 },
  21. left: { style: BorderStyle.SINGLE, size: 1 },
  22. right: { style: BorderStyle.SINGLE, size: 1 },
  23. };
  24. const headerShading = { fill: '4472C4', type: ShadingType.CLEAR };
  25. function p(text, opts = {}) {
  26. return new Paragraph({
  27. children: [new TextRun({ text, size: 21, font: FONT, ...opts })],
  28. spacing: { after: 120, line: 360 },
  29. });
  30. }
  31. function bp(text) { return p(text, { bold: true }); }
  32. function empty() { return new Paragraph({ spacing: { after: 80 }, children: [] }); }
  33. function pageBreak() { return new Paragraph({ children: [new PageBreak()] }); }
  34. function h1(text) {
  35. return new Paragraph({
  36. children: [new TextRun({ text, bold: true, size: 32, font: FONT })],
  37. heading: HeadingLevel.HEADING_1,
  38. spacing: { before: 400, after: 200 },
  39. });
  40. }
  41. function h2(text) {
  42. return new Paragraph({
  43. children: [new TextRun({ text, bold: true, size: 28, font: FONT })],
  44. heading: HeadingLevel.HEADING_2,
  45. spacing: { before: 300, after: 150 },
  46. });
  47. }
  48. function h3(text) {
  49. return new Paragraph({
  50. children: [new TextRun({ text, bold: true, size: 24, font: FONT })],
  51. heading: HeadingLevel.HEADING_3,
  52. spacing: { before: 200, after: 100 },
  53. });
  54. }
  55. function bullet(text, indent = 0) {
  56. return new Paragraph({
  57. children: [new TextRun({ text: ` ${text}`, size: 21, font: FONT })],
  58. spacing: { after: 60, line: 340 },
  59. indent: { left: 300 + indent * 400 },
  60. });
  61. }
  62. // Table helpers
  63. function hdrCell(text, w = 1800) {
  64. return new TableCell({
  65. children: [new Paragraph({ children: [new TextRun({ text, bold: true, size: 20, font: FONT, color: 'FFFFFF' })], alignment: AlignmentType.CENTER })],
  66. width: { size: w, type: WidthType.DXA }, borders: thinBorder, shading: headerShading,
  67. });
  68. }
  69. function tc(text, w = 1800, opts = {}) {
  70. return new TableCell({
  71. children: [new Paragraph({ children: [new TextRun({ text: String(text ?? ''), size: 20, font: FONT })], alignment: opts.center ? AlignmentType.CENTER : AlignmentType.LEFT })],
  72. width: { size: w, type: WidthType.DXA }, borders: thinBorder,
  73. });
  74. }
  75. function table(headers, rows, widths) {
  76. return new Table({
  77. rows: [
  78. new TableRow({ children: headers.map((h, i) => hdrCell(h, widths ? widths[i] : 1800)), tableHeader: true }),
  79. ...rows.map(r => new TableRow({ children: r.map((c, i) => tc(c, widths ? widths[i] : 1800)) })),
  80. ],
  81. width: { size: 100, type: WidthType.PERCENTAGE },
  82. });
  83. }
  84. // ── 活动流程表(5列) ──
  85. function activityTable(rows) {
  86. const w = [600, 1400, 1100, 3800, 1400, 1700];
  87. return table(['编号', '活动名称', '执行角色', '活动描述', '输入', '输出'], rows, w);
  88. }
  89. // ── 业务规则表(3列) ──
  90. function ruleTable(rows) {
  91. return table(['序列号', '业务情形', '描述/方案'], rows, [900, 2800, 6300]);
  92. }
  93. // ── 接口表(4列) ──
  94. function interfaceTable(rows) {
  95. return table(['序号', '接口名称', '接口说明', '频次及触发方式'], rows, [600, 2400, 4200, 2800]);
  96. }
  97. // ── 报表表(4列) ──
  98. function reportTable(rows) {
  99. return table(['序号', '名称', '描述', '方案'], rows, [600, 2600, 3600, 3200]);
  100. }
  101. // ── 权限表(3列) ──
  102. function permTable(rows) {
  103. return table(['序号', '岗位名称', '对应系统权限'], rows, [600, 2000, 7400]);
  104. }
  105. // ── 目录条目(模板格式:编号 + Tab + 标题 + Tab + 页码占位) ──
  106. // Template uses: TOC2 tab at 2880 twip, TOC3 tab at 3600 twip, left-align tab
  107. const TOC2_TAB = 2880; // ~2 inches for chapter entries
  108. const TOC3_TAB = 3600; // ~2.5 inches for sub-section entries
  109. const TOC_FONT_SIZE = 20; // 10pt, matching template
  110. function tocChapter(num, title) {
  111. // TOC2 style: "1" + TAB(at 2880) + "总体业务方案" + TAB + page placeholder
  112. return new Paragraph({
  113. children: [
  114. new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
  115. new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
  116. new TextRun({ text: title, bold: true, size: TOC_FONT_SIZE, font: FONT }),
  117. new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
  118. new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
  119. ],
  120. tabStops: [
  121. { type: TabStopType.LEFT, position: TOC2_TAB },
  122. { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
  123. ],
  124. spacing: { before: 80, after: 40 },
  125. });
  126. }
  127. function tocSub(num, title) {
  128. // TOC3 style: "1.1" + TAB(at 3600) + "目标和宗旨" + TAB + page placeholder
  129. return new Paragraph({
  130. children: [
  131. new TextRun({ text: String(num), size: TOC_FONT_SIZE, font: FONT }),
  132. new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
  133. new TextRun({ text: title, size: TOC_FONT_SIZE, font: FONT }),
  134. new TextRun({ text: '\t', size: TOC_FONT_SIZE, font: FONT }),
  135. new TextRun({ text: ' ', size: TOC_FONT_SIZE, font: FONT }),
  136. ],
  137. tabStops: [
  138. { type: TabStopType.LEFT, position: TOC3_TAB },
  139. { type: TabStopType.RIGHT, position: TabStopPosition.MAX },
  140. ],
  141. spacing: { before: 30, after: 30 },
  142. });
  143. }
  144. function buildTOC() {
  145. const ch = [];
  146. // TOC field code (user updates in Word to auto-generate with page numbers)
  147. ch.push(new TableOfContents('目录', {
  148. headingStyleRange: '1-2',
  149. hyperlink: true,
  150. }));
  151. ch.push(new Paragraph({
  152. children: [new TextRun({ text: '(如目录未显示,请在 Word 中右键此处 → 更新域)', size: 18, font: FONT, italics: true, color: '888888' })],
  153. spacing: { before: 40, after: 200 },
  154. }));
  155. // ── 目录前导区(文档控制) ──
  156. ch.push(tocChapter('', '文档控制'));
  157. ch.push(tocSub('', '更改记录'));
  158. ch.push(tocSub('', '审核'));
  159. ch.push(tocSub('', '发布'));
  160. ch.push(tocChapter('', '目录'));
  161. // ── 第1章 ──
  162. ch.push(tocChapter('1', '总体业务方案'));
  163. ch.push(tocSub('1.1', '目标和宗旨'));
  164. ch.push(tocSub('1.2', '总体业务流程图'));
  165. ch.push(tocSub('1.3', '方案设计'));
  166. // ── 第2~10章(8子章节) ──
  167. const chSubs = [
  168. '目标/宗旨', '业务流程图', '业务流程说明', '业务流程规则',
  169. '变化和改进点', '权限管理需求', '系统接口集成', '报表需求',
  170. ];
  171. const chTitles = [
  172. '产品设计管理', '订单评审管理', '订单交付管理', '订单发货管理',
  173. '合同评审管理', '计划联动', '需求明细核验', '工单下达', '产销协同看板与KPI',
  174. ];
  175. for (let i = 0; i < chTitles.length; i++) {
  176. const n = i + 2;
  177. ch.push(tocChapter(String(n), chTitles[i]));
  178. for (let j = 0; j < chSubs.length; j++) {
  179. ch.push(tocSub(`${n}.${j + 1}`, chSubs[j]));
  180. }
  181. }
  182. // ── 第11章 ──
  183. ch.push(tocChapter('11', '数据中台架构'));
  184. ['目标/宗旨', '分层设计', 'S1核心数据流', '对象映射总表', 'MDP作业调度'].forEach((title, i) => {
  185. ch.push(tocSub(`11.${i + 1}`, title));
  186. });
  187. // ── 第12章 ──
  188. ch.push(tocChapter('12', '权限管理汇总'));
  189. return ch;
  190. }
  191. // ═══════════════════════════════════════════════════
  192. // 流程图生成:根据步骤数组生成 SVG → sharp 转 PNG
  193. // ═══════════════════════════════════════════════════
  194. const FLOW_W = 680, BOX_W = 220, BOX_H = 42, GAP_Y = 28;
  195. const ROLE_X = 30, BOX_X = 130, MARGIN_Y = 40;
  196. // 流程图 PNG 缓存:key = 章节号,value = Buffer
  197. const flowImageCache = {};
  198. function flowImageParagraph(pngBuffer) {
  199. return new Paragraph({
  200. children: [new ImageRun({ data: pngBuffer, transformation: { width: 540, height: 360 }, type: 'png' })],
  201. alignment: AlignmentType.CENTER,
  202. spacing: { before: 120, after: 120 },
  203. });
  204. }
  205. async function generateFlowPNG(steps) {
  206. // steps: [{label: '活动名称', role: '执行角色'}, ...] — 纯纵向流程
  207. const N = steps.length;
  208. const svgH = MARGIN_Y * 2 + N * (BOX_H + GAP_Y) - GAP_Y;
  209. const centerX = BOX_X + BOX_W / 2;
  210. let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${FLOW_W}" height="${svgH}">`;
  211. svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
  212. steps.forEach((s, i) => {
  213. const y = MARGIN_Y + i * (BOX_H + GAP_Y);
  214. // 角色标签
  215. 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>`;
  216. // 节点框
  217. const isStart = i === 0, isEnd = i === N - 1;
  218. const fill = isStart ? '#1565C0' : (isEnd ? '#2E7D32' : '#E3F2FD');
  219. const stroke = isStart ? '#0D47A1' : (isEnd ? '#1B5E20' : '#1565C0');
  220. const textColor = (isStart || isEnd) ? '#FFFFFF' : '#333333';
  221. svg += `<rect x="${BOX_X}" y="${y}" width="${BOX_W}" height="${BOX_H}" rx="6" fill="${fill}" stroke="${stroke}" stroke-width="1.5"/>`;
  222. 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>`;
  223. // 箭头
  224. if (i < N - 1) {
  225. const ay = y + BOX_H + 4, by2 = ay + GAP_Y - 8;
  226. svg += `<line x1="${centerX}" y1="${ay}" x2="${centerX}" y2="${by2}" stroke="#888" stroke-width="1.5"/>`;
  227. svg += `<polygon points="${centerX - 5},${by2 - 10} ${centerX + 5},${by2 - 10} ${centerX},${by2}" fill="#888"/>`;
  228. }
  229. });
  230. svg += '</svg>';
  231. return sharp(Buffer.from(svg)).png().toBuffer();
  232. }
  233. // 第1章专用:三层复合流程图(主线+支撑+数据流)
  234. async function generateOverviewFlowPNG() {
  235. const W = 780, H = 620;
  236. let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}">`;
  237. svg += `<rect width="100%" height="100%" fill="#FAFBFC"/>`;
  238. // ── 标题栏 ──
  239. svg += `<text x="${W/2}" y="28" font-family="Microsoft YaHei" font-size="15" font-weight="bold" fill="#1F4E79" text-anchor="middle">S1 产销协同 — 总体业务流程</text>`;
  240. // ── 区域背景 ──
  241. const zones = [
  242. { y: 42, h: 200, color: '#E8F0FE', label: '主线流程(订单履约链)', lx: 10, ly: 60 },
  243. { y: 250, h: 120, color: '#FFF3E0', label: '支撑流程', lx: 10, ly: 268 },
  244. { y: 378, h: 90, color: '#E8F5E9', label: '数据中台链路', lx: 10, ly: 396 },
  245. ];
  246. zones.forEach(z => {
  247. 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"/>`;
  248. svg += `<text x="${z.lx}" y="${z.ly}" font-family="Microsoft YaHei" font-size="12" font-weight="bold" fill="#555">${z.label}</text>`;
  249. });
  250. // ── 主线流程节点 ──
  251. const mainNodes = [
  252. { x: 30, y: 85, w: 130, h: 38, label: '销售订单接收', color: '#1565C0' },
  253. { x: 190, y: 85, w: 130, h: 38, label: '订单评审\n(交期评估)', color: '#1565C0' },
  254. { x: 350, y: 85, w: 110, h: 38, label: '工单下达', color: '#1565C0' },
  255. { x: 485, y: 85, w: 110, h: 38, label: '排产执行', color: '#6C757D' },
  256. { x: 620, y: 85, w: 110, h: 38, label: '采购备料', color: '#6C757D' },
  257. { x: 485, y: 155, w: 110, h: 38, label: '生产制造', color: '#6C757D' },
  258. { x: 620, y: 155, w: 110, h: 38, label: '成品入库', color: '#2E7D32' },
  259. { x: 350, y: 170, w: 100, h: 38, label: '交期确认', color: '#1565C0' },
  260. { x: 215, y: 170, w: 100, h: 38, label: '变更管理', color: '#F57C00' },
  261. ];
  262. // 子流程节点 (smaller, secondary)
  263. const subNodes = [
  264. { x: 30, y: 170, w: 145, h: 38, label: '发货计划/ASN发货', color: '#2E7D32' },
  265. { x: 190, y: 170, w: 24, h: 0, label: '', color: '' }, // dummy
  266. ];
  267. [...mainNodes, subNodes[0]].filter(n => n.h > 0).forEach(n => {
  268. const fill = n.color === '#6C757D' ? '#E0E0E0' : n.color;
  269. const stroke = n.color === '#6C757D' ? '#999' : n.color;
  270. const tc = n.color === '#6C757D' ? '#555' : '#fff';
  271. svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="5" fill="${fill}" stroke="${stroke}" stroke-width="1.2"/>`;
  272. const lines = n.label.split('\n');
  273. const cy = n.y + n.h / 2;
  274. lines.forEach((l, li) => {
  275. const off = (lines.length - 1) * -7 + li * 14;
  276. 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>`;
  277. });
  278. });
  279. // 主线箭头 (simplified)
  280. const arrows = [
  281. { x1: 160, y1: 104, x2: 190, y2: 104 }, // 接收→评审
  282. { x1: 320, y1: 104, x2: 350, y2: 104 }, // 评审→工单
  283. { x1: 460, y1: 104, x2: 485, y2: 104 }, // 工单→排程
  284. { x1: 595, y1: 104, x2: 620, y2: 104 }, // 排程→采购
  285. { x1: 595, y1: 123, x2: 540, y2: 155 }, // 采购→生产 (down-left)
  286. { x1: 540, y1: 193, x2: 460, y2: 185 }, // 生产→入库 (left)
  287. { x1: 350, y1: 123, x2: 350, y2: 170 }, // 工单→交期确认 (down)
  288. ];
  289. arrows.forEach(a => {
  290. svg += `<line x1="${a.x1}" y1="${a.y1}" x2="${a.x2}" y2="${a.y2}" stroke="#666" stroke-width="1.2"/>`;
  291. const ang = Math.atan2(a.y2 - a.y1, a.x2 - a.x1);
  292. const lx = a.x2 - 6 * Math.cos(ang), ly = a.y2 - 6 * Math.sin(ang);
  293. 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"/>`;
  294. });
  295. // 灰色标记:虚线边框表示"跨模块就绪,待其他模块补齐"
  296. svg += `<text x="530" y="108" font-family="Microsoft YaHei" font-size="9" fill="#999" text-anchor="middle">(S2/S4/S5/S6/S7)</text>`;
  297. // ── 支撑流程节点 ──
  298. const suppNodes = [
  299. { x: 40, y: 295, w: 160, h: 36, label: '产品设计管理', color: '#E65100' },
  300. { x: 250, y: 295, w: 160, h: 36, label: '合同评审管理', color: '#E65100' },
  301. { x: 460, y: 295, w: 160, h: 36, label: '需求明细核验', color: '#E65100' },
  302. ];
  303. suppNodes.forEach(n => {
  304. 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"/>`;
  305. 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>`;
  306. });
  307. // 支撑箭头
  308. svg += `<line x1="200" y1="313" x2="250" y2="313" stroke="#666" stroke-width="1.2"/>`;
  309. svg += `<polygon points="246,308 246,318 253,313" fill="#666"/>`;
  310. svg += `<line x1="410" y1="313" x2="460" y2="313" stroke="#666" stroke-width="1.2"/>`;
  311. svg += `<polygon points="456,308 456,318 463,313" fill="#666"/>`;
  312. // ── 数据流节点 ──
  313. const dataNodes = [
  314. { x: 40, y: 418, w: 140, h: 36, label: '业务运行表', color: '#2E7D32' },
  315. { x: 210, y: 418, w: 120, h: 36, label: 'mdp_stg_*', color: '#6A1B9A' },
  316. { x: 360, y: 418, w: 120, h: 36, label: 'mdp_std_*', color: '#6A1B9A' },
  317. { x: 510, y: 418, w: 120, h: 36, label: 'dwd_* (宽表)', color: '#0D47A1' },
  318. { x: 660, y: 418, w: 100, h: 36, label: 'KPI 指标层', color: '#B71C1C' },
  319. ];
  320. dataNodes.forEach(n => {
  321. 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"/>`;
  322. 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>`;
  323. });
  324. // 数据箭头
  325. for (let i = 0; i < dataNodes.length - 1; i++) {
  326. const a = dataNodes[i], b = dataNodes[i + 1];
  327. const ax = a.x + a.w, ay = a.y + a.h / 2;
  328. const bx = b.x;
  329. svg += `<line x1="${ax}" y1="${ay}" x2="${bx}" y2="${ay}" stroke="#666" stroke-width="1.2"/>`;
  330. svg += `<polygon points="${bx - 6},${ay - 4} ${bx - 6},${ay + 4} ${bx},${ay}" fill="#666"/>`;
  331. }
  332. // ── 层级标注 ──
  333. svg += `<text x="45" y="475" font-family="Microsoft YaHei" font-size="10" fill="#555">STG(贴源)</text>`;
  334. svg += `<text x="215" y="475" font-family="Microsoft YaHei" font-size="10" fill="#555">STD(标准)</text>`;
  335. svg += `<text x="365" y="475" font-family="Microsoft YaHei" font-size="10" fill="#555">DWD(宽表)</text>`;
  336. svg += `<text x="515" y="475" font-family="Microsoft YaHei" font-size="10" fill="#555">KPI(指标)</text>`;
  337. // ── 串联标注(支撑→主线) ──
  338. svg += `<text x="150" y="240" font-family="Microsoft YaHei" font-size="10" fill="#888" text-anchor="middle">↓ 为订单评审提供设计/合同/物料约束 ↓</text>`;
  339. svg += '</svg>';
  340. return sharp(Buffer.from(svg)).png().toBuffer();
  341. }
  342. // ═══════════════════════════════════════════════════
  343. // 章节构建函数:自动生成 8 子章节
  344. // ═══════════════════════════════════════════════════
  345. // cfg.flowSteps: [{label, role}] → 自动生成纵向流程图
  346. // cfg.flowImage: Buffer → 直接嵌入(优先于 flowSteps)
  347. function buildChapter(num, title, cfg) {
  348. const ch = [];
  349. const pre = `${num}.`;
  350. // ── §X.1 目标/宗旨 ──
  351. ch.push(h2(`${pre}1 目标/宗旨`));
  352. (cfg.targets || []).forEach(t => ch.push(bullet(t)));
  353. // ── §X.2 业务流程图 ──
  354. ch.push(h2(`${pre}2 业务流程图`));
  355. const img = cfg.flowImage || (cfg.flowSteps ? flowImageCache[num] : null);
  356. if (img) {
  357. ch.push(flowImageParagraph(img));
  358. } else {
  359. ch.push(p('(见系统操作流程图 / 附件 Visio 流程图)'));
  360. }
  361. if (cfg.flowNote) ch.push(p(cfg.flowNote));
  362. // ── §X.3 业务流程说明 ──
  363. ch.push(h2(`${pre}3 业务流程说明`));
  364. if (cfg.activities && cfg.activities.length) {
  365. ch.push(activityTable(cfg.activities));
  366. } else {
  367. ch.push(p('参照系统页面操作流程,主要操作步骤包含:'));
  368. (cfg.activityDesc || []).forEach(a => ch.push(bullet(a)));
  369. }
  370. // ── §X.4 业务流程规则 ──
  371. ch.push(h2(`${pre}4 业务流程规则`));
  372. if (cfg.rules && cfg.rules.length) {
  373. ch.push(ruleTable(cfg.rules));
  374. } else {
  375. ch.push(p('无特殊业务规则'));
  376. }
  377. // ── §X.5 变化和改进点 ──
  378. ch.push(h2(`${pre}5 变化和改进点`));
  379. (cfg.improvements || []).forEach(i => ch.push(bullet(i)));
  380. // ── §X.6 权限管理需求 ──
  381. ch.push(h2(`${pre}6 权限管理需求`));
  382. if (cfg.permissions && cfg.permissions.length) {
  383. ch.push(permTable(cfg.permissions));
  384. } else {
  385. ch.push(p('参照第 12 章 权限管理汇总'));
  386. }
  387. // ── §X.7 系统接口集成 ──
  388. ch.push(h2(`${pre}7 系统接口集成`));
  389. if (cfg.interfaces && cfg.interfaces.length) {
  390. ch.push(interfaceTable(cfg.interfaces));
  391. } else {
  392. ch.push(p('无外部系统接口;页面数据通过 Admin.NET 内置 API 获取'));
  393. }
  394. // ── §X.8 报表需求 ──
  395. ch.push(h2(`${pre}8 报表需求`));
  396. if (cfg.reports && cfg.reports.length) {
  397. ch.push(reportTable(cfg.reports));
  398. } else {
  399. ch.push(p('无独立报表需求,相关数据通过看板和列表页面查看'));
  400. }
  401. return ch;
  402. }
  403. // ═══════════════════════════════════════════════════
  404. // 章节数据配置
  405. // ═══════════════════════════════════════════════════
  406. // ── Chapter 2: 产品设计管理 ──
  407. const _ch2cfg = {
  408. targets: [
  409. '实现产品设计任务的在线管理,涵盖产品设计BOM和工艺路线的录入与维护。',
  410. '将产品设计任务纳入 KPI 考核体系,监控设计周期、满足率和人效指标。',
  411. '产品设计采用直接保存模式,无审批流环节,保存即生效。',
  412. ],
  413. flowNote: '流程路径:产品设计新建 → BOM子表录入 → 工艺子表录入 → 保存 → 设计记录生效。',
  414. flowSteps: [
  415. { label: '新建产品设计任务', role: '设计员' },
  416. { label: 'BOM子表录入', role: '设计员' },
  417. { label: '工艺子表录入', role: '设计员' },
  418. { label: '保存生效', role: '设计员' },
  419. ],
  420. activities: [
  421. ['10', '新建产品设计任务', '设计员', '录入产品设计基本信息,选择成品物料编码,填写设计要求', '产品需求', '产品设计任务'],
  422. ['20', 'BOM子表录入', '设计员', '在BOM子表中新增行,选择子物料编码,填写单位用量和损耗率', '物料主数据', 'BOM清单'],
  423. ['30', '工艺子表录入', '设计员', '在工艺子表中新增行,通过下拉框选择产线,手动输入路线编码', '产线数据', '工艺路线'],
  424. ['40', '保存', '设计员', '填写完整信息后点击保存,单据编号自动生成,数据即时生效', '产品设计任务', '已保存记录'],
  425. ],
  426. rules: [
  427. ['1', 'BOM子表物料选择', '选择子物料编码后系统自动填入物料名称;父级物料关系通过任务主表关联;单位用量和固定损耗支持手动编辑'],
  428. ['2', '工艺子表产线选择', '产线列显示下拉框,从LineMaster表远程搜索选择,显示格式为「Line|Describe」;路线编码支持手动输入'],
  429. ['3', '保存/编辑/删除', '保存后列表显示新记录、单据编号自动生成;编辑后字段正确更新;删除后记录移除且关联BOM/工艺子表数据同步删除'],
  430. ['4', '必填字段校验', '物料编码、任务名称、计划完成日期为必填项;BOM子表至少保留一行;前后端双重校验'],
  431. ['5', '无审批流', '产品设计采用直接保存模式,保存即生效,无需经过审批流程;无审批状态列'],
  432. ],
  433. improvements: [
  434. '产品设计任务实现在线化闭环管理,替代线下Excel跟踪方式。',
  435. 'BOM和工艺信息直接录入系统,避免数据孤岛。',
  436. '设计周期、满足率和人效纳入KPI自动化考核。',
  437. '采用直接保存模式,简化操作流程,提升设计效率。',
  438. ],
  439. permissions: [
  440. ['1', '设计员', '新建/编辑/删除产品设计任务;录入BOM和工艺子表;保存操作'],
  441. ['2', '计划工程师', '查看产品设计列表和KPI指标'],
  442. ],
  443. interfaces: [
  444. ['1', '物料主数据同步', '通过MDP从SAP定时同步物料主数据,供产品设计选择使用', '定时(日)'],
  445. ['2', '产线主数据查询', '工艺子表通过API实时查询LineMaster表获取产线列表', '实时(下拉触发)'],
  446. ],
  447. reports: [
  448. ['1', '产品设计周期分析', '统计各设计任务的计划周期与实际完成周期差异', '按任务/按月度'],
  449. ['2', '产品设计满足率', '统计按时完成的设计任务占比', '按月度/按人员'],
  450. ],
  451. };
  452. const ch2 = () => buildChapter('2', '产品设计管理', _ch2cfg);
  453. // ── Chapter 3: 订单评审管理 ──
  454. const _ch3cfg = {
  455. targets: [
  456. '实现销售订单的在线录入、查询、编辑和删除操作。',
  457. '对无法被成品库存直接满足的订单进行交期评审,给出预计交付日期。',
  458. '支持订单变更管理,变更记录可追溯,变更时通过审批流管控。',
  459. '订单主流程无审批流:录入→评审→确认直接完成;仅变更场景触发审批。',
  460. ],
  461. flowNote: '流程路径:订单信息录入 → 订单评审(交期评估) → 交期确认 → 如需变更则发起变更审批流。',
  462. flowSteps: [
  463. { label: '订单信息录入', role: '销售/计划员' },
  464. { label: '订单评审(交期评估)', role: '计划员' },
  465. { label: '交期确认 / 资源锁定', role: '计划员' },
  466. { label: '变更审批(如需要)', role: '审批人' },
  467. ],
  468. activities: [
  469. ['10', '订单列表查询', '销售/计划员', '通过订单号、客户、日期范围等条件筛选查询订单', '查询条件', '订单列表'],
  470. ['20', '订单新增保存', '销售/计划员', '录入销售订单基本信息(客户、物料、数量、交期等)', '销售需求', '待评审订单'],
  471. ['30', '订单评审/交期评估', '计划员', '系统根据成品库存、在制工单、物料齐套约束给出预计交期', '订单信息', '建议交期'],
  472. ['40', '交期确认', '计划员', '确认交期结果,锁定产能和物料资源', '建议交期', '已确认订单'],
  473. ['50', '订单变更(审批流)', '销售/计划员→审批人', '交期确认后如需修改订单,发起变更申请,走审批流;变更记录自动归档并累加次数', '变更需求', '变更记录/审批流'],
  474. ],
  475. rules: [
  476. ['1', '订单编号', '订单编号全局唯一,保存时自动生成'],
  477. ['2', '订单主流程无审批', '订单从录入到评审到交期确认,无需经过审批流;保存和确认操作直接生效'],
  478. ['3', '变更审批流', '交期确认后如需修改订单,必须发起变更申请并通过审批流;变更记录可追溯,变更次数+1'],
  479. ['4', '变更历史可查', '变更历史记录在订单交付Tab2(评审信息)中查看'],
  480. ['5', '订单类型', '订单类型分为「销售订单」和「计划订单」,系统按类型区别处理'],
  481. ['6', '交期确认', '调用外部交期确认接口返回OK表示确认成功;确认后订单状态更新为已确认'],
  482. ['7', 'closed字段', '订单closed字段默认值为0(未关闭),订单关闭时更新为1'],
  483. ],
  484. improvements: [
  485. '订单管理实现在线化,替代线下Excel和纸质单据管理方式。',
  486. '变更记录自动归档,每次变更次数累计,支持历史追踪,审批流仅在变更时触发。',
  487. '交期评估系统化,替代人工估算方式。',
  488. '主流程简化(无审批),仅变更场景走审批流,降低操作复杂度。',
  489. ],
  490. permissions: [
  491. ['1', '销售/客服专员', '查看订单列表;新建/编辑/删除订单;发起变更申请'],
  492. ['2', '计划工程师', '查看订单列表;执行订单评审和交期确认操作'],
  493. ['3', '审批人', '审核订单变更申请(仅变更场景触发)'],
  494. ],
  495. interfaces: [
  496. ['1', '销售订单同步', '通过MDP从SAP定时同步销售订单到中台贴源层mdp_stg_so', '定时(日)'],
  497. ['2', '交期确认接口', '调用外部交期确认接口(不含Authorization头),返回OK表示成功', '手动触发'],
  498. ['3', '订单列表API', 'GET /api/Order/list 读取mdp_std_so标准层数据', '实时(页面加载)'],
  499. ],
  500. reports: [
  501. ['1', '订单评审周期分析', '统计从订单录入到交期确认的各环节耗时', '按月度/按客户'],
  502. ['2', '订单变更频率分析', '统计订单变更次数和变更原因分布', '按月度/按产品线'],
  503. ],
  504. };
  505. const ch3 = () => buildChapter('3', '订单评审管理', _ch3cfg);
  506. // ── Chapter 4: 订单交付管理 ──
  507. const _ch4cfg = {
  508. targets: [
  509. '以订单行(明细)为粒度,集中展示每笔订单的交付状态。',
  510. '通过8个Tab页签实现从"订单接收"到"成品发运"的全链路履约追踪。',
  511. '接入数据中台宽表层dwd_ship_trans,实现交付数据的统一口径。',
  512. ],
  513. flowNote: '流程路径:订单交付列表 → 多条件筛选 → 点击订单行 → 8Tab全景追踪(订单信息→评审→排程→物料需求→采购→备料→生产→入库发运)。',
  514. flowSteps: [
  515. { label: '交付列表筛选查询', role: '计划员/物流' },
  516. { label: '点击订单行进入详情', role: '计划员/物流' },
  517. { label: 'Tab1 查看订单信息', role: '计划员/物流' },
  518. { label: 'Tab2 查看评审/变更', role: '计划员/物流' },
  519. { label: 'Tab4 查看物料需求', role: '计划员/物流' },
  520. { label: 'Tab8 入库发运追踪', role: '计划员/物流' },
  521. ],
  522. activities: [
  523. ['10', '交付列表查询', '计划员/物流', '通过订单号、客户、交付状态、日期范围等条件筛选', '筛选条件', '交付列表数据'],
  524. ['20', '进入交付详情', '计划员/物流', '点击交付列表中的订单行,进入8Tab全景追踪页面', '订单行ID', '交付详情'],
  525. ['30', '查看订单信息(Tab1)', '计划员/物流', '查看订单基础档案信息(订单编号、客户、状态、审核人等)', 'mdp_std_so', '订单档案'],
  526. ['40', '查看评审信息(Tab2)', '计划员/物流', '查看交期评估结果和订单变更历史记录', 'dwd_requirement_examine_detail', '评审详情'],
  527. ['50', '查看物料需求(Tab4)', '计划员/物流', '查看BOM展开、库存占用、缺料分析和替代料信息', 'dwd_requirement_examine_detail', '物料需求明细'],
  528. ],
  529. rules: [
  530. ['1', '交付列表数据来源', '读取dwd_ship_trans宽表,只取最新成功批次(sync_batch_id=max);确保数据口径一致'],
  531. ['2', '交付列表字段', '客户编码、订单号、工单编号、物料编码、物料名称、规格型号、数量、客户单号、计划交付日期、实际交付日期、交付状态'],
  532. ['3', '交付状态流转', '待评审→已评审→排产中→采购中→生产中→已入库→已发运'],
  533. ['4', '8Tab数据来源', 'Tab1读mdp_std_so;Tab2读dwd_requirement_examine_detail+mdp_stg_so变更;Tab4读dwd_requirement_examine_detail;Tab8发运读mdp_std_ship_trans'],
  534. ['5', '跨模块Tab渐进就绪', 'Tab3排程待S2补齐;Tab5采购待S4补齐;Tab6备料待S6/WMS补齐;Tab7报工IQC待S5/S6补齐;Tab8 FQC入库待S7补齐'],
  535. ],
  536. improvements: [
  537. '交付状态一屏展示,替代多地分散查询的方式。',
  538. '8Tab全景追踪实现"一单到底",从订单到发运全链路可视化。',
  539. '接入数据中台宽表,确保各页面读取的数据口径一致。',
  540. '跨模块Tab结构已就绪,后续模块补齐后自动填充数据。',
  541. ],
  542. permissions: [
  543. ['1', '计划工程师', '查看交付列表和详情;筛选和排序操作'],
  544. ['2', '物流/仓管', '查看交付列表;查看发运相关Tab'],
  545. ['3', '销售/客服', '查看交付列表;跟踪订单交付状态'],
  546. ],
  547. interfaces: [
  548. ['1', '订单交付列表API', 'GET /api/Order/delivery/list 读取dwd_ship_trans', '实时(页面加载)'],
  549. ['2', '订单交付详情API', 'GET /api/Order/delivery/flow?entryId= 聚合多表数据', '实时(点击触发)'],
  550. ['3', '需求核验API', 'GET /api/RequirementExamineDetail/list 读取dwd_requirement_examine_detail', '实时'],
  551. ],
  552. reports: [
  553. ['1', '订单交付状态统计', '按交付状态汇总订单数量,分析履约瓶颈', '按日/周/月'],
  554. ['2', '订单交付周期分析', '统计从订单确认到实际交付的各阶段耗时', '按订单/按客户'],
  555. ],
  556. };
  557. const ch4 = () => buildChapter('4', '订单交付管理', _ch4cfg);
  558. // ── Chapter 5: 订单发货管理 ──
  559. const _ch5cfg = {
  560. targets: [
  561. '实现发货单的在线录入、查询、编辑和删除管理。',
  562. '通过销售单号关联,将发货单与销售订单联动。',
  563. '实现发货计划覆盖追踪和ASN发货记录管理。',
  564. '将发货数据纳入KPI考核(发货计划覆盖率、发货达成率)。',
  565. ],
  566. flowNote: '流程路径:发货计划制定 → 发货单新建 → 选择销售单号 → 填写发货信息 → 保存发货单 → 数据进入MDP中台。',
  567. flowSteps: [
  568. { label: '制定发货计划', role: '物流/仓管' },
  569. { label: '新建发货单', role: '物流/仓管' },
  570. { label: '选择关联销售单号', role: '物流/仓管' },
  571. { label: '填写发货明细行', role: '物流/仓管' },
  572. { label: '保存 → MDP数据同步', role: '系统(MDP)' },
  573. ],
  574. activities: [
  575. ['10', '发货单列表查询', '物流/仓管', '通过发货单号、销售单号、客户、日期范围筛选', '查询条件', '发货单列表'],
  576. ['20', '新建发货单', '物流/仓管', '选择销售单号(下拉搜索),选择客户,填写发货信息', '销售订单', '发货单'],
  577. ['30', '发货明细行管理', '物流/仓管', '在明细子表中新增行,填写物料、数量、批次等信息', '发货单', '发货明细'],
  578. ['40', 'ASN发货录入', '物流/仓管', '录入ASN发货单号、物料、发货数量、发货日期', '发货通知', 'ASN记录'],
  579. ['50', '数据同步中台', '系统(MDP)', '发货计划和ASN数据定时同步至mdp_stg_ship_trans,经标准化后进入dwd_ship_trans', '运行时表', '中台宽表'],
  580. ],
  581. rules: [
  582. ['1', '发货单号自动生成', '保存时自动生成,格式为SH+日期+流水号'],
  583. ['2', '销售单号下拉框', '支持远程搜索,回显格式为「BillNo|客户名」'],
  584. ['3', '客户下拉框', '支持远程搜索选择客户'],
  585. ['4', '明细行管理', '支持增删行;物料选择后自动填入物料名称;至少保留一行'],
  586. ['5', '已发货不可删除', '已发货的发货单不可删除,防止数据丢失'],
  587. ['6', '发货计划覆盖', '发货计划与销售订单关联,用于计算S1_L2_013发货计划覆盖率'],
  588. ['7', 'ASN发货追踪', 'ASN发货记录进入mdp_std_ship_trans,用于计算S1_L2_014发货达成率'],
  589. ],
  590. improvements: [
  591. '发货单实现在线管理,替代线下纸质单据流转。',
  592. '发货单号和销售单号自动关联,数据联动减少重复录入。',
  593. '发货数据和KPI自动对接,发货计划覆盖率和发货达成率自动计算。',
  594. ],
  595. permissions: [
  596. ['1', '物流/仓管', '新建/编辑/删除发货单和ASN发货单;管理发货明细行'],
  597. ['2', '计划工程师', '查看发货列表和发货KPI指标'],
  598. ],
  599. interfaces: [
  600. ['1', '发货数据同步', '运行时表ShippingPlan→mdp_stg_ship_trans→mdp_std_ship_trans→dwd_ship_trans', '定时(日)'],
  601. ['2', 'ASN数据同步', '运行时表ASNBOLShipper→mdp_stg_ship_trans→mdp_std_ship_trans', '定时(日)'],
  602. ],
  603. reports: [
  604. ['1', '发货计划覆盖率', 'S1_L2_013:统计有发货计划覆盖的订单占比', '按月度/按客户'],
  605. ['2', '发货达成率', 'S1_L2_014:统计实际发货与计划发货的一致率', '按月度/按产品线'],
  606. ],
  607. };
  608. const ch5 = () => buildChapter('5', '订单发货管理', _ch5cfg);
  609. // ── Chapter 6: 合同评审管理 ──
  610. const _ch6cfg = {
  611. targets: [
  612. '实现客户合同的在线评审管理,与销售订单评审协同运作。',
  613. '通过审批流管控合同评审的多级审核流程。',
  614. '将合同评审数据纳入KPI考核体系(周期、满足率、人效及L3/L4节点指标)。',
  615. ],
  616. flowNote: '流程路径:合同录入 → 合同评审列表 → 提交审批 → 审批流流转 → 审核通过/驳回 → KPI计算。',
  617. flowSteps: [
  618. { label: '合同录入', role: '商务人员' },
  619. { label: '查看合同评审列表', role: '商务人员' },
  620. { label: '提交审批流', role: '商务人员' },
  621. { label: '多级审批流转', role: '审批人' },
  622. { label: '审核通过/驳回→KPI计算', role: '系统' },
  623. ],
  624. activities: [
  625. ['10', '合同评审列表查询', '商务人员', '查看合同评审列表,支持多条件筛选', '查询条件', '合同评审列表'],
  626. ['20', '新建合同评审', '商务人员', '录入合同基本信息(合同编号、客户、签订日期等)', '合同信息', '待评审合同'],
  627. ['30', '提交审批', '商务人员', '提交合同进入审批流(审批流类型:CONTRACT_REVIEW)', '待评审合同', '审批中合同'],
  628. ['40', '审批流流转', '审批人', '多级审批节点流转,同意或驳回', '审批中合同', '已完成/已驳回'],
  629. ],
  630. rules: [
  631. ['1', '审批流类型', '使用CONTRACT_REVIEW审批流类型,通过IFlowBizHandler接口回调处理业务逻辑'],
  632. ['2', 'KPI关联', '合同评审周期(S1_L2_001)、满足率(S1_L2_002)、人效(S1_L2_003)自动计算'],
  633. ['3', 'L3/L4节点指标', '合同评审流程每个审批节点耗时单独统计(S1_L3_001~005、S1_L4_001~004)'],
  634. ['4', '数据链路', '运行时表ado_contract_review→mdp_stg_so贴源→KPI直接消费贴源层计算'],
  635. ],
  636. improvements: [
  637. '合同评审实现线上审批,替代纸质审批流程。',
  638. '合同评审周期和节点耗时自动统计,支持流程优化分析。',
  639. '与销售订单评审数据协同,形成完整的评审闭环。',
  640. ],
  641. permissions: [
  642. ['1', '商务人员', '新建/编辑合同评审;提交审批'],
  643. ['2', '审批人', '多级审核合同(同意/驳回)'],
  644. ['3', '计划工程师', '查看合同评审列表和KPI指标'],
  645. ],
  646. interfaces: [
  647. ['1', '合同评审数据同步', '运行时表ado_contract_review→mdp_stg_so→KPI计算', '定时(日)'],
  648. ],
  649. reports: [
  650. ['1', '合同评审周期分析', 'S1_L2_001:统计合同审批平均周期', '按月度'],
  651. ['2', '合同评审满足率', 'S1_L2_002:统计合同评审通过率', '按月度'],
  652. ['3', '合同节点耗时统计', 'S1_L3/L4:统计各审批节点耗时分布', '按节点/按月度'],
  653. ],
  654. };
  655. const ch6 = () => buildChapter('6', '合同评审管理', _ch6cfg);
  656. // ── Chapter 7: 计划联动 ──
  657. const _ch7cfg = {
  658. targets: [
  659. '实现销售订单、发货计划、ASN发货等业务数据的联动整合。',
  660. '通过MDP同步转换,将多源数据汇聚到dwd_ship_trans宽表。',
  661. '提供手动刷新机制,支持业务人员触发最新的数据联动计算。',
  662. ],
  663. flowNote: '流程路径:计划联动列表 → 查看联动状态 → 点击刷新按钮 → 触发S1_MDP_SYNC_TRANSFORM作业 → 数据联动更新 → 返回最新联动结果。',
  664. flowSteps: [
  665. { label: '查看联动列表/状态', role: '计划员' },
  666. { label: '点击手动刷新按钮', role: '计划员' },
  667. { label: 'MDP执行全量同步转换', role: '系统(MDP)' },
  668. { label: 'STG→STD→DWD→KPI 链路', role: '系统(MDP)' },
  669. { label: '返回最新联动结果', role: '系统' },
  670. ],
  671. activities: [
  672. ['10', '查看联动列表', '计划员', '查看各业务对象的联动状态(订单/发货/ASN)', '—', '联动列表'],
  673. ['20', '手动刷新', '计划员', '点击刷新按钮,触发S1_MDP_SYNC_TRANSFORM全量同步转换', '刷新请求', '最新联动数据'],
  674. ['30', 'MDP同步转换', '系统(MDP)', '执行STG贴源→STD标准化→DWD宽表构建→KPI计算全链路', 'S1_MDP作业', '更新后数据'],
  675. ['40', '查看联动结果', '计划员', '刷新完成后查看最新的联动达成率和数据一致性状态', 'MDP结果', '联动报告'],
  676. ],
  677. rules: [
  678. ['1', '作业编码', 'S1_MDP_SYNC_TRANSFORM,批次格式S1_MDP_FULL_yyyyMMddHHmmss'],
  679. ['2', '联动数据源', '订单(crm_seorder)、发货计划(ShippingPlan)、ASN(ASNBOLShipper)、联动(LinkagePlan)'],
  680. ['3', '联动状态', '联动成功/未联动/数据异常三种状态'],
  681. ['4', '刷新方式', '支持手动触发;未来可扩展定时调度'],
  682. ['5', '监控入口', 'mdp_transform_run_log记录作业状态;mdp_sync_log记录实体同步日志'],
  683. ['6', 'KPI关联', 'S1_L2_015计划联动达成率自动计算'],
  684. ],
  685. improvements: [
  686. '多源数据自动联动,替代人工Excel手工比对方式。',
  687. '手动刷新机制灵活可控,业务人员可根据需要随时触发。',
  688. '作业日志可观测,异常可追溯。',
  689. ],
  690. permissions: [
  691. ['1', '计划工程师', '查看联动列表;执行手动刷新操作;查看联动KPI'],
  692. ['2', '系统管理员', '监控MDP作业状态;处理执行异常'],
  693. ],
  694. interfaces: [
  695. ['1', '手动刷新API', 'POST /api/Order/linkageplan/refresh 触发全量同步转换', '手动触发'],
  696. ['2', 'MDP作业监控', 'mdp_transform_run_log/mdp_sync_log记录作业状态', '作业执行时写入'],
  697. ['3', '联动数据同步', 'LinkagePlan运行数据→mdp_stg_ship_trans→mdp_std_ship_trans→dwd_ship_trans', '定时(日)/手动'],
  698. ],
  699. reports: [
  700. ['1', '计划联动达成率', 'S1_L2_015:统计联动成功的业务对象占比', '按批次/按日'],
  701. ['2', 'MDP作业状态监控', '查看每次MDP转换作业的执行状态、处理行数和耗时', '按批次'],
  702. ],
  703. };
  704. const ch7 = () => buildChapter('7', '计划联动', _ch7cfg);
  705. // ── Chapter 8: 需求明细核验 ──
  706. const _ch8cfg = {
  707. targets: [
  708. '展示销售订单的BOM需求展开与核验结果。',
  709. '帮助计划人员确认物料需求数量和到货时间的准确性。',
  710. '核验数据服务于订单交付Tab2评审和Tab4物料需求。',
  711. ],
  712. flowNote: '流程路径:需求核验列表 → 多条件筛选 → 查看核验结果 → 核验结果同步展示在订单交付Tab2和Tab4。',
  713. flowSteps: [
  714. { label: '核验列表筛选查询', role: '计划员' },
  715. { label: '查看BOM需求展开', role: '计划员' },
  716. { label: '确认库存占用/缺料', role: '计划员' },
  717. { label: '数据同步到交付Tab2/Tab4', role: '系统' },
  718. ],
  719. activities: [
  720. ['10', '核验列表查询', '计划员', '通过订单号、物料编码、核验状态等条件筛选', '筛选条件', '核验列表'],
  721. ['20', '查看核验明细', '计划员', '查看订单行的BOM需求展开、库存占用、缺料分析和替代料信息', '核验结果', '需求明细'],
  722. ['30', '同步订单交付', '系统', '核验结果自动同步到订单交付详情Tab2和Tab4', '核验数据', '交付详情'],
  723. ],
  724. rules: [
  725. ['1', '数据来源', '运行时表b_examine_result/b_bom_child_examine→mdp_stg_so→dwd_requirement_examine_detail'],
  726. ['2', '核验内容', '物料编码、BOM编码、需求数量、缺料数量、替代料方案、满足时间'],
  727. ['3', '数据过滤', '只取最新成功批次的核验数据,确保数据一致性'],
  728. ['4', '跨页面复用', '核验结果同时在需求核验页面和订单交付Tab2/Tab4中使用'],
  729. ],
  730. improvements: [
  731. '需求核验数据在线化,替代手工Excel核对方式。',
  732. '核验结果跨页面共享,避免数据重复计算。',
  733. '接入中台DWD层,数据口径统一。',
  734. ],
  735. permissions: [
  736. ['1', '计划工程师', '查看需求核验列表;筛选和排序操作'],
  737. ],
  738. interfaces: [
  739. ['1', '需求核验API', 'GET /api/RequirementExamineDetail/list 读取dwd_requirement_examine_detail', '实时(页面加载)'],
  740. ['2', '核验数据同步', 'b_examine_result→mdp_stg_so→dwd_requirement_examine_detail', '定时(日)'],
  741. ],
  742. reports: [],
  743. };
  744. const ch8 = () => buildChapter('8', '需求明细核验', _ch8cfg);
  745. // ── Chapter 9: 工单下达 ──
  746. const _ch9cfg = {
  747. targets: [
  748. '管理生产工单的下达,是产销协同到制造执行的衔接环节。',
  749. '下达前必须执行物料齐套检查和物料需求同步(生成MRP),确保物料准备到位。',
  750. '工单逐单下达(非批量),每行操作列独立触发,下达后MES系统可见。',
  751. '支持工单状态的全程跟踪。',
  752. ],
  753. flowNote: '流程路径:工单列表 → 物料齐套检查(工具栏按钮) → 生成物料需求/同步MRP → 点击单行[工单下达]按钮 → 填写下达信息确认 → 工单状态变更 → MES可见。',
  754. flowSteps: [
  755. { label: '工单列表筛选查询', role: '计划员/调度' },
  756. { label: '物料齐套检查', role: '计划员/调度' },
  757. { label: '生成物料需求(MRP同步)', role: '计划员/调度' },
  758. { label: '逐行点击工单下达', role: '计划员/调度' },
  759. { label: '确认下达→状态变更→MES可见', role: '系统' },
  760. ],
  761. activities: [
  762. ['10', '工单列表查询', '计划员/调度', '通过工单号、物料编码、开工日期、状态筛选', '查询条件', '工单列表'],
  763. ['20', '物料齐套检查', '计划员/调度', '点击工具栏"物料齐套检查"按钮,调用外部资源审查服务检查各工单物料齐套状态', '工单列表', '齐套状态列更新'],
  764. ['30', '生成物料需求', '计划员/调度', '点击工具栏"生成物料需求"按钮,同步生成物料需求计划(MRP)', '工单列表', '物料需求计划生成'],
  765. ['40', '工单下达(逐单)', '计划员/调度', '点击单行操作列"工单下达"按钮,弹出下达表单填写开工日期和生产批号,确认后工单状态变更为"已下达"', '单行工单', '已下达工单'],
  766. ['50', '工单状态跟踪', '计划员/调度', '按状态筛选工单,跟踪工单从下达到完成的全过程', '工单列表', '状态跟踪'],
  767. ],
  768. rules: [
  769. ['1', '工单状态', '待下达→已下达→生产中→已完成→已关闭;下达前状态为"待下达"'],
  770. ['2', '防重复下达', '已下达工单不可重复下达;前端按钮置灰+后端校验双重保障'],
  771. ['3', '逐单下达', '工单逐行单个下达,操作列每行独立[工单下达]按钮,不支持批量一键下达'],
  772. ['4', '物料齐套检查', '下达前必须先通过工具栏"物料齐套检查"确认物料准备状态;调用外部资源审查服务'],
  773. ['5', '物料需求同步', '下达前需按需通过工具栏"生成物料需求"同步生成MRP物料需求计划'],
  774. ['6', '下游联动', '下达后MES系统可见,进入S6生产执行阶段'],
  775. ['7', '工单编码', '工单号全局唯一,由上游系统生成'],
  776. ],
  777. improvements: [
  778. '工单下达实现在线逐单操作,替代线下手工派发方式。',
  779. '物料齐套检查与物料需求同步(MRP生成)前置化,确保下达前物料准备到位。',
  780. '工单状态实时跟踪,下游系统联动自动化。',
  781. '防重复下达机制保障数据安全。',
  782. ],
  783. permissions: [
  784. ['1', '计划员/调度', '查看工单列表;执行工单下达操作;跟踪工单状态'],
  785. ],
  786. interfaces: [
  787. ['1', '工单下达列表API', 'GET /api/WorkOrder/dispatch/list', '实时(页面加载)'],
  788. ['2', '物料齐套检查API', 'GET 外部资源审查服务(前端直连)', '手动触发(工具栏)'],
  789. ['3', '生成物料需求API', 'GET 外部MRP服务(前端直连)', '手动触发(工具栏)'],
  790. ['4', '工单下达操作API', 'POST /api/WorkOrder/dispatch/release', '手动触发(逐行下达)'],
  791. ['5', '工单状态同步MES', '工单下达后状态同步至MES系统', '实时(下达触发)'],
  792. ],
  793. reports: [
  794. ['1', '工单下达及时率', '统计计划下达时间和工单下达时间差异', '按周/按月'],
  795. ],
  796. };
  797. const ch9 = () => buildChapter('9', '工单下达', _ch9cfg);
  798. // ── Chapter 10: 产销协同看板与KPI ──
  799. const _ch10cfg = {
  800. targets: [
  801. '为管理层和业务人员提供S1模块的核心KPI可视化看板。',
  802. '通过L1/L2/L3/L4多层级指标全面监控产销协同健康度。',
  803. '支持按客户维度筛选,灵活下钻分析。',
  804. ],
  805. flowNote: '流程路径:产销协同看板入口 → L1核心指标卡片 → 趋势图表 → 客户筛选 → 指标下钻 → L2/L3/L4分解指标。',
  806. flowSteps: [
  807. { label: '进入S1产销协同看板', role: '管理层/计划员' },
  808. { label: '查看L1核心KPI卡片', role: '管理层/计划员' },
  809. { label: '客户维度筛选过滤', role: '管理层/计划员' },
  810. { label: '指标下钻L2→L3→L4', role: '管理层/计划员' },
  811. { label: '趋势图表分析', role: '管理层/计划员' },
  812. ],
  813. activities: [
  814. ['10', '查看看板', '管理层/计划员', '进入S1产销协同看板,观察L1核心指标卡片', '—', 'L1指标展示'],
  815. ['20', '客户筛选', '管理层/计划员', '选择客户维度,看板数据按客户过滤刷新', '客户选择', '筛选后数据'],
  816. ['30', '指标下钻', '管理层/计划员', '点击指标卡片下钻查看L2分解指标详情', 'L1指标', 'L2指标详情'],
  817. ['40', '趋势分析', '管理层/计划员', '查看指标周期趋势图表,分析变化规律', '历史数据', '趋势图表'],
  818. ],
  819. rules: [
  820. ['1', '组件架构', '使用DynamicModuleDashboard动态看板组件,module-code="S1"'],
  821. ['2', 'L1核心指标', '订单及时交付率(S1_L1_001)、订单交付周期(S1_L1_002)、订单评审完成率(S1_L1_003)、成品库存周转天数(S1_L1_004)'],
  822. ['3', 'L2分解指标', '合同评审3项(S1_L2_001~003)、产品设计3项(S1_L2_004~006)、交期评审3项(S1_L2_010~012)、发货联动3项(S1_L2_013~015)'],
  823. ['4', 'L3/L4节点指标', '合同评审各审批节点的周期和通过率(S1_L3_001~005/101~105、S1_L4_001~004/101~104)'],
  824. ['5', '指标存储', '指标定义写入ado_smart_ops_kpi_master;计算结果写入ado_s9_kpi_value_l1~l4_day'],
  825. ['6', '刷新方式', '通过MDP作业S1_MDP_SYNC_TRANSFORM定时或手动刷新KPI数据'],
  826. ['7', 'S1_L1_004口径说明', '当前为数量代理口径(基于dwd_ship_trans),正式成本口径待S7成品库存快照和成本数据确认后升级'],
  827. ],
  828. improvements: [
  829. '实现S1模块KPI在线可视化和实时监控。',
  830. '多层级指标下钻,支持从L1到L4逐层分析。',
  831. 'KPI数据自动计算,替代人工Excel统计。',
  832. ],
  833. permissions: [
  834. ['1', '管理层', '查看S1看板全部指标'],
  835. ['2', '计划工程师', '查看S1看板;按客户维度筛选分析'],
  836. ['3', '销售/商务', '查看S1看板(本部门相关指标)'],
  837. ],
  838. interfaces: [
  839. ['1', '看板聚合API', 'GET /api/AidopKanban/module-detail?moduleCode=S1', '实时(页面加载)'],
  840. ['2', 'KPI数据源', 'ado_s9_kpi_value_l1~l4_day 按天粒度存储', 'MDP定时刷新'],
  841. ['3', '手动刷新(计划联动)', 'POST /api/Order/linkageplan/refresh 触发S1全链路刷新', '手动触发'],
  842. ],
  843. reports: [],
  844. };
  845. const ch10 = () => buildChapter('10', '产销协同看板与KPI', _ch10cfg);
  846. // ── Chapter 11: 数据中台架构 ──
  847. function buildCh11() {
  848. const ch = [];
  849. ch.push(h1('11 数据中台架构'));
  850. ch.push(h2('11.1 目标/宗旨'));
  851. ch.push(p('S1产销协同模块采用Ai-DOP统一数据中台分层架构,将业务运行数据经过贴源(STG)、标准化(STD)、宽表构建(DWD)、指标计算(KPI)四层处理,最终服务于前端页面查询、看板展示和KPI考核。'));
  852. ch.push(h2('11.2 分层设计'));
  853. ch.push(table(
  854. ['分层', '表前缀', '职责', '更新机制'],
  855. [
  856. ['贴源层(STG)', 'mdp_stg_*', '保留源系统原始数据,含raw_data/source_table/sync_batch_id', 'MDP同步覆盖写入'],
  857. ['标准层(STD)', 'mdp_std_*', '统一字段口径,清洗脏数据,形成标准业务对象', 'MDP转换(取最新批次)'],
  858. ['宽表层(DWD)', 'dwd_*', '面向页面和KPI的主题事实表,关联多源数据', 'MDP转换(取最新批次)'],
  859. ['指标层(KPI)', 'ado_s9_kpi_value_*', '按天存储L1-L4指标值', 'MDP KPI计算(取最新批次)'],
  860. ],
  861. [1500, 2000, 4200, 2300]
  862. ));
  863. ch.push(h2('11.3 S1核心数据流'));
  864. ch.push(bp('订单/评审/设计链路'));
  865. ch.push(p('crm_seorder / ado_contract_review / ado_product_design / b_examine_result'));
  866. ch.push(p(' → mdp_stg_so(贴源层)→ mdp_std_so(标准层)→ dwd_ship_trans / dwd_requirement_examine_detail(宽表)→ ado_s9_kpi_value_*(指标层)'));
  867. ch.push(bp('发货/ASN/联动链路'));
  868. ch.push(p('ShippingPlan / ASNBOLShipper / LinkagePlan'));
  869. ch.push(p(' → mdp_stg_ship_trans(贴源层)→ mdp_std_ship_trans(标准层)→ dwd_ship_trans(宽表)→ ado_s9_kpi_value_*(指标层)'));
  870. ch.push(h2('11.4 对象映射总表'));
  871. ch.push(table(
  872. ['业务对象', '运行表', '贴源层', '标准层/DWD', '消费端'],
  873. [
  874. ['销售订单', 'crm_seorder', 'mdp_stg_so', 'mdp_std_so / dwd_ship_trans', '订单列表、交付、KPI'],
  875. ['订单变更', 'crm_seorder_change', 'mdp_stg_so', '关联mdp_std_so', 'Tab2变更记录'],
  876. ['合同评审', 'ado_contract_review', 'mdp_stg_so', 'KPI直接消费', 'S1_L2_001~003'],
  877. ['产品设计', 'ado_product_design', 'mdp_stg_so', 'KPI直接消费', 'S1_L2_004~006'],
  878. ['发货计划', 'ShippingPlan', 'mdp_stg_ship_trans', 'mdp_std_ship_trans / dwd_ship_trans', 'S1_L2_013'],
  879. ['ASN发货', 'ASNBOLShipper', 'mdp_stg_ship_trans', 'mdp_std_ship_trans / dwd_ship_trans', 'S1_L2_014'],
  880. ['计划联动', 'LinkagePlan', 'mdp_stg_ship_trans', 'mdp_std_ship_trans / dwd_ship_trans', 'S1_L2_015'],
  881. ['需求核验', 'b_examine_result', 'mdp_stg_so', 'dwd_requirement_examine_detail', '核验页/Tab2/Tab4'],
  882. ],
  883. [1100, 1800, 1800, 2600, 2700]
  884. ));
  885. ch.push(h2('11.5 MDP作业调度'));
  886. ch.push(table(
  887. ['配置项', '内容'],
  888. [
  889. ['作业编码', 'S1_MDP_SYNC_TRANSFORM'],
  890. ['批次格式', 'S1_MDP_FULL_yyyyMMddHHmmss'],
  891. ['手动刷新入口', 'POST /api/Order/linkageplan/refresh'],
  892. ['监控入口', 'mdp_transform_run_log(作业日志)、mdp_sync_log(同步日志)、前端MDP监控页'],
  893. ['执行方式', '手动触发 + 未来扩展定时调度'],
  894. ],
  895. [2200, 7800]
  896. ));
  897. ch.push(h2('11.6 变化和改进点'));
  898. ch.push(bullet('建立统一数据中台分层架构,业务数据从"各自为政"收敛到标准数据链路。'));
  899. ch.push(bullet('页面读取统一从DWD/KPI层消费,确保数据口径一致。'));
  900. ch.push(bullet('MDP作业可观测,支持日志查询和异常追溯。'));
  901. return ch;
  902. }
  903. // ── Chapter 12: 权限管理汇总 ──
  904. function buildCh12() {
  905. const ch = [];
  906. ch.push(h1('12 权限管理汇总'));
  907. ch.push(p('以下汇总S1产销协同模块各功能所涉及的角色和权限:'));
  908. ch.push(table(
  909. ['序号', '角色', '权限范围', '涉及模块'],
  910. [
  911. ['1', '销售/客服专员', '查看销售订单列表、订单交付列表;新建/编辑/删除订单;发起订单变更申请', '订单评审、订单交付'],
  912. ['2', '计划工程师', '查看和操作订单评审、工单下达、计划联动、需求核验;执行交期确认;查看KPI看板', '所有模块(操作权限)'],
  913. ['3', '商务人员', '新建/编辑合同评审;提交审批;查看订单交付', '合同评审'],
  914. ['4', '设计员', '新建/编辑/删除产品设计任务;录入BOM和工艺子表;保存操作', '产品设计'],
  915. ['5', '物流/仓管', '新建/编辑/删除发货单和ASN发货单;管理发货明细行;查看订单交付', '订单发货'],
  916. ['6', '管理层', '查看S1看板全部指标', '看板与KPI'],
  917. ['7', '审批人', '审核订单变更申请/合同评审的审批流', '订单评审(变更)、合同评审'],
  918. ['8', '系统管理员', '管理用户角色权限;监控MDP作业状态;数据修复操作', '系统配置'],
  919. ],
  920. [600, 1800, 5000, 2600]
  921. ));
  922. return ch;
  923. }
  924. // ═══════════════════════════════════════════════════
  925. // 文档构建主流程
  926. // ═══════════════════════════════════════════════════
  927. async function buildDocument() {
  928. // ── 预生成所有流程图 PNG ──
  929. console.log('Generating flow chart images...');
  930. flowImageCache['overview'] = await generateOverviewFlowPNG();
  931. // 收集所有章节的 flowSteps 定义并预生成流程图
  932. const allChapterConfigs = {
  933. '2': _ch2cfg, '3': _ch3cfg, '4': _ch4cfg, '5': _ch5cfg,
  934. '6': _ch6cfg, '7': _ch7cfg, '8': _ch8cfg, '9': _ch9cfg, '10': _ch10cfg,
  935. };
  936. for (const [num, cfg] of Object.entries(allChapterConfigs)) {
  937. if (cfg.flowSteps) {
  938. flowImageCache[num] = await generateFlowPNG(cfg.flowSteps);
  939. }
  940. }
  941. console.log('Flow chart images generated.');
  942. const sections = [];
  943. // ── 封面 ──
  944. sections.push(empty(), empty(), empty());
  945. sections.push(new Paragraph({
  946. children: [new TextRun({ text: 'S1 产销协同模块', bold: true, size: 52, font: FONT, color: '1F4E79' })],
  947. alignment: AlignmentType.CENTER, spacing: { after: 100 },
  948. }));
  949. sections.push(new Paragraph({
  950. children: [new TextRun({ text: '蓝图设计方案', bold: true, size: 52, font: FONT, color: '1F4E79' })],
  951. alignment: AlignmentType.CENTER, spacing: { after: 600 },
  952. }));
  953. sections.push(new Paragraph({
  954. children: [new TextRun({ text: 'Ai-DOP 智慧运营决策平台', size: 28, font: FONT, color: '4472C4' })],
  955. alignment: AlignmentType.CENTER, spacing: { after: 120 },
  956. }));
  957. sections.push(new Paragraph({
  958. children: [new TextRun({ text: '版本:V2.0 日期:2026年6月8日 作者:彭熙玉', size: 22, font: FONT, color: '808080' })],
  959. alignment: AlignmentType.CENTER, spacing: { after: 2400 },
  960. }));
  961. sections.push(new Paragraph({
  962. children: [new TextRun({ text: '【机密文档 · 仅限项目内部使用】', size: 21, font: FONT, color: 'C00000', italics: true })],
  963. alignment: AlignmentType.CENTER,
  964. }));
  965. // ── 文档控制 ──
  966. sections.push(pageBreak(), h1('文档控制'));
  967. sections.push(h2('更改记录'));
  968. sections.push(table(['日期', '姓名', '版本', '变更说明'],
  969. [['2026/06/08', '彭熙玉', 'V1.0', '初版(基于DOP整体方案设计)'],
  970. ['2026/06/08', '彭熙玉', 'V2.0', '根据系统实际S1模块实现重写,聚焦产销协同范围,按模板格式补充8子章节']],
  971. [1800, 1200, 1000, 6000]));
  972. sections.push(empty());
  973. sections.push(h2('审核'));
  974. sections.push(table(['姓名', '职位', '签字/日期'], [['', '', ''], ['', '', '']], [3000, 3000, 4000]));
  975. sections.push(empty());
  976. sections.push(h2('发布'));
  977. sections.push(table(['编号', '名称', '地点'], [['', '', '']], [3000, 4000, 3000]));
  978. // ── 目录 ──
  979. sections.push(pageBreak(), h1('目录'));
  980. sections.push(...buildTOC());
  981. sections.push(pageBreak());
  982. // ── 第1章: 总体业务方案 ──
  983. sections.push(h1('1 总体业务方案'));
  984. sections.push(h2('1.1 目标和宗旨'));
  985. sections.push(bullet('S1产销协同模块是Ai-DOP平台订单履约链路的起点,承担"从订单到交付"的产销协同管理职能。'));
  986. sections.push(bullet('指导销售订单从接收→评审→排产→采购→生产→发运的全流程在线化协同。'));
  987. sections.push(bullet('通过KPI指标体系监控产销协同健康度,驱动业务流程持续改善。'));
  988. sections.push(h2('1.2 总体业务流程图'));
  989. sections.push(p('S1产销协同模块覆盖从"产品设计"到"成品发运"的订单履约全链路,下图展示了主线流程(订单履约链)、支撑流程和底层数据中台链路的整体关系:'));
  990. // 嵌入总体流程图(在 buildDocument 头部预生成)
  991. sections.push(flowImageParagraph(flowImageCache['overview']));
  992. sections.push(h2('1.3 方案设计'));
  993. sections.push(p('S1模块作为Ai-DOP S0-S9模块化架构中的"产销协同"功能域,覆盖以下核心功能模块:'));
  994. sections.push(bullet('产品设计管理:新产品开发中BOM和工艺路线的在线录入与保存,无审批流'));
  995. sections.push(bullet('订单评审管理:销售订单的CRUD、交期评估与确认;变更场景走审批流'));
  996. sections.push(bullet('订单交付管理:8Tab全景追踪,从订单信息到成品发运的全链路可视化'));
  997. sections.push(bullet('订单发货管理:发货计划、ASN发货、计划联动的在线管理'));
  998. sections.push(bullet('合同评审管理:客户合同的多级审批与KPI考核'));
  999. sections.push(bullet('需求明细核验:BOM需求展开与核验结果的在线查看'));
  1000. sections.push(bullet('工单下达:生产工单的物料齐套检查、MRP同步与逐单下达'));
  1001. sections.push(bullet('产销协同看板:L1/L2/L3/L4多层级KPI可视化监控'));
  1002. sections.push(p('数据层面,S1采用数据中台分层架构,业务运行数据通过MDP同步转换作业沉淀到中台表,前端页面和看板统一消费中台数据,确保数据口径一致。跨模块依赖的Tab数据(排程、采购、备料、报工IQC、FQC入库)界面结构已就绪,待S2/S3/S4/S5/S6/S7模块补齐后自动填充。'));
  1003. // ── 第2~10章: 各功能模块 ──
  1004. const moduleChapters = [ch2, ch3, ch4, ch5, ch6, ch7, ch8, ch9, ch10];
  1005. for (const chFn of moduleChapters) {
  1006. sections.push(pageBreak());
  1007. sections.push(h1(chFn().find(el => {
  1008. // extract the chapter title from h2 children
  1009. if (el.root && el.root[0] && el.root[0].children) {
  1010. return chFn.name;
  1011. }
  1012. return false;
  1013. })));
  1014. // Actually, the above approach is wrong. Let me just push the results.
  1015. }
  1016. // Let me restructure - push each chapter directly
  1017. sections.push(pageBreak()); sections.push(h1('2 产品设计管理')); sections.push(...ch2());
  1018. sections.push(pageBreak()); sections.push(h1('3 订单评审管理')); sections.push(...ch3());
  1019. sections.push(pageBreak()); sections.push(h1('4 订单交付管理')); sections.push(...ch4());
  1020. sections.push(pageBreak()); sections.push(h1('5 订单发货管理')); sections.push(...ch5());
  1021. sections.push(pageBreak()); sections.push(h1('6 合同评审管理')); sections.push(...ch6());
  1022. sections.push(pageBreak()); sections.push(h1('7 计划联动')); sections.push(...ch7());
  1023. sections.push(pageBreak()); sections.push(h1('8 需求明细核验')); sections.push(...ch8());
  1024. sections.push(pageBreak()); sections.push(h1('9 工单下达')); sections.push(...ch9());
  1025. sections.push(pageBreak()); sections.push(h1('10 产销协同看板与KPI')); sections.push(...ch10());
  1026. // ── 第11~12章 ──
  1027. sections.push(pageBreak()); sections.push(...buildCh11());
  1028. sections.push(pageBreak()); sections.push(...buildCh12());
  1029. return sections;
  1030. }
  1031. // ═══════════════════════════════════════════════════
  1032. // 生成文档
  1033. // ═══════════════════════════════════════════════════
  1034. async function main() {
  1035. const doc = new Document({
  1036. styles: { default: { document: { run: { font: FONT, size: 21 } } } },
  1037. sections: [{
  1038. properties: { page: { margin: { top: convertInchesToTwip(0.8), bottom: convertInchesToTwip(0.8), left: convertInchesToTwip(1.0), right: convertInchesToTwip(1.0) } } },
  1039. children: await buildDocument(),
  1040. }],
  1041. });
  1042. const buffer = await Packer.toBuffer(doc);
  1043. const outPath = 'd:\\DEMONET\\doc\\S1产销协同模块蓝图设计方案.docx';
  1044. const fallbackPath = 'd:\\DEMONET\\doc\\S1产销协同模块蓝图设计方案_V2.docx';
  1045. const tempPath = 'd:\\DEMONET\\doc\\S1_blueprint_temp.docx';
  1046. fs.writeFileSync(tempPath, buffer);
  1047. // 先尝试写入主文件名,若被占用则写入 V2 副本
  1048. try { fs.unlinkSync(outPath); } catch(e) { /* 被占用则跳过 */ }
  1049. try { fs.renameSync(tempPath, outPath); console.log(`Renamed to: ${outPath}`); }
  1050. catch(e) {
  1051. // 主文件被锁定,写入 V2 副本
  1052. try { fs.unlinkSync(fallbackPath); } catch(_) {}
  1053. try { fs.renameSync(tempPath, fallbackPath); console.log(`Fallback to: ${fallbackPath}`); }
  1054. catch(e2) { console.log(`Generated: ${tempPath} (target locked)`); }
  1055. }
  1056. console.log(`Size: ${(buffer.length / 1024).toFixed(1)} KB`);
  1057. }
  1058. main().catch(err => { console.error('Error:', err); process.exit(1); });