轻制造SaaS的生产闭环建模-BOM工单领料报工质检与入库

轻制造 SaaS 的生产闭环建模:BOM、工单、领料、报工、质检与入库

本文是轻制造 MRP Lite 系列的第二篇,重点讨论领域建模。文中模型已做抽象处理,不对应任何具体公司或项目实现。

摘要

生产模块最容易被做成一堆名词的集合:BOM、计划单、工单、领料单、报工单、质检单、入库单、返工单、报废单。

如果只按名词建表,很容易得到一个"看起来很 ERP,实际上跑不通"的系统。

更稳的方式是先明确三句话:

text 复制代码
BOM 是公式。
工单是闭环。
库存流水是事实账。

计划、领料、报工、质检、入库、返工、报废都要围绕这三句话展开。

本文讨论一个轻制造 SaaS 生产闭环的领域模型:如何从 BOM 推导需求,如何用工单承接生产责任,为什么报工和领料不强行多对多绑定,为什么前端可以融合,后端单据必须独立。

1. 先给核心对象定性

生产系统里的对象很多,但第一版必须先分清主次。

生产计划:需求来源和决策层

生产计划回答:

text 复制代码
为什么要生产?
准备生产什么?
准备生产多少?
当前缺哪些物料?
是否要合并多个来源需求?

计划不是生产现场的执行账,它更像生产意图和需求归集。

所以计划可以存在,也可以没有。小工厂里老板经常直接安排生产,不一定先走正式计划。

生产工单:生产闭环主单据

生产工单回答:

text 复制代码
这次到底由谁负责生产?
生产哪个 SKU?
目标数量是多少?
使用哪份 BOM 快照?
领了多少料?
交了多少货?
合格多少?
报废多少?
最终能不能关闭?

工单必须存在。因为领料、退料、报工、质检、入库、返工、报废都要围绕工单归集。

一个务实原则是:

text 复制代码
计划可选,工单必有。

BOM:理论用料公式

BOM 回答:

text 复制代码
生产一个成品,理论上需要哪些物料?
每种物料需要多少?
是否有损耗率?
单位如何换算?
如果有半成品,是否继续展开?

BOM 不是实际领料单。实际领料可以偏离 BOM,但必须留痕。

库存流水:库存事实账

库存流水回答:

text 复制代码
哪张来源单据导致库存变化?
哪个商品、哪个仓库、哪个批次变化了?
数量增加还是减少?
变化前后如何追溯?

库存模块不应该理解复杂生产语义。生产语义由生产单据解释,库存流水只证明库存事实怎么变。

2. 端到端单据闭环

