预留队列

Odoo 改了操作类型预留策略后为什么整批单据“排队顺序都变了”:reservation method、reservation_date 回写与调度队列讲透

很多人知道 operation type 有 At Confirmation、Manual、By Date 三种预留方法,但真正容易出事的是“改配置之后,老 move 的 reservation_date 会不会被回写、调度器按什么顺序抓、backorder 会继承什么策略”。本文从队列视角把这条链路讲透。

库存
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

很多人已经知道 Odoo 有三种 reservation method:

  • At Confirmation
  • Manual
  • Before scheduled date

但很多真实事故并不是“这三种是什么意思”这么基础,而是:

  • 操作类型从 manual 改成 by_date 后,旧 move 会不会跟着改?
  • 为什么两张计划日期差不多的单,调度器总先抓其中一批?
  • backorder 建出来以后,为什么有些立刻抢货,有些继续等?

这些问题都不是概念题,而是队列行为题

一句话先讲透

在 Odoo 里,reservation method 不只是一个静态配置,而是会直接改变三件事:

  1. move 的 reservation_date 是否存在、何时被写入;
  2. 调度器 _run_scheduler_tasks() 会不会把这批 move 捞进待分配集合;
  3. 单据拆补单、改 operation type、重新确认后,旧队列顺序会不会被重排。

所以它真正决定的是:

库存竞争发生在什么时候,以及谁先进入竞争队列。

At Confirmation 真正强的地方不只是“立刻预留”

_action_confirm() 里,凡是 operation type 为 at_confirm 的 move,系统会直接把:

  • reservation_date = 今天

写进去。

很多人以为这一步只是为了展示,其实不是。它相当于给这批 move 发了一张“立刻具备排队资格”的通行证。

因为后面调度器筛 move 时,就会看 reservation_date 和 reservation method。

By Date 的核心不是字段存在,而是字段会被倒推重算

_compute_reservation_date() 会根据:

  • move 的 date
  • operation type 的 reservation_days_before
  • starred priority 对应的 reservation_days_before_priority

去倒推 reservation_date

更关键的是,stock.picking.type.write() 在 reservation method 改动时,还会对相关 move 做批量回写:

  • 从非 by_date 改成 by_date,会重新按计划日期倒推 reservation_date;
  • by_date 改走别的方式,会把相关 move 的 reservation_date 清掉。

这说明 operation type 不是“只影响未来新单”,而是可能重塑现有队列。

Manual 为什么最容易造成“单据活着,但不进队列”

_compute_reservation_date() 里,如果是 manual,系统会把 reservation_date = False

这带来的结果是:

  • move 仍然存在;
  • 需求也仍然真实;
  • 但如果没人手动触发分配,它不会自然进入按日期排队的节奏。

所以 manual 的真正风险不是“系统不能预留”,而是:

队列资格要靠人来授予。

如果团队以为确认单据就等于排上队,manual 模式会制造很多灰色等待单。

调度器到底按什么顺序抓 move

stock.rule._run_scheduler_tasks() 里,待 assign move 的 domain 会要求:

  • state 在 confirmed / partially_available
  • 数量不为 0
  • reservation_date <= today 或 operation type 为 at_confirm

抓出来后排序是:

  • reservation_date
  • priority desc
  • date asc
  • id asc

这说明很多团队嘴上说“按优先级抢货”,但在 Odoo 真正的默认队列里,优先级不是唯一因素。先过门槛的还是 reservation_date。

所以如果两张单都标了星,计划进入队列的日期不同,系统仍然会先处理更早具备资格的那张。

_should_assign_at_confirm() 透露的另一个真相

move 里 _should_assign_at_confirm() 的条件其实是三选一:

  • 源库位本来就 bypass reservation;
  • operation type 是 at_confirm
  • reservation_date <= today

这揭示了一个关键事实:

  • at_confirm 不是唯一能“现在就抢”的路径;
  • by_date 来说,只要日期到了,行为上也会进入即时可分配状态;
  • 某些 bypass reservation 场景甚至完全不走普通预留节奏。

所以不要把 reservation method 简化成“某个按钮默认点不点”的差异。

backorder 为什么有时会立刻重新抢货

stock.picking 创建 backorder 时,也会看 operation type 的 reservation method。

如果是 at_confirm,新生成的 backorder move 仍可能立刻进入抢货语义;反过来,如果还是 manual 或需要等日期窗口,就不会自动表现得那么积极。

这解释了一个常见现场现象:

  • 同样是部分发货后拆出来的补单;
  • 有的补单一生成就像“马上活了”;
  • 有的补单却继续安静等着。

原因往往不是库存突然变多变少,而是队列策略不同。

最容易踩的 4 个坑

1. 改了 operation type,以为只影响新单

实际上现有 move 的 reservation_date 可能被回写,队列顺序一起变。

2. 只看 priority,不看 reservation_date

结果误判“为什么高优先级单没先抢到货”。

3. 把 manual 理解成“只是晚一点预留”

它更准确的意思是“默认不给自动排队资格”。

4. backorder 行为异常时只查库存,不查 operation type

补单的活跃程度很多时候继承自这里。

推荐排查顺序

只要问题是“为什么这批单没有按预期进入预留”,建议按这个顺序看:

  1. operation type 当前 reservation_method 是什么;
  2. move 的 reservation_date 当前值是什么;
  3. 这张单是否因为改配置被回写过日期;
  4. 调度器筛选 domain 是否已把它纳入;
  5. 排序时它输在 reservation_date、priority,还是 planned date。

最后的结论

reservation method 真正控制的不是“能不能预留”,而是:

  • 谁什么时候拿到入场券;
  • 队列如何排序;
  • 配置改动会不会把旧单重排;
  • 拆补单后新的等待节奏如何继承。

所以如果你要理解 Odoo 的库存竞争,别只盯 assign 动作本身,更要盯 reservation queue 是怎么被建出来的

DISCUSSION

评论区

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