先说结论
在 Odoo 里,“给 move line 填上一个 result_package_id” 和 “系统认定这是一笔整包作业”,不是一回事。
前者更像是:
- 这些明细最后装进了哪个包
后者表达的则是:
- 这次作业能不能被系统当成一个完整 package 单元来处理、搬运、继续传递和下游识别
所以 package 不是简单的物流标签。真正难点在于:
Odoo 什么时候认为你是在处理“若干行”,什么时候认为你是在处理“一个完整包裹”。
为什么很多人会把 package 想浅了
因为在界面上,最显眼的动作是:
- Put in Pack
看起来像是:
- 选几条行
- 点一下按钮
- 系统生成一个 package
于是很容易误会成:
- package 就是一个容器字段
- result_package_id 就是“装箱后显示一下”
但源码往下看会发现,package 会进入:
- quant 维度
- reservation 维度
result_package_id/package_id的源包与目标包语义_check_entire_pack()这类完整包判断- package level 相关聚合
这说明 Odoo 处理的不是“箱子外观”,而是包装层级上的库存结构。
package_id 和 result_package_id 先别混
这是理解整包语义的第一步。
可以先用最朴素的话记:
package_id:货原本在哪个包里result_package_id:这次操作后,货要进哪个包
也就是说:
- 一个是来源包
- 一个是结果包
这两个字段一旦混掉,你就会把很多场景看错,比如:
- 拆包后重装
- 同包转移
- 从旧包挪进新包
- 整包拣起又整包放下
Odoo 之所以能区分“包从哪里来、包最终变成什么”,靠的就是这两个方向性的字段,而不是单一一个 package 标记。
action_put_in_pack() 真正做的不是“生成个箱号”
stock.move.line.action_put_in_pack() 往下走时,会先做两件很关键的事:
- 通过
_get_lines_and_packages_to_pack()选出哪些 move line 能打包 - 再用
_put_in_pack()给这些行写入result_package_id
看起来好像仍然只是“写字段”,但里面藏着两个很重要的判断:
第一,picked 优先
如果已经存在 picked 的 move line,系统会优先打包这些 picked 行,而忽略还没 picked 的行。
这背后的设计不是技术细节,而是业务保护:
打包动作优先服务于已经被确认正在作业的那一部分货,而不是把尚未真正拣起的行也一起假装打包。
第二,递归打包 package
如果一部分内容本身已经在 package 里,Odoo 不只是打包散行,还可能继续把 package 往更外层 package 里装。
也就是说,系统天然支持:
- 行进包
- 包再进更大包
- 外层包继续形成层级
这已经不是简单“给箱子编号”,而是包装树结构。
entire pack 的难点:不是“同包”就等于“整包”
很多人会把“这些货都在一个 package 里”理解成“我正在做整包操作”。
但源码不是这么粗暴判断的。
_check_entire_pack() 与相关逻辑真正想确认的是:
- 这次 picking 下,某个 package 的相关 move line 是否构成一个完整、可整体处理的包
- 有没有只动了包里一部分货
- 有没有同一个包在多个 transfer / 多种条件下被拆散
所以 entire pack 的重点不是“同名 package”,而是:
这个包是不是被这次动作完整而一致地接住了。
只要你对同一个包:
- 只挑了一部分行
- 目标不一致
- picking 之间被拆开
- 中途改写了 result_package_id
那它就可能不再被系统视为一个干净的整包单元。
为什么 package level 比 move line 更像“仓库操作语言”
move line 是明细层。
它表达的是:
- 什么产品
- 数量多少
- 从哪到哪
- lot / owner / package 是谁
但 package level 更像仓库执行语言,它表达的是:
- 这一个包装单元被整体搬运
- 这批作业可以按包聚合
- 下游界面与逻辑可以不必先拆回逐行理解
所以当你在做整包拣货、整托移动、扫码整包过站时,真正重要的不是“我有多少行”,而是“系统能不能把这一坨行当成一个 package 级单元”。
为什么 result_package_id 还会影响后续 putaway
_action_assign() 结束后,move line 还会走 _apply_putaway_strategy()。
而 _put_in_pack() 在某些情况下也会根据 package 去算默认目标库位。
这意味着 package 不只是“包装结果”,它还会继续影响:
- 目标库位重定向
- putaway 行为
- 后续包级移动
换句话说:
包裹结构一旦成立,后面的库位决策也可能因此变化。
所以 package 不是出库最后一步的摆设,而是会继续向后传导影响的结构信息。
实战里最容易踩的 5 个坑
1. 把 package_id 和 result_package_id 当成同义词
一个是来源包,一个是结果包。
2. 以为 Put in Pack 只是界面装箱动作
它其实在建立包级结构。
3. 以为同一个包下的行天然就是 entire pack
只要只动了一部分,整包语义就可能破。
4. 以为 package 不影响后续库位或 reservation
实际上它会继续进入 quant 与策略层。
5. 只从 move line 看问题,不从 package level 看
这样很难解释扫码整包、整托搬运和多层包装的真实语义。
一句话记忆法
在 Odoo 里,package 不是“箱子标签”,而是库存结构;
result_package_id只是结果落点,而 package level / entire pack 才是在表达系统能不能把这次动作当成一个完整包装单元来处理。
理解这句话,你再看整包拣货、整包转移和扫码包装,就不会只盯着几条 move line 了。
DISCUSSION
评论区