先说结论
在 Odoo 里,制造单的 Split、Merge、Backorder 不是三个按钮,而是三种完全不同的业务语义。
它们分别回答的是:
- Split:一张单太大,要不要先拆成几批跑
- Merge:几张同类单太碎,要不要并成一张排
- Backorder:这次只做完一部分,剩下的以后再续
一句话记:
Split 解决“怎么开工”,Merge 解决“怎么计划”,Backorder 解决“怎么收尾”。
如果把这三个动作混着用,就很容易把 move、工单、追溯和上下游链路搅乱。
这三个机制分别在解决什么问题
Split:控制批次和排产粒度
当单张 MO 数量太大时,现场未必想让它一直以一张单的形式存在。
典型原因包括:
- 工装 / 夹具一次只能跑固定批量
- 不同班组要分担同一批生产
- 同一产品要分散到不同日期或责任人
所以 split 的目标不是“补剩余”,而是:
在正式执行前,把一张生产任务拆成多张可排、可派、可控的小任务。
Merge:降低计划碎片化
反过来,如果系统已经长出了多张同产品、同 BOM、同操作类型的 MO,计划层可能更希望把它们并起来:
- 减少切换次数
- 合并备料和工位安排
- 把相同生产任务做成一条更连续的执行链
所以 merge 的目标是:
把多张计划上等价的制造单合成一张新的主单。
Backorder:接受“这次没做完”
backorder 发生在完工边界。
它解决的不是“怎么排更好”,而是:
- 本次实际完工量小于原计划量
- 但已经要收当前这次执行成果
- 剩余量需要保留到后续单据继续跑
所以 backorder 的语义是:
把一条正在执行中的生产链,按已完成和未完成部分正式切开。
Split 的核心链路:先拆明细,再生成新 MO
addons/mrp/wizard/mrp_production_split.py 里可以看到,split 向导不是让你随便点一下就结束。
它会先准备:
max_batch_sizenum_splitsproduction_detailed_vals_ids
也就是说,Odoo 先把“要拆成哪些数量、给谁、排哪天”显式建模出来,再调用 action_split()。
真正落地时,向导会调:
production._split_productions(...)
然后把每条拆出来的生产单再回填:
user_iddate_start
这说明 split 不是纯数量动作,它还顺带回答两个执行问题:
- 谁负责
- 什么时候开始
这也是为什么它更接近“拆批排产”,而不只是“数学拆分”。
Merge 的核心链路:新建一张主单,再迁移上下游关系
很多人以为 merge 就是把其中一张单数量加总,然后删掉其他几张。
不是。
mrp.production.action_merge() 的实现明显更谨慎:
1. 先校验同类单
系统要求被合并的单在关键维度上兼容,比如:
- 同产品
- 同 BOM
- 同 operation type / picking type 语义
2. 新建一张全新的制造单
源码会创建一个新的 mrp.production,数量是所有原单数量之和。
这很关键。
因为 merge 的本质不是“选一个旧单做宿主”,而是:
创建一张新的中心单,把旧单的计划身份收编进去。
3. 迁移 production group、orig links、dest links
源码会:
- 更新 linked picking 的
production_group_id - 把原有父子 MO 关系挂回新 production group
- 重新挂 finished move 的
move_dest_ids - 把下游 move 的
created_production_id改到新单
这说明 merge 真正难的地方不在数量相加,而在:
- 谁是新的来源单
- 谁还指向它
- 三步制造下前置 / 后置搬运链要不要继续成立
4. 最后取消原单
原单不是简单删除,而是走取消链并写日志说明“已合并到哪张新单”。
所以 merge 是一次有历史、有追溯、有上下游重挂的计划重组。
Backorder 的核心链路:基于实际产出切开执行链
Backorder 最核心还是 _split_productions(),但触发背景完全不同。
它通常发生在 button_mark_done() 前后:
- 当前准备完工的数量是
qty_producing - 原单目标数量是
product_qty - 两者不相等,就可能进入补单逻辑
这时 Odoo 的关注点不再是“怎么排得更漂亮”,而是:
- 已消耗原料怎么保留
- 已完工成品怎么留在当前单
- 剩余量怎么挂到补单继续
- workorder 已做和未做怎么衔接
所以 Backorder 比 Split 更像“执行态切单”,不是“计划态拆批”。
新手最容易混淆的边界
误区 1:把 Split 当作 Backorder 的前置版
不是。
Split 发生在更偏计划/排产层,强调:
- 拆批
- 分日期
- 分负责人
Backorder 则强调:
- 本次实际只做了多少
- 剩余量以后再做
一个是开工前优化粒度,一个是完工时承认剩余。
误区 2:把 Merge 理解成“几张单合并显示”
不是。
Merge 会新建主单,并重挂很多上下游关系。你如果有自定义字段、外部系统关联或者报工扩展,不能只看 MO 表头。
误区 3:忽视三步制造里的库位和搬运链
在 test_warehouse_multistep_manufacturing.py 里能看到,三步制造下:
- 组件先从 Stock 到 Pre-Production
- 成品完工后从 Post-Production 回 Stock
- move_orig_ids / move_dest_ids 是有链的
所以 split / merge / backorder 不是只影响 MO,它们还会影响前后两端搬运 move 的父子关系。
误区 4:以为批次大小只影响 UI
split 向导里 max_batch_size、num_splits 不是装饰字段。
它们表达的是:
- 一张 MO 最多以什么批量执行
- 系统应该自动分成几批
这直接影响排产粒度,而不只是界面显示。
开发时最该注意什么
1. 先分清你在改“计划”还是改“执行”
这是所有自定义前最该问的一句。
- 要拆批排产:看 split
- 要并计划:看 merge
- 要处理未完工剩余:看 backorder
不要一个按钮里全做。
2. 自定义字段必须检查 copy / cancel / relink 行为
这三条链都会导致:
- 新建 MO
- 复制 move
- 重挂 move_dest_ids / group links
- 取消旧单
如果你的字段挂在:
mrp.productionstock.movestock.move.linemrp.workorder
就必须逐条验证继承策略。
3. 三步制造尤其要测 source/dest location
官方测试专门覆盖了 merge 后默认 source location 是否正确。
这说明在多步制造下,错误的 location 默认值会直接让后续 picking 串错链。
4. 不要只测单步制造
很多自定义在一库位单步制造里看起来正常,一到:
pbmpbm_sam
就暴露出 origin、reservation、move link 全部不一致的问题。
最后总结
Split、Merge、Backorder 三者的真正区别,不在按钮,而在时间点和语义:
- Split:生产前,把大单拆成可执行批次
- Merge:计划层,把碎单合成新的中心单
- Backorder:完工时,把已完成和未完成正式切开
如果你把这三种场景区分开,再去看 Odoo 源码,会发现官方设计一直很一致:
不是在“改一张单”,而是在维护一条制造链在不同阶段的正确形态。
理解了这一点,制造单拆并补的很多混乱用法,基本都能收住。
DISCUSSION
评论区