一个轻制造 MVP 的主链路可以这样建模:
#mermaid-svg-sDelkzKMHBw53eCt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sDelkzKMHBw53eCt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sDelkzKMHBw53eCt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sDelkzKMHBw53eCt .error-icon{fill:#552222;}#mermaid-svg-sDelkzKMHBw53eCt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sDelkzKMHBw53eCt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sDelkzKMHBw53eCt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sDelkzKMHBw53eCt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sDelkzKMHBw53eCt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sDelkzKMHBw53eCt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sDelkzKMHBw53eCt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sDelkzKMHBw53eCt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sDelkzKMHBw53eCt .marker.cross{stroke:#333333;}#mermaid-svg-sDelkzKMHBw53eCt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sDelkzKMHBw53eCt p{margin:0;}#mermaid-svg-sDelkzKMHBw53eCt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-sDelkzKMHBw53eCt .cluster-label text{fill:#333;}#mermaid-svg-sDelkzKMHBw53eCt .cluster-label span{color:#333;}#mermaid-svg-sDelkzKMHBw53eCt .cluster-label span p{background-color:transparent;}#mermaid-svg-sDelkzKMHBw53eCt .label text,#mermaid-svg-sDelkzKMHBw53eCt span{fill:#333;color:#333;}#mermaid-svg-sDelkzKMHBw53eCt .node rect,#mermaid-svg-sDelkzKMHBw53eCt .node circle,#mermaid-svg-sDelkzKMHBw53eCt .node ellipse,#mermaid-svg-sDelkzKMHBw53eCt .node polygon,#mermaid-svg-sDelkzKMHBw53eCt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sDelkzKMHBw53eCt .rough-node .label text,#mermaid-svg-sDelkzKMHBw53eCt .node .label text,#mermaid-svg-sDelkzKMHBw53eCt .image-shape .label,#mermaid-svg-sDelkzKMHBw53eCt .icon-shape .label{text-anchor:middle;}#mermaid-svg-sDelkzKMHBw53eCt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sDelkzKMHBw53eCt .rough-node .label,#mermaid-svg-sDelkzKMHBw53eCt .node .label,#mermaid-svg-sDelkzKMHBw53eCt .image-shape .label,#mermaid-svg-sDelkzKMHBw53eCt .icon-shape .label{text-align:center;}#mermaid-svg-sDelkzKMHBw53eCt .node.clickable{cursor:pointer;}#mermaid-svg-sDelkzKMHBw53eCt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sDelkzKMHBw53eCt .arrowheadPath{fill:#333333;}#mermaid-svg-sDelkzKMHBw53eCt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sDelkzKMHBw53eCt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sDelkzKMHBw53eCt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sDelkzKMHBw53eCt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sDelkzKMHBw53eCt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sDelkzKMHBw53eCt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sDelkzKMHBw53eCt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sDelkzKMHBw53eCt .cluster text{fill:#333;}#mermaid-svg-sDelkzKMHBw53eCt .cluster span{color:#333;}#mermaid-svg-sDelkzKMHBw53eCt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sDelkzKMHBw53eCt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sDelkzKMHBw53eCt rect.text{fill:none;stroke-width:0;}#mermaid-svg-sDelkzKMHBw53eCt .icon-shape,#mermaid-svg-sDelkzKMHBw53eCt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sDelkzKMHBw53eCt .icon-shape p,#mermaid-svg-sDelkzKMHBw53eCt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sDelkzKMHBw53eCt .icon-shape .label rect,#mermaid-svg-sDelkzKMHBw53eCt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sDelkzKMHBw53eCt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sDelkzKMHBw53eCt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sDelkzKMHBw53eCt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 也可直接创建
生产需求

库存预警 / 销售缺货 / 手工
生产计划

可选,做需求归集
计划明细

一个计划可包含多个成品
生产工单

生产闭环主单据
领退料单

分批领料 / 退料
报工单

本次交付多少
质检验收单

合格 / 返工 / 报废 / 待处理
生产入库单

合格品正式入库
返工记录

重新处理后再次报工
报废 / 损耗记录
在制台账

投入物料追溯
库存流水

这条链路里有几个关键边界。

计划和工单不是一回事

计划偏"我要不要做、做多少、缺什么料"。

工单偏"这次生产责任闭环怎么跑完"。

计划可以一对多生成工单。比如一个计划里有多个成品,或者一个成品拆成多批生产。

但工单也可以手工直接创建。直接创建工单时,不应该偷偷在后台生成一个用户不可见的计划单,否则会制造概念上的脏数据。

更合理的方式是:工单带来源类型,比如手工、销售缺货、库存预警、计划下达。

领料和报工都挂工单,不强行互绑

一个常见疑问是:报工单是否要关联具体哪几张领料单?

在轻制造场景里,不建议第一版强绑。

原因是生产现场经常分批领料、分批报工,领料和产出并不总是严格一一对应。如果强行要求"这次报工消耗哪几张领料单",用户操作会变复杂,数据也未必更真实。

更主流、更稳的归集口径是:

text 复制代码
领料挂工单。
报工挂工单。
工单关闭时统一计算理论用料、实际净领料、合格入库、报废和差异。

这能保持操作简单,同时保留差异分析能力。

