未开始预留

Odoo 为什么明明看起来有库存,却还没有开始预留:free_qty、reservation_date、operation type 排查讲透

有些问题不是“预留失败”,而是“系统压根还没开始预留”。本文不再泛讲“有货却不分配”,而是专门拆解 free_qty、reservation_date、reservation method、operation type、bypass reservation 等因素,解释 Odoo 为什么会让一张 move 暂时停在 confirmed。

Odoo 开发 库存
进阶 开发者 3 分钟阅读
0 评论 0 点赞 0 收藏 10 阅读

先说结论

很多人排查库存时,一看到 move 还没 assign,就立刻问:

  • 为什么有货却不分配?

但有相当一类问题,根本不是“分配失败”,而是:

系统在当前时点还没打算开始抢这批库存。

最实用的理解是:

  • free_qty 决定“理论上现在还有多少自由可用量”
  • reservation_method + reservation_date 决定“这张 move 什么时候该进入库存竞争”
  • operation type 决定这套时机规则挂在哪个作业制度上
  • should_bypass_reservation() 决定这张 move 是否根本不走普通 reservation 路径

所以有些 move 停在 confirmed,并不是因为系统没看到库存,而是因为:

  • 它还没到该预留的时间
  • 或者 它根本不该走普通预留逻辑

为什么这类问题特别容易被误判

因为用户的观察顺序通常是:

  1. 产品还有库存
  2. 这张单还没 ready
  3. 那一定是系统没分到货

但源码真正的判断顺序不是这样。

系统更像是在问:

  1. 这张 move 现在应不应该开始 assign?
  2. 如果应该,当前 location 有没有 free_qty?
  3. 如果有,能不能按 reservation 规则真正 reserve 住?

所以“明明有库存却还没开始预留”这类问题,很多时候卡在第 1 步,而不是第 2 步或第 3 步。


free_qty 不是“你看到的库存总量”

很多人会把 free_qty 想成:

  • 仓里剩多少就等于 free_qty

其实不是。

在产品数量计算里,free_qty 更接近:

当前在手量扣掉已预留量之后,理论上还能自由使用的量。

所以它本来就比 on hand 更贴近 reservation 逻辑。

这意味着:

  • on hand 很多,不代表 free_qty 很多
  • free_qty 看起来够,也不代表这张 move 现在就会立刻 reserve

换句话说:

free_qty 只是“如果现在开始竞争库存,大概还有多少可抢”;它不是“系统已经决定要抢你这张单”的承诺。


真正决定“现在要不要开始抢”的,是 reservation timing

这层很多人容易忽略。

stock.move._should_assign_at_confirm() 里,源码逻辑大意是:

  • 如果 bypass reservation → 可以按特殊逻辑走
  • 或者 picking type 的 reservation_method == at_confirm
  • 或者 reservation_date <= 今天

否则,哪怕 move 已 confirmed,也不代表它会马上进入 _action_assign()

这非常关键。

因为它说明:

“有没有库存” 和 “系统此刻会不会动手预留” 是两个问题。


reservation_method 为什么其实是在决定库存竞争的入场时机

在 operation type 上,Odoo 提供常见几种 reservation method:

  • at_confirm
  • manual
  • by_date

它们真正决定的不是“预留功能开不开”,而是:

  • 这类 move 在什么时点允许进入 reservation 竞争

at_confirm

一确认就尽快尝试。

manual

默认不自动抢,等人工或后续动作触发。

by_date

reservation_date 到了再说。

这就是为什么两张看起来一模一样的 move,会有完全不同的库存体感:

  • 一张一确认就锁货
  • 另一张可能还在 confirmed 静静待着

根因不是库存本身,而是 reservation method 不同。


reservation_date 是把“策略”落成“具体日期”的关键字段

如果 reservation method 是 by_date,系统会根据 move 的计划日期倒推出 reservation_date

源码里 stock.move._compute_reservation_date() 就在干这个事情:

  • 取 move.date
  • 减去 operation type 上的提前天数
  • 得到真正的 reservation_date

所以这张 move 即使已经存在、库存也看起来有,系统仍可能选择:

  • 今天先不抢

因为在它眼里:

  • 这张 move 还没到该进场的时间窗口

