批量拣货

Odoo 批量拣货为什么不是“把几张单放进一个列表”:batch transfer、wave 和批次校验逻辑讲透

很多人把 Odoo 的 batch transfer / wave 理解成“把多张 picking 打个包”。但官方源码里的重点其实是:哪些单能合批、批次状态如何跟随明细变化、空单为什么会在校验时被自动摘出,以及 batch 和 wave 为什么不能混。本文把这套逻辑讲透。

库存
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

Odoo 的 batch transfer / wave,不是“把几张 picking 放进一个列表里统一看”这么简单。

它更准确的语义是:

把一组符合约束的拣货单,组织成一个可共同分配、共同执行、共同校验,但仍保留各自库存事实的作业容器。

所以 batch 的重点,不只是“聚合展示”,而是:

  • 哪些 picking 可以被纳入同一容器
  • 这个容器何时进入执行态
  • 校验时哪些空单要被摘出
  • batch 与 wave 为什么不能乱混
  • 批次自身的状态为什么会跟着子 picking 自动演化

这也是为什么官方专门做了 stock.picking.batch,而不是让用户靠过滤器把几张单勾在一起就算完。


为什么“只是打包几张单”这个理解会误导人

因为从界面观感上,batch 确实像一个装 pickings 的壳。

但如果它真只是个壳,很多问题就答不出来:

  • 为什么 operation type 不同的单不能进同一批
  • 为什么 done / cancel 的批次不能继续 merge
  • 为什么有的 picking 会在验证时被自动从批次里移除
  • 为什么批次也有自己的 state
  • 为什么 wave 和 batch 在源码里还要区分 is_wave

这些现象都在说明:

batch 不是静态集合,而是带执行规则的作业对象。


源码抓手:addons/stock_picking_batch/models/stock_picking_batch.py

配合向导文件一起看更清楚:

  • addons/stock_picking_batch/models/stock_picking_batch.py
  • addons/stock_picking_batch/wizard/stock_picking_to_batch.py

最关键的方法包括:

  • _compute_allowed_picking_ids
  • _compute_state
  • action_confirm
  • action_done
  • _sanity_check
  • action_merge
  • StockPickingToBatch.attach_pickings

第一步:不是任何 picking 都能进 batch

_compute_allowed_picking_ids 会按这些条件筛:

  • 同公司
  • state 必须在允许集合里
  • 如果已有 picking_type_id,还得同 operation type
  • draft picking 只有在 batch 本身还是 draft 时才允许加入

这意味着 batch 的边界不是“用户想怎么拼就怎么拼”,而是:

只有执行语义兼容的 picking,才允许进入同一个作业容器。

这很合理。因为不同 operation type 的单据,往往代表:

  • 不同流程节点
  • 不同仓位逻辑
  • 不同人员和设备
  • 不同校验习惯

把它们强塞进一个批次,执行上通常只会更乱。


第二步:batch 自己也不是手填状态,而是跟着 picking 演化

_compute_state 会根据子 picking 状态来计算批次状态:

  • 全部取消 → batch 取消
  • 所有未取消 picking 都 done → batch done
  • 否则保留进行中 / 草稿等语义

这说明 batch 的状态不是独立业务事实,而是:

对子 picking 执行结果的作业层总结。

换句话说,batch 不是替代 picking 的状态机,而是叠加在 picking 之上的“执行容器状态机”。


第三步:action_confirm 的重点不是换状态,而是确认“这批单真的能一起干”

action_confirm 会做几件事:

  • 没有 picking 不让确认
  • 对 picking 执行 action_confirm()
  • 做公司检查
  • 把 batch 状态设为 in_progress

这说明 batch confirm 不是 UI 上“我准备好了”的按钮,而是:

把一组 pickings 正式推进到同一批作业执行语境。

这就是为什么它会连带推动 picking 自己进入确认流程。


第四步:批量校验时,空单不是一刀切报错,而是按语义拆开处理

action_done 这段源码很值得读。

它会先区分几类 picking:

  • 正常待处理的 pickings
  • waiting/confirmed 且没有实际处理数量的空单
  • assigned 但本质为空的单
  • 至少做了一部分的单

