| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902 |
- /**
- * 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); });
|