销售防重复

销售改数量后为什么不会重复生成发货单:`_get_qty_procurement()` 的防重复逻辑

从 sale_stock 源码看,Odoo 怎样先统计已履约数量,再决定要不要重新发起 procurement,避免确认、改数量、补单时重复造 move。

Odoo 开发 库存 销售
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

很多人看到“销售单改数量”时,脑子里会自动担心一件事:

会不会又重新生成一套发货单?

Odoo 的答案是:不会直接重来。它会先算已经履约了多少,只对差额继续发起 procurement。

这正是 sale_stock 里最值得学的一段防重复逻辑。

先看入口:不是每次写入都会全量重算

sale.order.line.write() 里,Odoo 只有在销售行确实处于 sale 状态时,才会继续往下走,而且它会先把旧数量记下来,再把 previous_product_uom_qty 传给 _action_launch_stock_rule()

这一步的用意很直接:

  • 不是“看到改动就重建”;
  • 而是“对比新旧数量,只补真正缺的部分”。

这也是为什么你改一笔订单的数量,常常不会看到全新的 move 链,而是看到已有链路继续被补齐。

_get_qty_procurement() 先统计“已经推进了多少”

这段逻辑的核心很朴素:

  1. 找出这条 sale order line 的 outgoing moves;
  2. 再找出会抵消数量的 incoming moves;
  3. 把已经履约、已退回、已反向处理的量一起折算进来;
  4. 得出当前这条行还有多少“净需求”。

所以它看的不是“订单上写了多少”,而是:

这条行在真实库存链路里,已经被推进了多少。

这就是防重复的关键。

如果你只盯着 product_uom_qty,很容易误判;但 Odoo 是拿它去和已履约数量做差,再决定是否继续发起 procurement。

_action_launch_stock_rule() 只处理差额

算完净需求后,Odoo 只对差额发起后续动作:

  • 先准备 procurement values;
  • 再把差额换算成正确的 UoM;
  • 最后交给 stock.rule.run()

也就是说,它的真实策略不是“整条行重发”,而是:

只把还没覆盖的部分送进库存规则系统。

这对修改数量、补单、部分交付后的再次编辑特别重要。

为什么这套逻辑对业务很友好

假设一条销售行原来是 10 件:

  • 系统已经为 6 件建过 move;
  • 你把数量改成 12 件;

Odoo 不会把 10 件全删掉再来一次,而是倾向于计算:

  • 已经覆盖了 6 件;
  • 现在还差 6 件;
  • 只把这 6 件继续推向 procurement。

这样就能避免:

  • 重复生成 move;
  • 重复占用库存;
  • 重复打乱后续 picking。

_prepare_procurement_values() 负责把“这条差额”讲清楚

差额不是裸数字,而是带上下文的需求包。_prepare_procurement_values() 会带上:

  • origin
  • sale_line_id
  • warehouse_id
  • partner_id
  • route_ids
  • date_planned
  • date_deadline
  • sequence

这意味着后续 rule 看到的不是“一个 6 件”,而是:

某张销售单上的某一行,还差 6 件,需要按当前仓库、路线和交货时间继续处理。

新手最容易踩的坑

1)把 write() 当成“普通字段保存”

在销售单上,write() 可能会触发库存链路重算。二开时如果你绕过这条逻辑,就容易漏掉差额或重复创建 move。

2)只看订单,不看既有 move

真正决定是否继续发货的,不是订单数字本身,而是已有 move 的状态和数量。

3)忽略 rounding

Odoo 会用 float_compare 和 UoM 精度来判断差异。很多“看起来只差 0.0001”的问题,都会在这里被吞掉或保留。

排错时最值得问的三个问题

  1. 这条 sale line 现在到底已经履约了多少?
  2. 新旧数量差额是多少?
  3. stock.rule.run() 看到的是整单,还是只是一部分差额?

只要把这三个问题答清楚,重复发货、重复补货、数量改动后链路混乱的问题,通常就能定位到八九不离十。

一句话总结

Odoo 处理销售改数量时,不是“重新来一遍”,而是:

先用 _get_qty_procurement() 算出已履约数量,再让 _action_launch_stock_rule() 只处理差额,所以发货链路不会轻易重复造轮子。

DISCUSSION

评论区

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