报工和质检可以前端一页,后端两张单

对客户来说,"今天交了 100 个,其中 95 个合格、3 个返工、2 个报废"可能就是一个页面动作。

但后端语义上,报工和质检不是同一件事。

报工记录的是:

text 复制代码
生产现场交付了多少产出。

质检记录的是:

text 复制代码
这些产出如何被判定和处置。

所以可以设计成:

text 复制代码
前端:一个报工质检确认页面。
后端:报工单 1:1 质检验收单。

这样后续要拆独立质检页、质检标准库、返工流程、质检附件,都有稳定基础。

生产入库单不能被质检单替代

质检合格不等于库存已经入库。

第一版为了简化交互,可以在质检确认后后台自动生成生产入库单并立即过账。前端只显示"已入库"。

但后端仍要有正式入库单。

原因是后续很容易出现这些需求:

  • 质检合格,但仓库稍后确认入库。
  • 合格数量分批入库。
  • 入库单打印。
  • 入库冲销。
  • 仓库员独立操作生产入库。

如果第一版把入库动作完全塞进质检单,后面这些能力都会反向冲击质检模型。

因此更稳的规则是:

text 复制代码
前端可以融合。
后端单据必须独立。
融合接口只是 Facade,不是单据模型合并。

3. BOM 建模:版本、快照和多层展开

BOM 是生产系统里最容易低估的部分。

如果只是单层公式:

text 复制代码
可生产数量 = min(各物料库存 / 单位成品需求量)

那么树形 BOM 的价值会大打折扣。

真正的多层 BOM 要处理半成品库存:

text 复制代码
成品 A 需要半成品 B。
B 自己也有 BOM。
如果 B 有库存,先用 B 的库存。
如果 B 不够,不足部分再展开 B 的 BOM 或生成 B 的生产需求。

示例:

text 复制代码
生产成品 A 50 个。
每个 A 需要半成品 B 1 个。
B 当前库存 20 个。

B 毛需求 = 50
B 可用库存 = 20
B 净需求 = 30

系统应该给出:

text 复制代码
A 工单先领用 B 现货 20 个。
B 缺口 30 个形成半成品生产需求或子工单。
B 子工单完工入库后,A 工单继续领用 B。

这就是多层净需求展开,而不是无脑把整棵 BOM 炸到底层原料。

BOM 版本和工单快照

BOM 会变。

可能今天换了包装材料,明天调整了损耗率,后天增加了替代料。

如果历史工单一直引用当前最新 BOM,那么后续差异分析会失真:

text 复制代码
当时生产用的是旧配方。
现在系统却拿新配方算理论用量。
最后差异看起来异常,其实是系统自己改了公式。

所以工单创建或下达时必须锁定 BOM 快照:

text 复制代码
BOM 可以有多个版本。
默认版本用于省操作。
工单一旦创建 / 下达,锁定当时使用的 BOM 版本和组件快照。
BOM 后续修改不能影响历史工单。

计划单到工单的转换点也要注意:

text 复制代码
计划创建时算过一次缺料。
计划审核或下达时 BOM 可能已经变了。
生成工单前应重新计算,并提示差异,由用户确认。
工单创建后再锁定快照。

这比静默沿用旧快照或静默切换最新 BOM 都更稳。

4. SPU 和 SKU 的双层口径

很多系统里有 SPU 和 SKU 两层商品模型。

生产模块不能只看 SPU,也不能只看 SKU。

更合理的口径是:

text 复制代码
商品能力、默认 BOM、需求聚合,可以先看 SPU。
实际 BOM 明细、工单快照、库存预占、领料、退料、入库、库存流水,必须落到 SKU。

原因很简单:库存账通常认的是 SKU。

如果生产模块只按 SPU 设计,到了库存扣减、批次追踪、仓库库存、销售出库时一定断层。

但如果所有配置都强制按 SKU 维护,客户操作成本又会很高。

