副产品成本

Odoo 副产品成本为什么不能拍脑袋分:cost_share、finished moves 与估值分摊链路讲透

很多人把副产品理解成 BOM 上顺手多挂几行产出,但在 Odoo 里,副产品会把制造成本真正切走。本文结合 mrp 源码讲清 cost_share 的校验、落到 stock.move 的方式,以及它为什么会影响后续估值。

制造
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

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 是很合适的建模方式。

但如果只是:

  • 偶尔多出一点边角料
  • 价值极低
  • 不打算做稳定估值

那就别为了“看起来完整”强行上副产品分摊,不然反而会把成本体系搞复杂。


你可以怎样排错

如果你发现副产品成本结果不对,排查顺序建议是:

  1. 先看 BOM 上副产品行有没有填错 cost_share
  2. 再看总和是否超过 100,或是否有变体条件导致某些行生效/失效
  3. 看制造单上的 move_finished_ids 是否已经把 cost_share 正确继承下来
  4. 再去看完工后的库存估值与会计结果

不要一上来只盯会计分录,因为问题往往在制造单生成阶段就已经埋下了。


最后总结

Odoo 对副产品的设计很清楚:

  • BOM 层负责定义副产品和成本比例
  • 生产单负责把规则展开成 finished moves
  • finished move 负责把分摊事实带入后续完工与估值

所以副产品真正难的地方,不是“怎么多生成一条库存记录”,而是:

你是否用 cost_share 明确表达了这张制造单的价值分配。

一旦这个认知清楚了,副产品就不再是 BOM 边上的小字段,而是制造成本建模里非常核心的一环。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。