制造放行向导

Odoo 制造为什么一缺料就弹一堆提醒而不是直接拦住:consumption warning wizard、backorder wizard 与放行顺序讲透

从 mrp.production.pre_button_mark_done、_get_consumption_issues 与两个 wizard 的衔接看,Odoo 制造完工前并不是简单做一次校验,而是把缺料、超耗、未产完数量拆成不同层级的交互,尽量让现场既能继续推进,也能保留责任边界。

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

在很多一线用户眼里,制造单点 Mark as Done 之后跳出来的那些窗口很烦:不是提示多耗了料,就是问要不要 backorder,看起来像系统“没想清楚”。

但从源码看,Odoo 其实是故意把这些问题拆成两层:先校验“你现在消耗的料对不对”,再校验“你这次实际产出的数量够不够”。这不是界面细节,而是制造放行的责任分层。

完工前的总入口:不是 button_mark_done(),而是 pre_button_mark_done()

addons/mrp/models/mrp_production.py 里,真正决定是否弹窗的是 pre_button_mark_done()

这个入口先做三件事:

  1. 基础 sanity check,例如公司一致性、序列号唯一性。
  2. 对自动生产或没有 lot 的情况做预处理。
  3. 然后才开始判断异常:先 consumption issues,再 quantity issues

这意味着 Odoo 的顺序是固定的:

  • 先看组件消耗是不是和 BoM / 当前产量匹配;
  • 再看本次产量是否会产生 backorder;
  • 两个问题都没有,才真正放行完成。

这套顺序很重要,因为“料不对”和“量不够”不是同一种业务问题。前者更像执行偏差,后者更像计划拆分。

_get_consumption_issues() 真正在比较什么

很多人以为 strict / warning consumption 只是“超领料就报错”。源码并不是这么粗糙。

_get_consumption_issues() 的核心逻辑是:

  • 先根据当前 MO、当前 qty_producing 和 BoM,用 _get_moves_raw_values() 重新推一遍理论应耗
  • 再遍历 move_raw_ids,读取实际 picked 数量,汇总成实际已耗
  • 如果有额外物料、数量不一致、或某个产品理论/实际不匹配,就把 (order, product, consumed_qty, expected_qty) 塞进 issues 列表。

也就是说,这里不是拿 move 上静态字段机械比较,而是在 “当前这次准备完工的产量” 上重新计算基准。

这就解释了两个常见误区:

误区一:改了 qty_producing,为什么 warning 结果也会变

因为 expected quantity 不是死的,它会按 qty_producing / product_qty 比例换算。你这次只报工一半,理论消耗自然也跟着缩。

误区二:为什么多出一条额外领料也会被抓出来

源码里专门处理了“BoM 里没有,但 move 上 picked 了非零数量”的场景。只要额外物料真的被拣过,系统就会把它当成 issue,而不是默默吞掉。

这反映出 Odoo 的态度:允许现场偏差被记录,但不允许偏差无声发生。

consumption warning wizard 不是报错框,而是“二次决策层”

_get_consumption_issues() 返回内容后,_action_generate_consumption_wizard() 会把问题转成 transient lines,打开 mrp.consumption.warning

这个 wizard 里有两个特别关键的动作。

action_confirm():承认偏差,但继续放行

这个动作会带着 skip_consumption=True 回到 button_mark_done()。意思很直接:

  • 这次偏差我已经看过;
  • 不再重复校验 consumption;
  • 继续后续流程。

所以 warning 模式并不是“自动放过”,而是要求有人显式确认偏差

action_set_qty():把 move 修回理论值再放行

这个动作更有意思。它会遍历 warning lines:

  • 找到对应原料 move;
  • 把 move 数量改到 expected qty;
  • picked=True 补上;
  • 如果原 move 已不存在,还会新建额外 move;
  • 但若涉及 tracked product 且缺 lot/serial,则直接拒绝自动修正。

这里能看出 Odoo 很克制:

  • 对普通物料,系统可以帮你把数字纠正到理论值;
  • 对受 lot / serial 管控的物料,不敢替你“瞎补”,必须由现场明确指定批次。

这就是制造系统里典型的自动化边界:数量能代填,追溯不能代签。

为什么 consumption 处理完之后,才轮到 backorder wizard

pre_button_mark_done() 在 consumption issue 解决后,才会去看 _get_quantity_produced_issues()

后者只做一件事:检查 _get_quantity_to_backorder() 是否为零。如果不为零,说明这张 MO 本次没有全部做完,系统就要决定是否拆补单。

这一步又会根据 operation type 的 create_backorder 策略分流:

  • always:自动 backorder;
  • ask:弹 wizard 让用户决定;
  • never:直接完工,不再拆补单。

这和 consumption 的治理逻辑完全不同。

  • consumption 处理的是执行内容是否偏离
  • backorder 处理的是执行数量是否拆批

两者如果混成一个弹窗,现场很难理解到底是在承认超耗,还是在决定剩余产量的命运。源码把它们拆开,是对现场认知负担的优化。

这套放行顺序对实施和开发有什么启发

1. 不要把 warning 当 bug

很多项目里用户会说:“为什么我点完成还要多点一次确认?”

如果你的业务允许偏差但必须留痕,那这恰恰是正确行为。真正要看的是:当前产品是否该用 flexible / warning / strict 中的哪一种,而不是一味追求“一键完成”。

2. 自定义放行流程时,优先接在 pre_button_mark_done() 前后

如果你要增加额外校验,最好理解 Odoo 现有顺序。因为你插在不同位置,业务含义完全不同:

  • 插在 consumption 之前,是更强的放行前置条件;
  • 插在 backorder 之后,意味着你接受拆单逻辑先发生;
  • 如果直接绕过 pre_button_mark_done(),很容易把 Odoo 原有的责任分层打散。

3. tracked 产品的自动修正要格外保守

action_set_qty() 对 lot / serial 的谨慎处理很值得借鉴。任何“自动补 move line”“自动补批次”的定制都要非常小心,否则你修的是数字,坏的是追溯。

一句话总结

Odoo 制造完工前的两个 wizard,不是重复弹窗,而是两道不同的治理关口:先确认“你到底耗了什么”,再确认“你这次到底做完了多少”。

理解这层顺序后,很多看似烦人的弹窗,就会从“系统不顺手”变成“系统在帮你把执行偏差和计划拆分分开记账”。

DISCUSSION

评论区

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