先说结论
Odoo 里的副产品,不是“顺手多产出一点东西”的展示字段。
它真正做的是:
- 在
mrp.bom.byproduct上定义副产品产出 - 用
cost_share指定这部分产出应该分走多少制造成本 - 在制造单生成 finished moves 时,把这个比例带到
stock.move - 让后续完工、入库、估值时,不再把全部成本都压到主产品身上
一句话记住:
副产品在 Odoo 里不是备注,它是制造成本分摊规则的一部分。
为什么很多团队会把副产品用错
现场最常见的误区有两个:
误区一:把副产品当“额外库存行”
很多人觉得,只要在 BOM 上加一个 by-product,系统完工时多生成一条成品 move 就行。
这只看到了“数量流”。
但源码里真正重要的是 cost_share。因为副产品不是只多一条库存记录,而是会参与“这张制造单的总成本到底分给谁”。
误区二:以为副产品默认不分成本
也有人觉得副产品只是记账方便,主产品成本照样吃满,副产品价值以后再人工修。
Odoo 不是这么设计的。
在 addons/mrp/models/stock_move.py 中,副产品 finished move 上有一个明确的 cost_share 字段,帮助文本直接写着:
- 这是副产品分走最终制造成本的百分比
- 所有副产品的总和必须小于等于 100
也就是说,官方实现从一开始就把副产品当成成本承载对象,不是展示对象。
BOM 层先做了什么约束
第一道约束发生在 mrp.bom。
在 addons/mrp/models/mrp_bom.py 里,官方会检查:
- 副产品不能和 BOM 主产品是同一个产品
cost_share不能为负数- 对每个变体,所有有效副产品的
cost_share总和不能超过 100
这几个约束背后的业务含义很强:
1)不能把主产品再伪装成副产品
否则你就等于在一张单里给同一个产物设两套成本归属,逻辑会乱掉。
2)不能出现负成本分摊
副产品不是“把成本倒贴回主产品”的倒冲机制。它只能分走 0 到 100 之间的比例。
3)总和不能超过 100
因为制造单的总成本就这么多。副产品可以切走一部分,但不可能把总成本切成 120%。
所以 cost_share 的本质不是“建议值”,而是 Odoo 在 BOM 层明确校验的硬规则。
这个比例是怎么从 BOM 落到制造单的
很多人关心:BOM 上写了副产品和比例,制造单里到底怎么继承?
关键逻辑在 mrp.production。
官方在生成 finished moves 时,会调用 _get_move_finished_values(),其中显式接收一个 cost_share 参数;而在遍历 BOM 副产品行时,又会把 bom_byproduct.cost_share 传进去。
这意味着:
- BOM 不是只定义一个静态模板
- 制造单确认后,副产品会被展开成真实的 finished move
- 这个 finished move 自己携带
cost_share
这一步很关键,因为真正参与库存流转和估值的是 stock.move,不是 BOM 配置本身。
换句话说:
BOM 上的副产品只是规则,落到 move 上才开始变成真实业务数据。
为什么 finished move 上也要存一份 cost_share
因为完工和估值不是一直回头查 BOM。
制造过程中,BOM 可能已经被改过;甚至同一产品不同制造单来自不同版本的 BOM。
所以 Odoo 的设计是:
- 在生成制造单时,把当时有效的副产品规则固化到
move_finished_ids - 后面这张制造单怎么做完工、拆分、成本计算,都基于这批 move 自己的数据继续走
这就是为什么 stock.move 上也有 cost_share 字段。
它不是冗余,而是为了把“当时这张单的成本分摊事实”冻结下来。
副产品为什么会影响估值,而不只是 UI 展示
源码里虽然字段名字很朴素,但信号很明确:
- 副产品是 finished move 的一部分
- finished move 会参与制造单完工
- 完工后会进入库存估值链
主产品和副产品既然都作为 finished move 存在,系统就必须决定:
- 哪一部分成本属于主产品
- 哪一部分成本属于副产品
而 cost_share 就是这条分界线。
所以在业务上,你可以把它理解为:
- 主产品拿“剩余成本”
- 每个副产品按
cost_share拿走对应比例
这也是为什么副产品成本不能瞎填。你一旦把比例设高,就不是“报表好看一点”,而是直接改了库存价值和后续毛利基础。
新手最容易误解的三个点
1)副产品数量和成本分摊不是同一个概念
副产品数量是 product_qty,成本分摊是 cost_share。
两者可以相关,但不是自动等价。
例如副产品数量很多,也不代表它一定应该分走很多成本;有时它只是低价值副产物。
2)副产品不是“废料回收入库”
如果业务语义更接近报废回收、返工回流、拆解回收,那更该考虑 scrap、unbuild 等链路,而不是把所有反向动作都塞进 by-product。
3)BOM 上能配,不代表会计上就合理
Odoo 允许你配置副产品分摊,但“副产品到底该分多少成本”仍然是业务与财务策略问题。
系统负责执行,不替你做会计判断。
实战里应该怎么用
如果你们的场景确实会稳定地产生副产品,比如:
- 主加工过程会同步产出可销售副产物
- 副产物会正式入库并参与后续销售/领用
- 成本需要在主产品和副产品之间做长期一致的分摊
那么 by-product + cost_share 是很合适的建模方式。
但如果只是:
- 偶尔多出一点边角料
- 价值极低
- 不打算做稳定估值
那就别为了“看起来完整”强行上副产品分摊,不然反而会把成本体系搞复杂。
你可以怎样排错
如果你发现副产品成本结果不对,排查顺序建议是:
- 先看 BOM 上副产品行有没有填错
cost_share - 再看总和是否超过 100,或是否有变体条件导致某些行生效/失效
- 看制造单上的
move_finished_ids是否已经把cost_share正确继承下来 - 再去看完工后的库存估值与会计结果
不要一上来只盯会计分录,因为问题往往在制造单生成阶段就已经埋下了。
最后总结
Odoo 对副产品的设计很清楚:
- BOM 层负责定义副产品和成本比例
- 生产单负责把规则展开成 finished moves
- finished move 负责把分摊事实带入后续完工与估值
所以副产品真正难的地方,不是“怎么多生成一条库存记录”,而是:
你是否用
cost_share明确表达了这张制造单的价值分配。
一旦这个认知清楚了,副产品就不再是 BOM 边上的小字段,而是制造成本建模里非常核心的一环。
DISCUSSION
评论区