库存预约链

为什么拣货会先占用库存:`_action_assign()` 与 `_gather()` 的协作

从 stock.move._action_assign() 和 stock.quant._gather() 看 Odoo 如何把可用库存变成 reservation,以及为什么 assigned 不等于 done。

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

很多人第一次看 Odoo 库存,会把 assigned 误解成“已经完成”。其实它只是说明:系统已经把某些可用库存锁给了这张单据,但真正的出库验证还没发生。

这篇就顺着 /home/ubuntu/odoo-temp/addons/stock/models/stock_move.pystock_quant.py 看一遍:Odoo 到底是怎么从“可用库存”走到“预留库存”的。

先记住一句话:_action_assign() 做的是“分配”,不是“完成”

stock.move._action_assign() 的职责很明确:给符合条件的 move 创建或更新 move line,让这张单的需求和某些 quant 绑定起来。

所以它解决的是:

  • 这张单还缺多少数量;
  • 哪些库存行可以拿来用;
  • 需要把多少数量标成 reserved_quantity
  • 最后这张 move 是 assigned 还是 partially_available

它不做的是:

  • 不把货真的发走;
  • 不把 picking 直接变成 done
  • 不替你跳过后续验证。

_action_assign() 先算“还差多少”,再决定怎么补

源码里最值得注意的第一步,是它先读出每个 move 当前的 reserved_availability,再计算缺口。

你可以把它理解成:

  1. 先看现在已经占了多少;
  2. 再看订单目标是多少;
  3. 差额才是这轮真正要处理的数量。

这就是 Odoo 很少重复造 move 的原因之一。它不是“确认一次就全量重来”,而是持续追踪缺口。

_gather() 负责找“哪些 quant 可以被拿来用”

真正决定“拿哪几行库存”的,不是 move 自己拍脑袋,而是 stock.quant._gather()

这个方法先构造一个 domain,再按 removal strategy 排序:

  • FIFO
  • LIFO
  • closest
  • least_packages

_get_gather_domain() 里最关键的差异,是 strict

  • strict=False 时,location 会用 child_of,lot/package/owner 也会放宽;
  • strict=True 时,位置和特征都要精确匹配。

所以你看到“同样是找库存,有时能找到,有时找不到”,很多时候不是系统坏了,而是 strict 和 domain 条件变了

为什么会出现 partially_available

_action_assign() 不是只会成功或失败,它还会标记“部分可用”。

逻辑很简单:

  • 找到的可用量 >= 需求量:assigned
  • 找到的可用量 < 需求量:partially_available

这在实务里非常重要,因为它让 Odoo 能继续工作,而不是因为少一部分货就整单卡死。

例如:

  • 你手上只有 8 件;
  • 销售要 10 件;
  • 系统会先锁 8 件,并把状态标成部分可用;
  • 剩下 2 件继续等补货或后续规则处理。

你容易误解的两个点

1)“有库存”不等于“能分配”

quantity 只是总量,真正可分配的是:

quantity - reserved_quantity

如果库存行已经被别的 move 占了,当前单也许看得到数量,但拿不到。

2)“找得到 quant”不等于“已经能出库”

_gather() 只是找候选库存; _action_assign() 只是把候选库存锁住; 真正出库还得等 move / picking 后续确认。

开发和排错时该看什么

如果你在调 reservation 问题,建议按这个顺序看:

  1. move 的状态是不是 confirmed / waiting / partially_available
  2. 当前 location、lot、package、owner 是否和 quant 匹配;
  3. removal strategy 是不是你以为的 FIFO;
  4. strict 有没有被切换;
  5. 这个 move 有没有走 bypass reservation 的特殊链路;
  6. reserved_quantity 是否已经被别的单据占掉。

一句话总结

Odoo 的库存分配不是“先抓货再说”,而是:

先算缺口,再用 _gather() 找可用 quants,最后通过 _action_assign() 把这部分库存正式预留给当前 move。

理解了这条链,你就能把“库存明明在,但单据就是分配不上”的问题拆开看了。

DISCUSSION

评论区

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