|
|
@@ -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); });
|
|
|
+
|