先说结论
在 Odoo 里,原料消耗并不一定是“整张 MO 一次性扣完”。
如果 BOM line 或 by-product 绑定了 operation,系统会把对应 stock move 也挂到那道工序,再进一步挂到对应 work order。
一句话记:
BOM 上的 operation 绑定,决定的是某个组件或副产品应该在哪道工序被正式执行。
为什么很多人会忽略这层
因为从 MO 大视角看,大家更容易看到的是:
- 原料 move 在
move_raw_ids - 成品 / 副产品 move 在
move_finished_ids
于是容易产生一个误解:
- 这些 move 都只是挂在 MO 头上
- 和具体工序关系不大
但源码其实给了明确的 operation 维度。
在生成原料 move 时,_get_moves_raw_values() 会优先取:
bom_line.operation_id- 如果当前行来自上级展开,也会看
parent_line.operation_id
然后把这个 operation_id 写进 move。
同样,_get_move_finished_values() 给副产品生成 move 时,也会把 byproduct.operation_id 写进去。
这说明 Odoo 从模型上就支持:
- 某些料在某道工序领
- 某些副产品在某道工序产
不是所有东西都在 MO 级别平均摊开。
move 是怎么进一步挂到 workorder 上的
这一步在 mrp.production._link_workorders_and_moves()。
系统会先建立:
operation -> workorder
的映射。
然后遍历 move_raw_ids | move_finished_ids:
- 如果 move 上有
operation_id - 就把
workorder_id写成对应 operation 实例化出来的那张工单
这意味着:
- BOM / byproduct 上定义工序归属
- MO 确认后生成具体工单
- move 再挂到具体工单
所以最后车间不是对着抽象 operation 操作,而是对着具体 workorder 上的领料 / 产出任务操作。
为什么这对原料消耗特别重要
在 mrp.workorder.button_finish() 里,Odoo 会找:
- 当前待结束工单的
move_raw_ids - 以及 operation 属于当前工序的 byproduct move
然后给这些 move 补本次该 done 的数量,并标记 picked。
这个行为非常关键,因为它说明:
工单结束时,系统默认只推进属于这道工序的物料与副产品,而不是整张 MO 上所有 move 一起推进。
这就是为什么 operation 绑定在复杂制造里很值钱。
比如:
- 某种辅料在最后装配工序才消耗
- 某种边角料副产品在切割工序就产生
如果不做 operation 绑定,现场执行语义会明显变粗。
父子 BOM 展开后为什么还能保留工序语义
源码里 _get_moves_raw_values() 有个细节很妙:
- 当前 BOM line 自己没有
operation_id - 但如果它来自展开结构,
parent_line.operation_id也会被继承进来
这表示 Odoo 在处理 exploded lines 时,并没有完全丢掉上层工序语义。
业务含义就是:
即使物料明细被展开,系统也尽量保留“它属于哪道工序”这件事。
对多层结构或带 phantom 展开思维的人来说,这点很重要。
副产品为什么也能挂工序
很多人只想到组件有工序归属,没意识到副产品也有。
但 _get_moves_finished_values() 明确会按 byproduct.operation_id 生成 finished move。
这就意味着你可以表达:
- 主产品最终在整单完成后产出
- 某个副产品其实在中间某道工序就已经产生
这对下面这些场景特别有用:
- 切割产生边角余料
- 熔炼产生副渣
- 中间工序产生可回收副产物
如果全都压到 MO 完工时统一产出,现场和成本时点都会变粗糙。
为什么这和 routing 设计是一体的
很多团队做 routing 时,只想“步骤怎么排”。
但在 Odoo 里,routing / operation 设计还在回答一个更落地的问题:
- 哪一步用掉哪种料
- 哪一步生出哪类副产品
也就是说,operation 不是只负责排程。
它还承担了制造业务对象的执行归属。
所以一个好的 routing,不只是顺序合理,还要能把:
- 领料时点
- 副产品时点
- 工位执行时点
尽量对齐。
实战里最容易踩的 5 个坑
1. 以为所有原料都该挂在 MO 上统一消耗
复杂工艺下,很多料应该属于具体工序。
2. 只给 operation 排顺序,不给 BOM line 绑定工序
最后工单层执行会很粗。
3. 忽略副产品也能绑定 operation
会丢失很多真实现场语义。
4. 看到 move 在 MO 上,就以为和 workorder 没关系
_link_workorders_and_moves() 正是在做这层映射。
5. routing 设计只考虑时间,不考虑物料发生点
结果工序设计“看起来顺”,执行却很别扭。
一句话记忆法
在 Odoo 里,operation 不只是工序步骤,它还是组件消耗和副产品产出该落在哪张工单上的定位器。
DISCUSSION
评论区