POS 批次序列号

Odoo POS 批次序列号为什么不是“填个 lot 就行”:existing lots、create lots 与 quant 匹配讲透

很多团队知道 POS 能卖批次/序列号商品,却低估了 Odoo 背后那条“先找可用 lot、再决定是否可创建、最后与 stock.quant 对位”的链路。本文结合 point_of_sale 与 stock_picking 源码讲清:为什么前台能搜到的 lot 不一定真能出库、为什么 serial 和 lot 数量规则不同,以及遇到批次销售报错时该按什么顺序排查。

POS
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

很多人知道 Odoo POS 支持批次和序列号商品,于是会自然地想成:

前台把 lot 填进去,订单保存,拣货就会自己跟上。

这在概念上没错,但离真实机制差得很远。Odoo POS 处理批次/序列号,不是“收一个文本框”,而是一条“可见 lot 候选 → 订单行 pack lot → 拣货时 quant 对位 / 新 lot 建立”的严格链路。 真正难的,不是把 lot 名字录进去,而是保证这串 lot 在库存、出库规则和跟踪方式上都说得通。

先说结论:POS 前台能看到某个 lot,只代表它在指定来源库位下当前有正库存并被作为候选返回;真正出库时,系统还会区分 existing lots 与 create lots,并按 tracking=serial / lot 采用不同的数量规则。 所以“前台选到了 lot,后台还报错”并不矛盾。

前台“可选 lot”来自哪里

pos.order.line.get_existing_lots() 里,Odoo 会:

  • 取 POS config 对应 picking type 的来源库位;
  • 只看这个来源库位及其子内部库位;
  • 过滤 quantity > 0lot_id != Falsestock.quant
  • 再按 lot_id 聚合,返回 lot 名称与数量。

这说明前台 lot 下拉并不是查全库,也不是看 stock.lot 主表就完了。它本质上查的是“这个 POS 的发货来源位置里,当前还有货的 lot”。

所以如果仓库里确实有这个 lot,但 POS 前台搜不到,先别怀疑浏览器,先看:

  1. POS 用的 picking_type 来源库位是不是对的;
  2. 货是不是在别的 internal location;
  3. quant 是否已经为 0;
  4. lot 是否属于当前公司范围。

existing lots 与 create lots,不是一回事

stock_picking.py_create_production_lots_for_pos_order() 里,Odoo 先看拣货类型是否允许 use_existing_lots,再决定:

  • 先从已有 stock.lot 里找匹配的 (product_id, lot_name)
  • 找到的加入 valid_lots
  • 没找到但又允许 use_create_lots 的,才会新建 lot。

这非常关键。很多现场争议都来自一句话:

为什么前台输入了一个新 lot,有的门店能过,有的门店不能?

答案通常不是用户权限,而是拣货类型是否允许现有 lot / 新建 lot。Odoo 在这里走的是库存操作边界,不是单纯 POS 表单边界。

serial 和 lot,数量规则完全不同

源码在生成 move line 时明确区分:

  • tracking == 'serial',每个 lot/serial 默认数量按 1 算;
  • 若是普通 lot tracking,则按订单行数量的绝对值处理。

这意味着:

  • 批次商品可以“一条 lot 对多件数量”;
  • 序列号商品则天然是一号一件。

所以如果门店把 serial 商品当 lot 商品卖,很容易出现:

  • 前台填了多个序列号,但数量不对;
  • 或数量改了,后台 move line 结构跟不上;
  • 最终表现为验证失败、预留失败或出库数量异常。

quant 匹配成功与否,决定 move line 是“引用现货”还是“仅写 lot 名”

源码里如果找到 existing lot,还会继续去 stock.quant 搜这个 lot 在来源库位下是否仍有正库存:

  • 找到 quant,就把 quant_id 绑定到 move line;
  • 找不到 quant,但 lot 存在,可能只写 lot_id/lot_name
  • 如果压根没有 existing lot,且又允许 create lots,则走新建 lot 路径。

这意味着“lot 存在”与“有可出库存”不是一个判断层级。很多人只看到了 stock.lot 记录存在,就认定 POS 应该能顺利出库,但 Odoo 还要继续追问:

这批货现在是不是确实在这个 POS 的源库位里还有 quant?

UoM 转换也可能把批次销售卡死

_add_mls_related_to_order() 里还有个非常实用但少有人提的保护:如果 move 的 UoM 转换到商品基础单位后,因为 rounding 变成 0,系统会直接抛出 conversion error。

这说明批次/序列号问题未必总是 lot 本身错,也可能是单位换算太小,转换后数量归零,导致 move line 无法成立。 在按重量、分装、最小销售单位很细的业务里,这点尤其容易踩坑。

最容易误判的几个点

误区一:POS 能搜到 lot,就说明一定能出库

不对。能搜到只说明此前聚合量里有它;真正落 move line 时还要看当前 quant 与拣货规则。

误区二:序列号和批次只是“严格程度不同”

不对。它们的数量建模就不同,一边是一号一件,一边是一批可多件。

误区三:新 lot 输不进去,是前台权限问题

很多时候不是,而是 picking type 没开 use_create_lots

建议的排错顺序

POS 批次/序列号异常,建议按下面顺序查:

  1. 先看商品 tracking 类型:是 lot 还是 serial;
  2. 再看 POS picking type:是否允许 existing lots、create lots;
  3. 再看来源库位 quant:这批货是否真在该 POS 源库位有正库存;
  4. 再看前台录入的 lot 集合:是否与产品一一对应,serial 是否做到一号一件;
  5. 最后看 UoM 与 rounding:是否在转换后把数量抹成了 0。

真正难的是“库存真相”和“前台输入”之间的对齐

POS 扫 lot、输 serial,看起来只是收银台多几步;但从源码看,Odoo 其实是在保护三件事:

  • 门店不能卖一件无来源的追踪商品;
  • 已有 lot 和新建 lot 必须受仓储规则约束;
  • 批次、序列号、数量、库位四件事必须对得上。

所以批次/序列号 POS 做得稳不稳,关键不在前台有没有一个输入框,而在于这套前台输入能不能被库存层严格解释成一组合法的 move lines。

把这条链理解透,你再看 Odoo POS 的 lot/serial 处理,就不会把它误会成“高级一点的备注字段”了。

DISCUSSION

评论区

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