很多人知道 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 > 0且lot_id != False的stock.quant; - 再按
lot_id聚合,返回 lot 名称与数量。
这说明前台 lot 下拉并不是查全库,也不是看 stock.lot 主表就完了。它本质上查的是“这个 POS 的发货来源位置里,当前还有货的 lot”。
所以如果仓库里确实有这个 lot,但 POS 前台搜不到,先别怀疑浏览器,先看:
- POS 用的
picking_type来源库位是不是对的; - 货是不是在别的 internal location;
- quant 是否已经为 0;
- 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 批次/序列号异常,建议按下面顺序查:
- 先看商品 tracking 类型:是 lot 还是 serial;
- 再看 POS picking type:是否允许 existing lots、create lots;
- 再看来源库位 quant:这批货是否真在该 POS 源库位有正库存;
- 再看前台录入的 lot 集合:是否与产品一一对应,serial 是否做到一号一件;
- 最后看 UoM 与 rounding:是否在转换后把数量抹成了 0。
真正难的是“库存真相”和“前台输入”之间的对齐
POS 扫 lot、输 serial,看起来只是收银台多几步;但从源码看,Odoo 其实是在保护三件事:
- 门店不能卖一件无来源的追踪商品;
- 已有 lot 和新建 lot 必须受仓储规则约束;
- 批次、序列号、数量、库位四件事必须对得上。
所以批次/序列号 POS 做得稳不稳,关键不在前台有没有一个输入框,而在于这套前台输入能不能被库存层严格解释成一组合法的 move lines。
把这条链理解透,你再看 Odoo POS 的 lot/serial 处理,就不会把它误会成“高级一点的备注字段”了。
DISCUSSION
评论区