取消回退

Odoo 取消采购单为什么不只是改状态:propagate_cancel、下游 move 与 MTS 回退链讲透

很多人以为取消采购单只是把 PO 设为 cancel,但 purchase_stock 源码里,它还会处理收货单、下游 move、是否继续补货,以及何时改成 make_to_stock。本文把这条回退链讲透。

采购
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

在 Odoo 里,取消采购单绝不是“把状态改成 cancel”这么浅。

/home/ubuntu/odoo-temp/addons/purchase_stock/models/purchase_order.pypurchase_order_line.py 的逻辑表明,取消 PO 时系统会同时考虑:

  • 采购行对应的收货 move 要不要取消
  • 下游 move_dest_ids 要不要一起取消
  • 如果不一起取消,是否应该回退成 make_to_stock
  • 已完成的收货单怎么办
  • 多条采购行共同指向同一 downstream move 时怎么解绑

所以采购取消,本质上是在重写一部分供应链承诺关系。


第一层:为什么系统先拦“锁定单”和已入账单

基础 purchase.order.button_cancel() 就已经有两道硬门槛:

  • 锁定的采购单不能直接取消
  • 相关 Vendor Bill 不是草稿/取消状态时,也不能直接取消

这说明 Odoo 很清楚:

  • 一旦采购单已经被当作稳定业务事实
  • 你想撤销,不能只从采购视角出发

先处理账单和锁定状态,目的是防止“采购撤销了,但财务与履约还挂着”。


第二层:purchase_stock 真正复杂的,是下游 move 处理

purchase_stock 里,取消 PO 会遍历采购行,并处理两类 move:

  1. move_ids:采购行自己生成的收货 move
  2. move_dest_ids:依赖这笔采购供给的下游需求 move

重点就在第二类。

因为采购单常常不是孤立存在的,它背后可能服务于:

  • MTO 销售
  • 补货规则
  • 生产需求
  • 跨仓补给

取消采购,不等于这些需求一起消失。


propagate_cancel 才是真正决定“要不要一锅端”的开关

采购行上有一个字段:

  • propagate_cancel

当它为真时,下游 move 会跟着取消;为假时,系统会把下游 move 改成:

  • procure_method = 'make_to_stock'

然后重新计算状态。

这背后的业务含义特别重要:

propagate_cancel = True

表示这笔下游需求明确依赖这次采购,一旦采购没了,下游也应该一起终止。

propagate_cancel = False

表示“采购这条供应来源取消了,但需求本身还在”,系统要把它重新交还给库存/补货体系继续解决。

这就是你看到某些需求在 PO 取消后没有消失,而是重新等待补货的根因。


为什么源码还要处理“共享 downstream move”

有些 move_dest_ids 不是一对一,而是可能被多条采购行共同指向。

源码会先找:

  • len(m.created_purchase_line_ids.ids) > 1

这种情况下,它不是粗暴把 move 干掉,而是先解绑当前采购行。

这是非常关键的保护。

否则你取消一条采购线,可能会把别的仍然有效的供应承诺一起误杀。


已完成收货单为什么不会被硬取消

代码里对已完成 picking 的处理很克制:

  • 不强行取消 done picking
  • 只在其上留言,说明关联采购单已取消

这其实是在尊重“已经发生的仓储事实”。

货已经收了,就不能靠取消 PO 让历史物理动作消失。

这和很多新手想象的“采购单是总开关”完全不同。

在 Odoo 里,单据之间更像相互引用、相互修正,而不是一张单无条件统治全部事实。


最容易误解的 5 个点

1. 以为取消采购单只影响采购模块

实际上会波及库存与补货链。

2. 以为取消后下游需求一定消失

还要看 propagate_cancel

3. 以为没取消的下游 move 就是异常残留

它可能是被故意回退为 make_to_stock

4. 以为 done picking 会被一并撤销

源码明确不会这么做。

5. 以为一条采购行对应一条独立 downstream move

共享引用场景下,系统会先解绑再处理。


排错顺序

当你遇到“PO 取消了,为什么还有 move/需求/收货记录”,建议按这个顺序排:

  1. 看采购单是否被锁定或已有有效 Vendor Bill
  2. 检查采购行 propagate_cancel 的值
  3. move_idsmove_dest_ids 各自状态
  4. 确认 downstream move 是否被多个 created_purchase_line_ids 共享
  5. 看未取消的 move 是否已被改成 make_to_stock
  6. 对 done picking,不要期待系统会替你抹掉历史事实

一句话记忆法

Odoo 取消采购单,取消的不只是单据状态,而是这笔采购对下游需求“是否继续供给”的承诺关系。

DISCUSSION

评论区

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