从库存黑盒到生产闭环:轻制造 SaaS 的 MRP Lite 架构实践
摘要
在讨论“工厂生产管理”时,很多团队会自然想到 MES:工序、工位、设备、工时、产线节拍、扫码过站、质量追溯和实时看板。但在大量中小工厂场景中,第一版直接建设完整 MES 往往并不是最优解。
这类客户真正迫切的问题,通常不是“每一道工序是否被实时采集”,而是更基础的库存和生产结果黑盒:
- 原料到底出了多少?
- 这些原料理论上能生产多少?
- 现场最终交回多少成品?
- 返工、报废、退料和合格入库之间的差异是否可解释?
本文复盘一套面向中小工厂的轻制造 SaaS 架构设计。它的第一版没有从完整 MES 起步,而是选择 MRP Lite:用 BOM 计算理论需求,用生产工单承接责任闭环,用库存预占保护物料,用领退料、报工、质检和生产入库解释库存变化,再用在制台账补上原料离仓后到成品入库前的追溯断点。
文章重点讨论三个问题:如何在客户流程不够精细的情况下确定 MVP 边界;如何设计 BOM、计划、工单和单据闭环;以及如何用事务、幂等、UNKNOWN 状态、原子累加和反向冲销保证后端账务稳定。
1. 先从“生产黑盒”而不是“MES 功能清单”开始
“生产模块”是一个容易失控的需求入口。
如果按照成熟制造系统的能力清单展开,很快会得到一组看起来非常专业的功能:
- 工艺路线
- 工序任务
- 工位设备
- 工时成本
- 产能负荷
- 甘特排产
- 扫码过站
- IoT 采集
- 质检标准库
- 员工绩效
这些能力并不错误。问题是,它们都依赖一组前置条件:现场人员愿意持续录入,工艺路线相对稳定,设备和工位主数据可靠,每个节点有明确责任人,网络和终端条件可用,管理者也愿意按系统流程约束生产。
在很多中小工厂和新兴市场轻制造场景里,这些前置条件并不稳定。更常见的情况是:老板知道原料经常买多或买少,但不知道如何计算;仓库发出去多少料、现场用了多少料、最后产出多少成品,中间缺少系统化记录;现场人员未必都有系统账号,很多数据可能由老板、管理员或工头事后代录。
如果第一版系统直接管工序、工时和设备,很可能会把客户推到一个过高的使用门槛上。流程越细,越依赖现场实时录入;现场录入越不稳定,系统账越不可信;账不可信,老板就会重新回到 Excel、纸面记录或口头汇报。
所以第一版设计不应该先问“MES 应该有哪些功能”,而应该先问:
老板现在最想看清哪几笔账?
对这类客户,最有价值的问题通常是:
现有库存最多能生产多少?
要生产指定数量,还缺哪些物料?
谁领走了多少料?
最终报工多少、合格多少、报废多少?
理论用料和实际净耗料差了多少?
这不是完整 MES 的第一性问题,而是库存驱动生产闭环的问题。
2. 架构定位:后端像 ERP,前端像轻量 SaaS
这类系统最难的取舍是:后端不能粗糙,前端不能太重。
成熟 ERP 给我们提供的是账务严谨性:
BOM 要有版本和快照;
库存变化必须有来源单据和库存流水;
工单要有状态机;
领料、退料、入库、报废要能冲销;
数量累计要能从明细事实重算;
关键动作要有审计和责任人。
而轻量 SaaS 场景给我们的约束是:
页面要少;
术语要少;
必填要少;
允许老板或管理员代录;
允许事后补录;
先让异常可见,再逐步做自动提醒。
因此,这套系统采用了一个双层原则:
后端像 ERP:单据、状态机、库存流水、在制台账、BOM 快照、冲销和审计必须稳。
前端像轻量 SaaS:页面少、动作少、术语少,把复杂内部结构折叠成客户能理解的操作。
这个原则可以解释很多设计取舍。
例如“报工、质检、入库”在前端可以是一个确认动作:本次交了多少,合格多少,返工多少,报废多少,合格品是否入库。但后端不能因此只落一张“生产确认单”。后端必须仍然拆分报工事实、质检判定、生产入库、库存移动、库存流水和在制台账。
交互融合是为了降低使用门槛;单据独立是为了保证账务和追溯。二者不能互相替代。
3. 领域主线:计划是来源,工单是闭环
轻制造 MVP 的主线可以抽象为:
flowchart LR
A["需求来源:库存预警 / 销售缺货 / 手工"] --> B["生产计划:可选"]
B --> C["BOM 展开:缺料 / 可生产数量"]
C --> D["生产工单:必有"]
D --> E["库存预占:保护物料"]
E --> F["领料 / 退料"]
F --> G["在制台账"]
D --> H["报工"]
H --> I["质检"]
I --> J["合格入库"]
I --> K["返工 / 报废 / 待处理"]
J --> L["关单差异复核"]
K --> L
G --> L
这里有一个关键判断:计划可选,工单必有。
生产计划回答的是:
为什么要生产?
准备生产什么?
准备生产多少?
当前缺哪些物料?
是否要合并多个来源需求?
生产工单回答的是:
这次生产由谁负责?
生产哪个 SKU?
目标数量是多少?
使用哪份 BOM 快照?
领了多少料?
报工多少?
合格入库多少?
最终能否关闭?
计划偏需求归集和决策,工单偏执行责任和闭环追溯。
在中小工厂里,有些生产来自库存预警或销售缺货,有些来自老板直接安排。如果强制所有工单都必须先有计划,会增加操作门槛;如果为了链路统一而在后台偷偷生成用户不可见的计划单,又会制造概念上的脏数据。
更稳的方式是:允许手工直接创建工单,但工单必须明确来源类型。无论是否来自计划,领料、退料、报工、质检、入库、返工、报废都围绕工单归集。
这背后的抽象可以概括为:
计划是来源。
BOM 是公式。
工单是责任闭环。
库存流水是事实账。
在制台账解释原料离仓后的去向。
4. BOM 不是树形展示,而是净需求决策
BOM 很容易被低估。
如果只做单层公式,系统可以快速算出:
可生产数量 = min(各物料可用库存 / 单位成品需求量)
但这只适用于最简单的单层 BOM。
真实生产中,成品可能需要半成品,半成品自己也有 BOM。系统不能无脑把整棵树全部展开到底层原料,因为半成品本身可能已经有库存。
举一个抽象例子:
成品 A 需要半成品 B x 1。
半成品 B 需要原料 C x 2。
计划生产 A 50 个。
B 当前可用库存 20 个。
如果系统无脑把 B 展开到 C,会直接认为需要原料 C 100 个。
但更合理的多层净需求展开应该是:
B 毛需求 = 50
B 可用库存 = 20
B 净需求 = 30
系统应先让成品 A 的工单领用 B 的现货 20 个;B 不足的 30 个再形成半成品生产需求或子工单;等 B 子工单完工入库后,A 工单再继续领用。
所以,多层 BOM 的核心不是“树形结构展示”,而是每个节点如何处理。一个 BOM 行至少有几类可能:
原料:缺了形成采购建议。
库存型半成品:先看库存,不足再生产。
外采件:缺了采购,不生成工单。
虚拟件:不看自身库存,直接展开下级 BOM。
订单绑定供给:为某个父工单专门生产,不共享库存池。
MVP 不一定要把所有策略都开放给客户,但架构上必须预留“BOM 行供给策略”。否则后续遇到半成品、外采件、虚拟件、订单专供半成品时,BOM 展开算法会被迫重构。
另一个关键点是 BOM 快照。
BOM 会随着材料替换、包装调整、损耗率变化而变化。如果历史工单永远引用当前最新 BOM,差异分析会失真:当时生产用的是旧配方,系统却用新配方回算理论用料,最后看起来像生产异常,其实是系统把公式改了。
因此,工单创建或下达时必须锁定 BOM 版本和组件快照。BOM 后续修改不能影响历史工单。
计划生成工单时也要处理时序一致性:计划创建时可能算过一次缺料,但下达生成工单时 BOM 可能已经变了。稳妥做法是重新解析当前可用 BOM,如果与计划快照不一致,提示差异并要求用户确认,然后再生成工单快照。
5. 前端可以融合,后端单据不能合并
轻制造客户不一定愿意分别操作报工单、质检单和入库单。
对他们来说,更自然的动作是:
这次交了 100 个;
95 个合格;
3 个返工;
2 个报废;
合格的直接入库。
前端完全可以把这设计成一个页面、一个按钮。
但后端不能因此只落一张“生产确认单”。否则后续会遇到一系列问题:
- 质检通过但仓库稍后确认入库,应该挂在哪个状态上?
- 合格数量分批入库,如何解释已入和未入?
- 入库冲销时,冲哪张来源单据?
- 返工后再次报工,如何关联原始质检结果?
- 报废数量如何解释在制投入减少?
- 库存流水的来源单据是什么?
因此,前端融合接口在后端只应该是一个 Facade:
flowchart TD
A["前端一次确认:报工 + 质检 + 自动入库"] --> B["Facade 编排"]
B --> C["报工单:记录交付事实"]
B --> D["质检单:记录判定结果"]
B --> E["生产入库单:记录合格品入库"]
E --> F["库存移动执行"]
F --> G["库存流水"]
B --> H["在制台账"]
B --> I["工单累计字段"]
B --> J["审计事件"]
这个设计让 MVP 前端足够轻,同时让后端账务边界保持稳定。后续如果拆独立报工页、质检页、生产入库页,或者增加仓库员确认、入库单打印、入库冲销,都可以复用底层单据和领域服务。
6. 预占、库存流水和在制台账解决的是三类问题
生产系统里,有三个概念很容易混:
库存预占:库存还在仓库,但被某个业务锁住。
库存流水:库存余额实际增加或减少的事实。
在制台账:原料离开仓库后,在生产现场如何被解释。
它们不是同一个东西。
6.1 库存预占解决“抢料”
工单审核或下达后,系统可以按 BOM 理论用量预占原料。
预占不是扣库存,而是保护库存:
这个工单已经安排生产;
理论上需要这些物料;
不要让销售、调拨或其他工单把同一批物料拿走。
因此,审核通过和预占成功不是一件事。
审核通过:主管认可这张工单可以生产。
预占成功:库存系统已经把理论物料锁住。
如果缺料就不允许审核,老板反而看不到“已经批准但待料”的生产安排。更合理的状态是:全量预占成功则物料已备齐;部分预占或完全预占失败则进入待备料。缺料不一定阻断审核,但必须阻断全量领料和完整开工。
6.2 库存流水解决“库存怎么变”
领料、退料、生产入库和报废都会改变库存。
这些变化不能由生产模块直接修改库存余额。生产域应该组装库存移动意图,提交给统一库存移动编排层,再由库存底座完成真实库存变更和库存流水记录。
库存流水只回答事实问题:
哪个商品、哪个仓库、哪个批次,数量增加或减少了多少?
来源单据是什么?
它不应该承载所有生产语义。生产语义应该由工单、领退料单、报工单、质检单、生产入库单和在制台账解释。
6.3 在制台账解决“原料离仓后去哪了”
原料从仓库被领走以后,库存减少了,但它还没有变成成品。
如果系统只看库存,会出现一段黑洞:
原料库存少了;
成品库存还没增加;
中间这些料去哪了?
属于哪个工单?
最后是消耗、退回、报废,还是仍在生产现场?
在制台账记录生产投入的流水事实:
领料:投入进入在制;
退料:投入从在制退回仓库;
合格入库:按 BOM 快照确认投入被消耗;
报废:投入转为损耗;
返工:投入继续挂在生产闭环里;
冲销:用反向流水解释历史动作。
在制台账不是库存主账,也不是库存预占。它解决的是原料离仓后到成品入库前的追溯断点。
7. 核心账务动作要同步事务优先
以生产领料审核为例,一次操作至少会影响:
- 领料单状态
- 工单累计领料数量
- 库存预占转换
- 原料库存扣减
- 库存移动执行记录
- 库存流水
- 在制台账
- 审计事件
这不是普通单表更新。如果这些动作分散在多个不受控步骤里,就会出现难以修复的不一致:
单据成功了,库存没扣;
库存扣了,在制没记;
预占释放了,领料库存没扣;
库存流水有了,工单累计没更新;
前端显示成功,后端部分失败。
对于这类 ERP 账务动作,MVP 阶段不建议把主链路做成异步最终一致。用户点击“审核领料”后,系统应该给出明确结果。
推荐同步事务口径:
1. 校验商户隔离、权限、工单状态、数量阈值;
2. 校验预占或当前可领库存;
3. 创建库存移动执行记录;
4. 创建库存移动执行行;
5. 扣减库存余额;
6. 写库存流水;
7. 将预占部分转为实扣;
8. 写在制台账;
9. 原子累加工单累计字段;
10. 审核通过领料单;
11. 写审计事件。
核心原则是:
单据状态不能先成功,库存失败。
库存不能变了,在制台账没记。
预占不能释放了,但领料库存没扣。
库存流水和在制台账不能靠前端补偿。
如果事务内失败,整体回滚。事务提交后发现业务录错,不直接修改历史流水,而是通过反向单据和反向库存流水解释。
8. 幂等不能只靠前端防抖
前端按钮防抖有价值,但远远不够。
后端还要面对网络重试、网关超时后的客户端重试、用户多页面重复提交、浏览器刷新重复请求、第三方回调重复触发和后端任务重复执行。
库存动作必须在后端幂等。
一个实用方案是两层保护:
业务状态条件更新:先抢到动作资格;
库存移动 eventKey:保证库存动作幂等。
例如审核领料单时,不应先查状态再写回,而应通过条件更新拿到动作资格:
UPDATE material_order
SET status = 'APPROVED'
WHERE id = ?
AND status = 'PENDING_REVIEW';
只有影响行数为 1 的请求,才有资格继续执行后续库存动作。
同时,库存移动执行记录使用唯一事件键:
merchantId + eventKey
例如:
PRODUCTION_MATERIAL_ISSUE:{merchantId}:{materialOrderId}:APPROVE
即使重复进入库存编排层,也只能创建或复用同一条库存执行记录,不能重复扣库存。
业务状态条件更新和库存 eventKey 幂等不能互相替代:前者保护业务动作资格,后者保护库存动作不重复。
9. UNKNOWN 是账务系统的安全刹车
库存动作有三类结果:
明确成功;
明确失败;
结果未知。
结果未知可能来自边界情况:数据库提交状态不明确、底层库存服务超时、事务边界外出现异常、外部依赖返回不确定。
如果把 UNKNOWN 当成功,后续账可能已经错了还继续流转。如果把 UNKNOWN 当失败并简单重试,又可能造成重复扣减。
因此,库存移动执行层需要显式 UNKNOWN 状态。
对客户页面,不需要展示这个技术词。可以翻译成:
库存处理结果待确认,请联系管理员处理。
并阻断后续高风险动作:
不能继续领料;
不能关单;
不能反审核;
不能冲销。
直到后台完成核对和修复。
UNKNOWN 不是业务流程的一环,而是账务系统面对不确定性时的安全刹车。
10. 累计字段必须原子更新,并能从事实账重算
生产工单上通常会有一些累计字段:
已领料数量;
已报工数量;
合格入库数量;
报废数量;
返工数量。
这些字段用于列表展示和看板统计,是冗余汇总。
但冗余字段最怕并发覆盖。错误写法是:查询当前数量,在内存里加上本次数量,再写回。分批领料、分批报工、多人同时操作时,这种写法会丢累计。
正确方式是数据库原子累加:
UPDATE work_order
SET reported_quantity = reported_quantity + ?
WHERE id = ?;
同时还要有累计结果校验:
不能超过可报工余额;
不能超过可入库余额;
不能让在制余额扣成负数;
不能让已预占转换数量超过预占数量。
更重要的是:累计字段只是冗余汇总,不是事实账。事实账应该来自明细单据、库存流水和在制台账。必要时,系统应能从底层事实重算累计字段,并记录修复事件。
11. 不做什么,也是架构设计的一部分
这套设计没有把所有制造能力都放进第一版。
MVP 暂不做:
- 复杂工序排产
- 员工级绩效
- 工时成本核算
- 设备和工位管理
- 扫码过站
- IoT 采集
- 完整批次强追踪
- 移动加权或 FIFO 成本核算
- 报废仓和残值核算
- 自动 MRP 扫描和复杂排程
这些不是遗漏,而是基于客户成熟度和第一版目标做出的取舍。
但“不做”不等于“不考虑”。好的 MVP 不是把未来堵死,而是在当前可用性和后续扩展性之间找到平衡:
工单保留负责人和班组口径;
领料和入库明细预留批次字段;
BOM 行保留供给策略;
质检先记录结果、备注和附件,后续再接质检标准库;
在制台账先做流水事实账,后续再做余额快照;
看板先实时聚合,后续再做报表快照和推送。
这类边界设计决定了 MVP 是否能真正落地,也决定了系统后续是否能平滑演进。
12. 复盘:这套架构适合什么场景
这套 MRP Lite 架构不是完整 MES 的替代品,也不解决所有制造业问题。
它更适合以下场景:
- 工厂生产流程相对粗放,但老板已经强烈需要看清库存和产出;
- 当前没有稳定工序采集、工时采集、设备采集基础;
- 第一阶段的目标是减少库存黑盒,而不是精确计算产线效率;
- 系统需要支持老板、管理员或工头代录,而不是假设每个工人都实时使用系统;
- 后续可能逐步扩展批次、工序、任务、质检标准、成本核算和自动排程。
它不适合以下场景:
- 已经具备成熟产线和设备数据采集能力;
- 必须精确控制每道工序、每台设备、每个工时成本;
- 强依赖实时过站、实时质量追溯和复杂排程;
- 第一版就要求完整成本核算和批次全链路追踪。
因此,架构设计的关键不是把 ERP 或 MES 的功能清单照搬过来,而是判断当前阶段最应该先管住什么。
对这类轻制造场景,我的结论是:
先管住库存黑盒,再谈生产精细化。
总结
面向中小工厂的生产系统,第一版不一定要从完整 MES 起步。很多时候,更有价值的是先做一套 MRP Lite:用 BOM 计算理论需求,用工单承接生产责任,用库存预占保护物料,用领退料、报工、质检和入库形成闭环,再用库存流水和在制台账解释每一次物料变化。
本文的核心原则可以归纳为:
计划是来源。
BOM 是公式。
工单是闭环。
库存流水是事实账。
在制台账解释原料离仓后的去向。
前端可以融合,后端单据必须独立。
审核表达生产意图,预占表达库存保护结果。
库存动作必须有事务、幂等和 UNKNOWN 兜底。
累计字段必须原子更新,并能从明细事实重算。
系统架构的难点往往不在于堆多少功能,而在于判断第一版到底应该先管住什么。对于流程不够精细、但老板已经强烈需要看清库存和生产差异的轻制造场景,MRP Lite 是比完整 MES 更务实的起点。
本文为作者原创,首发于掘金,阿里巴巴开发者社区 为同步发布版本。