制造数量变更

Odoo 改制造数量为什么不是改个数字:Change Production Qty、move 重算与工单续算讲透

很多人以为制造单数量改了,BOM 和工单会自动按最新理解全部重建。源码其实更克制:Odoo 主要是在现有 move、成品 move 和工单上做比例重算,而不是重新发明整张 MO。

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

先说结论

Odoo 里的 Change Production Qty,本质上不是“把制造单数量字段改一下”。

它解决的是一个非常现实的问题:

当生产目标数量变化时,系统要尽量沿用当前这条 MO 的执行链,而不是粗暴删掉 move、工单和预留再重建。

所以官方实现的重点不是“重新生成一张新单”,而是:

  • 按比例更新原料 move
  • unit_factor 更新成品和副产品 move
  • 重新计算工单预计时长和待生产数量
  • 对缺料 move 再触发 scheduler

这也是为什么很多人改完数量后会觉得“怎么和我想的 BOM 不一样”。

因为这个向导的目标是延续执行链,不是重新解释工艺定义


这个机制到底在解决什么问题

真实现场里,制造数量经常在生产前后被改动:

  • 客户临时加单,原本做 50 件改成 80 件
  • 某批次试产后决定缩量,只做 20 件
  • 已经开始领料,但计划层又重算了一次需求

如果系统每次都把 MO 删除重建,会立刻出现一堆问题:

  • 已经挂上的 reservation 怎么办
  • 已有 work order 的状态和已产数量怎么接
  • 已创建的成品 / 副产品 move 怎么保留追溯

所以 Odoo 在 addons/mrp/wizard/change_production_qty.py 里采用了更稳妥的路线:

优先改已有对象,让 move 和 workorder 顺着新数量继续跑。


核心链路:向导到底做了什么

入口是 change.production.qtychange_prod_qty()

1. 先拿旧数量和新数量

向导先读:

  • old_production_qty
  • new_production_qty
  • factor = new / old

后面大部分更新,都是围绕这个比例展开。

2. 调 production._update_raw_moves(factor)

这是最关键的一步。

它会把原料 move 按比例放大或缩小,并返回哪些 move 被改了。

这一步解决的是“原料需求怎么跟着目标数量变”。

注意这里的语义是:

  • 更新现有 raw moves 的需求量
  • 不等于“重新按最新 BOM 全量 explode 一次”

所以如果你改数量之前,BOM 自身已经变了,Change Production Qty 并不会自动替你完成“换工艺版本”这件事。

3. 记录异常与活动日志

源码里会把 move 变化整理成 documents,再调用:

  • _log_manufacture_exception()

这说明官方并不把数量调整当成一个无痕操作,而是默认它可能影响上下游执行对象,应该留下可追踪的提示。

4. 更新成品与副产品 move

向导随后调用 _update_finished_moves(production, new_qty, old_qty)

这里的逻辑非常值得注意:

  • 它只处理 未 done / 未 cancel 的 finished moves
  • 变更量按 (new_qty - old_qty) * move.unit_factor
  • 如果 move 还有 move_dest_ids,可能走 copy + confirm 的传播路线

这说明成品和副产品不是直接写死成“新数量”,而是沿着既有 move 结构按单位比例续算。

一句话记:

Change Production Qty 处理 finished move 时,依赖的是现有 move 的 unit_factor,不是重新回到 BOM 重新推一遍。

5. 调整工单时长和 qty_producing

对每条 workorder_ids,系统会:

  • 重算 duration_expected
  • 重新计算 qty_production - qty_produced
  • 更新 qty_producing
  • 必要时把状态从 done 拉回 progress,或者从 progress 推回 done

这一步解决的是执行层最容易乱掉的问题:

  • 数量变了,工单预计时长要不要变
  • 已做一半的工单,后面还要做多少
  • 工单关联的 move_line、operation 绑定还对不对

6. 最后再给缺料 move 触发 scheduler

源码最后会对 confirmed/progress 状态的 raw moves 调 _trigger_scheduler()

意思很明确:

  • 如果这次改大数量,把需求放大了
  • 系统会再去看哪些组件需要补货 / 补制

所以数量调整不是停留在 MRP 表单层,而会继续把影响传到补货逻辑。


为什么它不是“重新展开 BOM”

这是新手最容易误解的地方。

源码注释已经说得很直接:

更新 finished moves 时,不会考虑生产过程中 BoM 被修改的情况。

raw move 这边虽然会按比例更新,但整个向导的目标仍然是在原 MO 上续跑,不是“把 MO 变成按最新 BOM 全新生成的一张单”。

所以这两件事不能混:

  • 改数量:用 Change Production Qty
  • 换 BOM / 更新工艺定义:看是否需要 action_update_bom() 或重新建 MO

很多现场问题,本质上都是把这两个动作混成一个动作了。


新手最常踩的 4 个坑

坑 1:以为直接 write({'product_qty': ...}) 就够了

不够。

你如果只改 product_qty 字段:

  • raw moves 不会按官方链路重算
  • finished moves 的传播逻辑不会跑
  • work order 预计时长和状态续算也可能失真

坑 2:以为数量调整会顺便吃掉最新 BOM 变化

不会。

如果你同时改了 BOM 行、工序或副产品定义,单独跑数量调整,常常只会得到“按旧 move 结构放大/缩小”的结果。

坑 3:忽视序列号产品的特殊分支

源码里对 tracking == 'serial' 有单独处理。

序列号产品的 qty_producing 不会像普通批量产品那样直接按剩余量推进,而是更接近“一次一件”的执行语义。

坑 4:以为 scheduler 会自动补齐所有后果

scheduler 只是在最后对 raw moves 再看一遍补货。

它不是万能修复器,修不了你错误的 BOM、错误的 move 绑定、或者你绕开官方向导直接改字段造成的脏数据。


开发时最该注意什么

1. 不要把“改数量”和“改工艺”塞进同一个 override

这是最危险也最常见的自定义错误。

如果你在数量变更时顺手:

  • 重建 raw moves
  • 重建 finished moves
  • 重建 workorders

很容易把 Odoo 原本保留的 reservation、move_dest 传播和状态续链全冲掉。

2. 自定义字段要想清楚挂在哪一层

因为 finished move 可能走 copy 传播,raw move 也可能重算数量。

如果你有自定义字段依赖:

  • move copy
  • workorder duration
  • qty_producing

就要检查这些字段在变更数量后是否还能正确继承。

3. 调试时先看 4 组对象

最有效的排查顺序通常是:

  • mrp.production.product_qty
  • move_raw_ids.product_uom_qty
  • move_finished_ids.product_uom_qty
  • workorder_ids.duration_expected / qty_producing / state

不要只盯 MO 头字段。

4. 真要按新 BOM 重算,就走另一条链路

如果业务语义已经不是“继续当前单”,而是“按新定义重算这张单”,那就应该明确走:

  • 更新 BOM
  • 重新 link BOM
  • 或直接取消旧单、重建新单

而不是把 Change Production Qty 硬改成 BOM 重建器。


最后总结

Change Production Qty 的真正价值,不是方便改数字,而是:

在制造执行链已经长出来之后,让 Odoo 用尽量小的代价把数量变化传播到 raw move、finished move、workorder 和补货链。

所以你可以把它理解成一个“延续式重算器”。

它擅长的是:

  • 数量变化
  • 执行续链
  • 预留与工单尽量保真

它不擅长的是:

  • 用最新 BOM 重新定义整张 MO
  • 替你修复绕开官方链路的脏更新

把这个边界看清,很多“为什么数量改了结果怪怪的”问题,基本就能解释通了。

DISCUSSION

评论区

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