很多人第一次深挖 Odoo lot 逻辑,都会被一个细节吓一跳:
- 原本库位里看起来有一批未追踪库存;
- 你在 move line 上补了
lot_id; - 系统没有直接报错,反而像是把一部分库存“转成了这个 lot”。
如果只从业务直觉看,这很像系统在偷偷改历史。 但从源码角度看,这其实是一个非常有意图的补偿机制。
关键入口在 stock.move.line._synchronize_quant()。
先说结论
当 Odoo 在同步某条带 lot 的 move line 时,如果发现该 lot 维度下的可用量被打成负数,它会再去看:
- 同产品
- 同库位
- 同 package / owner 条件
- 但
lot_id = False的未追踪 quant
如果这类未追踪库存存在,系统会尝试拿其中一部分来补偿,把它“搬”成当前 lot 维度的库存。
所以你看到的并不是“凭空生货”,而是:
Odoo 在同一库存事实内部,把一部分原本未指明批次的数量,重新归属到了当前 lot。
为什么要有这层补偿
因为真实业务里,追踪信息的录入时点并不总是和数量流转完全同步。
常见情况包括:
- 先做了数量动作,后补 lot;
- 仓库现场先收货,批次在后续操作里才明确;
- 某些场景下,库存确实在这个位置,但历史上没先写 lot。
如果系统在这种情况下永远只会说“lot 库存不足”,那很多实际流程根本走不动。
Odoo 的设计是:
- 先尊重 lot 维度;
- 如果 lot 维度下出现负量,再看是否有同条件的未追踪库存可以补;
- 只有连未追踪库存都没有时,问题才真正暴露为库存不足。
这相当于给“追踪信息晚到一步”的现实场景留了一个缓冲带。
这里最容易被误解的地方
很多人会把它理解成:
- 系统允许你随便把任何货指给任意 lot;
- lot 库存不足也没关系,反正 Odoo 会自己补。
这就理解过头了。
补偿并不是无限制发生,它有非常明确的边界:
- 只能在同产品下进行;
- 还要同库位;
- 还受 package / owner 维度约束;
- 并且只能从
lot_id = False的未追踪库存里拿。
也就是说,Odoo 不是在创造 lot 历史,而是在同一真实库存池里,把“原本未声明批次”的那部分数量重新挂到当前 lot 名下。
为什么这不是普通 reservation 逻辑能解释的
如果你只盯 _update_reserved_quantity(),会很容易漏掉这个设计。
因为这件事不是单纯“能不能预留到某个 quant”, 而是更深一层的:
当带 lot 的同步动作让该 lot 维度的 available quantity 变成负数时,系统要不要尝试从未追踪库存中搬一部分过来兜住这个差额?
源码里这一步发生在 _synchronize_quant() 中,而且是在处理 available quantity 时做的补偿。
也就是说,它更偏“库存事实整理”,而不是“预留争抢”本身。
哪些场景最容易触发这种现象
1)给原本没写 lot 的 move line 补 lot
这是最典型的。
你会感觉系统“怎么没报 lot 不足”,其实它可能是拿了未追踪 quant 做了补偿。
2)收货 / 内部流转过程中,lot 信息录入滞后
数量已经在库位里了,lot 后补。此时系统为了让 lot 维度和真实库存对齐,可能会走这条补偿链。
3)二开批量补 lot
如果脚本给 move line 批量写 lot_id,而团队又没意识到 _synchronize_quant() 会尝试迁移未追踪库存,就很容易误读结果,以为系统“自动造了 lot 库存”。
为什么这种机制既有用也危险
有用,是因为它让很多真实操作不会因为“追踪信息晚一步”而完全卡死。
危险,是因为它也会掩盖一些流程管理问题:
- 本该在收货时就明确的 lot,被拖到后面才补;
- 现场操作对 lot 纪律不严格;
- 自定义程序误把“未追踪库存”当作可随时洗成任意 lot 的缓冲池。
所以这个机制应该被理解成:
- 系统对现实世界的容错,
- 而不是 lot 管理可以随便补的通行证。
与追溯准确性的关系
这也是最该提醒业务方的一点。
系统能把未追踪库存补到某个 lot,不代表你的追溯历史就天然可信。
因为从追溯角度看,真正重要的问题是:
- 这批货一开始到底是不是这个 lot;
- 你只是现在才录,还是现在才决定;
- 现场证据和系统录入是否一致。
Odoo 只能帮你把库存维度整理到一致, 但它不能替你证明历史事实。
所以如果企业对 lot traceability 要求很高,这个补偿机制反而提醒你:
- 应该把 lot 录入时点前置;
- 不要长期依赖“后补再补偿”。
推荐的排错顺序
当你看到“写了 lot 以后库存像自动补出来”时,建议按这个顺序看:
- 当前 move line 写 lot 前,是不是原本就没有
lot_id; - 同产品、同库位下,是否存在
lot_id = False的 quant; - package / owner 条件是否一致;
- 这次动作是在改 available quantity,还是只是在改 reserved quantity;
- 是人工补录,还是自定义代码批量写 lot;
- 业务上这批货是否真的应该属于该 lot。
这样你就能区分:
- 系统是在做合理补偿;
- 还是业务流程本身已经把 lot 管理拖散了。
最后的结论
Odoo 在 lot 录入时允许从未追踪 quant 中做补偿,不是因为它不重视追踪,而是因为它知道现实仓库并不总能在第一秒把所有批次信息录完整。
这套机制真正表达的是:
如果同一真实库存池里只是“还没声明批次”,系统可以帮你把那部分数量重新挂到当前 lot 上。
但它的价值在于兜底,不在于常态化依赖。
如果你把它当成 lot 管理的默认工作流,最后受伤的通常不是 Odoo,而是你的追溯可信度。
DISCUSSION
评论区