这和很多用户的直觉冲突很大,因为用户更容易按“单据已经确认了”思考,而 Odoo 更像按“执行窗口到了没”思考。


operation type 为什么是这个问题的制度层开关

很多人排查 reservation 时,只盯当前 move。

其实 reservation timing 很多时候不是 move 自己决定的,而是它所属的 operation type 决定的。

也就是说:

  • 这张 move 是不是 at_confirm
  • 是不是 by_date
  • 提前几天进场

这些很多是 picking type / operation type 级别的制度配置。

所以如果你发现一批 move 都有类似表现:

  • 都 confirmed 很久
  • 都没开始 assign
  • 但库存看起来没问题

优先看 operation type,往往比只盯单张 move 更快。


should_bypass_reservation() 为什么能让问题彻底换个维度

源码里还有一个很关键的判断:

  • location.should_bypass_reservation()
  • 或者产品不是 storable

这时 move 可能根本不走普通 reservation 路径。

翻成人话就是:

有些 move 本来就不是按“先锁 quant,再进入 assigned”这套常规剧本来跑的。

所以如果你在某些库位 / 产品上硬套普通 reservation 逻辑,就会看得越来越怪。

这也是为什么排查时第一步应该先确认:

  • 当前 location / product 是否属于 bypass reservation 场景

不然你会一直在问一个系统根本没打算回答的问题。


为什么“还没开始预留”和“预留失败”必须分开看

这两个问题很像,但不是一回事。

还没开始预留

重点问的是:

  • 为什么它还没进 _action_assign()
  • reservation_date 到没到
  • reservation method 是什么
  • operation type 是怎么规定的

预留失败

重点问的是:

  • 进了 assign 之后
  • 为什么 free_qty / quant / lot / owner / package / strict 边界不满足

所以这篇更关注的是前者:

  • 为什么系统当前时点还没动手

而不是:

  • 动手以后为什么失败

把这两层分开,你排查会快很多。


一个很常见的误判场景

假设:

  • 产品页面还有 free_qty
  • 出库 move 处于 confirmed
  • 用户说“这不是应该马上 ready 吗?”

这时很多人直接去查 quant。

但更高效的顺序其实是:

  1. 先看 move 有没有上游依赖,排除 waiting 逻辑
  2. 再看 reservation_method
  3. 再看 reservation_date
  4. 再看 operation type 的制度配置
  5. 最后才去看 quant / free_qty / reserve 细节

因为这类问题里,最常见的真相往往是:

  • 它不是 reserve 失败,而是压根还没轮到 reserve

最实用的排查顺序

1. 先确认当前 move 是否应该走普通 reservation

看:

  • should_bypass_reservation()
  • 产品是不是 storable

2. 再确认它当前是否到了 assign 时机

看:

  • reservation_method
  • reservation_date
  • _should_assign_at_confirm()

3. 再确认当前 operation type 的制度配置

看:

  • 是 at_confirm 还是 by_date / manual
  • 提前天数怎么配

4. 最后再看 free_qty

如果前面都没问题,再问:

  • 当前 location 真的还有可自由竞争的量吗

5. 如果 free_qty 也够,再下钻 reservation 失败边界

比如:

  • lot / owner / package / strict / quant 等细节

这样排查会比“上来就看产品数量”高效得多。


最容易踩的 6 个坑

1. 把 on hand 当成“现在就该预留”

不是。

2. 把 free_qty 当成“已经为这张单保留的量”

也不是。它只是理论自由量。

3. 忽略 reservation timing

这会把时机问题误判成库存问题。

4. 只看 move,不看 operation type

很多制度规则根本挂在 picking type 上。

5. 看到 confirmed 就默认系统应该已经开始 assign

不一定,可能还没到 reservation_date。

6. 忘了 bypass reservation 场景

这样会把本来不走普通预留的 move 也硬往 reservation 逻辑里塞。


一句话记忆法

把这个问题记成一句话:

有库存却还没开始预留,很多时候不是库存不够,而是这张 move 还没到该进库存竞争的时间,或者它根本不走普通 reservation 路径。

理解这一句之后,你以后再看到一张 move 停在 confirmed,就不会只盯着库存数字问“为什么还没分配”,而会先去问:

  • 它现在到底该不该开始分配?

DISCUSSION

评论区

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