很多人已经知道 Odoo 有三种 reservation method:
- At Confirmation
- Manual
- Before scheduled date
但很多真实事故并不是“这三种是什么意思”这么基础,而是:
- 操作类型从
manual改成by_date后,旧 move 会不会跟着改? - 为什么两张计划日期差不多的单,调度器总先抓其中一批?
- backorder 建出来以后,为什么有些立刻抢货,有些继续等?
这些问题都不是概念题,而是队列行为题。
一句话先讲透
在 Odoo 里,reservation method 不只是一个静态配置,而是会直接改变三件事:
- move 的
reservation_date是否存在、何时被写入; - 调度器
_run_scheduler_tasks()会不会把这批 move 捞进待分配集合; - 单据拆补单、改 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_datepriority descdate ascid 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
补单的活跃程度很多时候继承自这里。
推荐排查顺序
只要问题是“为什么这批单没有按预期进入预留”,建议按这个顺序看:
- operation type 当前
reservation_method是什么; - move 的
reservation_date当前值是什么; - 这张单是否因为改配置被回写过日期;
- 调度器筛选 domain 是否已把它纳入;
- 排序时它输在 reservation_date、priority,还是 planned date。
最后的结论
reservation method 真正控制的不是“能不能预留”,而是:
- 谁什么时候拿到入场券;
- 队列如何排序;
- 配置改动会不会把旧单重排;
- 拆补单后新的等待节奏如何继承。
所以如果你要理解 Odoo 的库存竞争,别只盯 assign 动作本身,更要盯 reservation queue 是怎么被建出来的。
DISCUSSION
评论区