随后它会:

  • 对一些“空但没必要继续校验”的 picking 从 batch 里摘掉
  • 对真正要执行的 picking 统一做 sanity_check
  • 再调用这些 picking 的 button_validate()

这说明 Odoo 对 batch validate 的理解,不是“所有子单全有或全无”。

而是:

尽可能让有实际作业内容的 picking 继续完成,同时把纯空壳 picking 安全地剥离。

这比简单粗暴地整批报错要成熟得多。


为什么 batch 里还要算重量、体积、lots 文本等

源码里 batch 会计算:

  • estimated_shipping_weight
  • estimated_shipping_volume
  • show_lots_text
  • move_ids
  • move_line_ids

这说明 batch 不是只拿来“批量点按钮”,它还承担了一个实际执行视角:

  • 这批货大概多重
  • 体积多大
  • 是否涉及 lot 展示
  • 聚合后的 move / move line 长什么样

这在仓库现场非常重要,因为批量作业通常首先是一个组织拣货与搬运资源的问题。

所以 batch 是执行单元,不只是数据集合。


第五步:为什么 batch 和 wave 不能混着 merge

action_merge 里有几个特别关键的约束:

  • picking type 不同不能 merge
  • is_wave 不同不能 merge
  • state 不同不能 merge
  • done / cancel 的 batch/wave 不能 merge

这说明 Odoo 认为 batch 和 wave 虽然很像,但仍然不是同一种语义容器。

最朴素的理解可以这么记:

  • batch 更像把一组 transfer 绑成一个执行批次
  • wave 更强调波次组织与作业编排语义

源码没有允许你把这两种东西混着合并,本质上是在保护操作含义不被搅乱。


attach_pickings 也说明:加入 batch 不是简单 many2one 赋值

向导 stock.picking.to.batch 在新增 batch 时,会先检查:

  • 选中的 pickings 是否属于同一公司

然后创建 batch,并根据选项决定:

  • 仅创建 draft
  • 还是直接 action_confirm()

这说明“把 picking 放进 batch”在 Odoo 里并不是静态归档动作,而是带执行后果的动作。

尤其是非草稿模式下,加入 batch 之后就可能直接进入作业态。


为什么 _sanity_check 如此重要

_sanity_check 会检查当前 batch 下的 picking 是否都仍然属于 allowed_picking_ids

如果有不兼容的单据混进来,就直接报错,并列出 incompatible transfers。

这很像一个作业前的最后保险丝。

因为 batch 允许你聚合很多单,但聚合越多,越需要防止:

  • 状态漂移
  • 类型不一致
  • 后续加单导致容器语义被污染

所以 sanity check 其实是在守住一句话:

批量执行可以提高效率,但前提是这批东西真的适合一起执行。


实战里最容易误解的 5 件事

1)batch 不是报表分组

它是可执行容器,不是单纯分组视图。

2)把 picking 放进 batch,不等于业务事实合并

库存事实仍然发生在各自 picking / move / move line 上,batch 只是组织层。

3)空单在 batch validate 时不是“异常边角料”

官方专门设计了摘出逻辑,说明这是常见现场情况。

4)wave 和 batch 不是完全同义词

既然源码要用 is_wave 区分,还禁止混并,就说明两者操作语义不同。

5)不要为了“方便”把不兼容 operation type 硬合批

短期省点点选,长期会增加现场混乱与解释成本。


小结

看完官方源码后,最实用的理解方式是:

Odoo 的 batch transfer / wave,本质上是仓库作业容器,而不是若干 picking 的可视化文件夹。

它负责的是:

  • 把兼容的单据组织起来
  • 让它们能一起分配、一起执行、一起校验
  • 但又不抹掉每张 picking 原本独立的库存事实

所以你如果要做批量拣货相关定制,最先该守住的,不是界面好不好看,而是:

  • 批次准入边界
  • 批次执行边界
  • 批次与 picking 的责任边界

这三条一旦守住,batch 才会真正提升仓库效率,而不是把复杂度藏起来。

DISCUSSION

评论区

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