所以推荐:

text 复制代码
SPU 维护默认规则。
SKU 可以覆盖规则。
解析时按 SKU 专用 > SPU 通用 > 系统默认。

比如一个成品默认使用通用 BOM,但某个颜色或包装规格的 SKU 有专门 BOM,就用 SKU 专用 BOM。

这和很多成熟系统里的 Product Template / Product Variant 思路是一致的。

5. 供给策略:让算法给建议,让人做决策

多层 BOM 还有一个关键概念:BOM 行上的供给策略。

它回答的是:

text 复制代码
这个物料在当前 BOM 里,缺了以后应该怎么处理?

常见策略可以抽象为:

text 复制代码
原料:缺了形成采购建议。
库存型半成品:先看库存,不足再生产。
外采件:缺了采购,不生成工单。
虚拟件:不看自身库存,直接展开下级 BOM。
订单绑定供给:为某个父工单专门生产,不共享库存池。

MVP 不需要全部开放。普通客户只需要:

text 复制代码
原料
库存型半成品
外采件

但架构上要预留。

尤其是 Make-or-Buy 场景:

text 复制代码
某个半成品既能自己生产,也能外购。

这时候系统不要替老板自动决定。

更好的方式是:

text 复制代码
系统计算净需求。
缺口进入待处理建议。
用户选择:转生产子工单、转采购、暂不处理。

算法提供建议,人做关键业务决策。

6. 关单时才真正看差异

生产过程允许现实偏差。

比如:

  • 超领。
  • 少领。
  • 超报。
  • 少报。
  • 超产。
  • 报废。
  • 返工。
  • 退料。

如果系统完全阻断,会不好用;如果完全不管,就退化成 Excel。

更合理的方式是:

text 复制代码
允许偏差。
超过提醒阈值时标红。
超过确认阈值时要求主管确认。
超过阻断阈值时后端拒绝。
所有偏差必须留痕。

工单关闭时做最终守恒校验:

text 复制代码
理论用料
实际领料
退料
净耗料
合格入库
返工
报废
在制余额

如果仍有差异,进入关单复核:

text 复制代码
主管确认差异原因。
生成退料、损耗或待处理记录。
在制台账归零或进入允许关闭状态。
工单关闭。

这一步是老板真正要看的地方:不是每一分钟谁做了什么,而是一张工单最终有没有把料、货、损耗解释清楚。

7. 一句话总结

轻制造生产闭环建模,不是把 ERP 名词都建成表。

更重要的是分清:

text 复制代码
计划是来源。
BOM 是公式。
工单是责任闭环。
库存流水是事实账。
在制台账解释原料离仓后去哪了。
质检解释产出如何处置。
入库单解释合格品如何进入库存。

前端可以把操作做简单,但后端不能把账做简单。

这就是轻制造 SaaS 第一版能不能"耐造"的关键。

本文为作者原创,首发于掘金,CSDN 为同步发布版本。

相关推荐
ai_coder_ai1 小时前
实时流处理架构(Kappa 架构)在实时业务系统中的应用
架构
DS随心转APP2 小时前
AI生成的word怎么下载?AI导出鸭技术架构深度测评
人工智能·ai·架构·word·deepseek·ai导出鸭
敲个大西瓜2 小时前
Spring Could Alibaba 核心面试题
java·后端·spring
这个DBA有点耶2 小时前
Oracle 迁移金仓兼容性评估指南:5 大维度深度拆解
数据库·oracle·架构
骇客野人3 小时前
Java SSO 统一认证方案
java·开发语言
START_GAME3 小时前
MSSQL$SQL2016
java·服务器·前端
ydd1001003 小时前
字符串转换整数
java
嘟嘟07173 小时前
SDD 规范驱动开发:从 vibe coding 崩盘到"两次创造"
设计模式·代码规范·vibecoding
会编程的吕洞宾3 小时前
DeepAgents In Action学习(Second)
android·java·学习