很多人第一次教仓库同事用 Odoo 时,都会把 Put in Pack 描述得很轻:
- 选几行;
- 点一下按钮;
- 生成一个箱号。
这个说法对培训有帮助,但对排错帮助不大。
因为源码里的 action_put_in_pack() 真正在保护的,不只是“生成包裹”,而是整条发运包裹工作流:
哪些行现在允许装箱、它们是否指向同一目的地、要不要先定义 package type、已拣货与未拣货谁优先、已有包是否继续嵌套,以及装箱后要不要立刻打标签。
所以 Put in Pack 不是一个 UI 小动作,而是仓内执行秩序的入口。
一句话先讲透
Odoo 的 Put in Pack 至少串了 5 层判断:
- 先看 destination 是否一致,不一致就不允许直接打包;
- 再看 operation type 是否要求先选 package / package type;
- 选取待装箱对象时优先 picked 行;
- 需要时不只装 move line,也会继续把已有 package 往外层 package 里装;
- 装箱完成后还能按 operation type 自动打印 package label。
这就是为什么“只是打个包”背后会冒出向导、弹窗和标签动作。
第一步为什么先查 destination
_pre_put_in_pack_hook() 里第一件事不是建 package,而是 _check_destinations()。
如果待装箱行的 location_dest_id 不一致,系统不会假装看不见,而是直接弹“Choose destination location”向导。
这层保护很重要,因为在仓库现实里:
- 一个运输箱里的货,通常应该去同一条后续路径;
- 如果目的地都不同,还强行先装成一个包,后面要么拆箱,要么让 traceability 混乱。
所以 Odoo 在装箱前先保证目的地语义一致,不是多此一举,而是在阻止错误包装结构。
为什么有时一按按钮先弹 package type 向导
很多团队会疑惑:
- 同样都是 Put in Pack;
- 为什么有时直接生成包裹;
- 有时却先弹让你选 package type。
答案在 _should_display_put_in_pack_wizard():
- 如果 operation type 开启了
set_package_type; - 且你这次没显式传 package / package type / package name;
- 系统就会要求先补 package 定义。
也就是说,Odoo 允许企业把装箱从“临时生成箱号”升级为“带包装规格约束的标准流程”。
这对以下场景尤其有价值:
- 不同承运商要求不同包材;
- 包装规格会影响后续标签模板;
- 需要区分信封、纸箱、托盘等不同作业容器。
为什么系统优先装 picked 行
_get_lines_and_packages_to_pack() 有个很容易被忽略的设计:
- 只要存在 picked 的 move line,系统就优先处理 picked 行;
- 未 picked 的行会暂时被忽略。
这背后的意思很明确:
打包优先服务于已经进入实际作业状态的货,而不是把“计划里要拣的货”和“手上已经拣到的货”混装。
对现场执行来说,这能明显减少两类错误:
- 纸箱已经打出来了,但实际还有一半货没拣到;
- 系统层面看似装箱完成,现场却仍然在补货。
为什么 Put in Pack 不只处理散行,还会递归处理已有 package
同一个 action_put_in_pack() 流程里,系统会把对象拆成两类:
- 还没有
result_package_id的 move line; - 已经在 package 里的内容,进一步提取其
outermost_package_id。
这意味着 Odoo 支持的不是单纯“散货入箱”,而是连续包装:
- 先把商品装进小箱;
- 再把多个小箱装进大箱;
- 最后按最外层运输包继续流转。
这条链路和普通 package 字段介绍不同,它强调的是操作流程连续性,而不是单个字段含义。
为什么装箱后有些旧 package 关系会被清掉
在 stock.package.action_put_in_pack() 和 move line 的写入 / unlink 逻辑里,都有一个重要清理动作:
- 如果某些 package 的
package_dest_id还在; - 但它已经不再关联任何 active picking;
- 系统会把这段失效的目标包关系清掉。
这层设计解决的是“包装树残影”问题。
否则你一旦反复重装、拆包、重分流,系统里会留下很多看似还挂在外层箱上的旧关系,后续追踪会越来越假。
自动打印标签为什么挂在 Put in Pack 后面
_post_put_in_pack_hook() 里,如果 operation type 开启了:
auto_print_package_label
系统会根据 package_label_to_print 决定输出 PDF 还是 ZPL。
这说明 Odoo 认定装箱和标签不是两件松散动作,而是一条紧邻链路:
- 货一旦真正装入运输包;
- 包裹身份就应该被立即固化成标签。
这对现场最直接的好处是:
- 避免先装箱、后面忘打标签;
- 降低箱号与实体箱脱节的概率;
- 把包装动作和可追踪载体绑定在同一个时刻。
实战里最该先排查什么
只要遇到 Put in Pack 行为异常,我建议先别急着看包裹最终结果,先按这个顺序拆:
- 待装箱行的 destination 是否一致;
- operation type 是否要求
set_package_type; - 当前有无 picked 行,系统是不是因此只取了部分行;
- 这次是在装散行,还是在把现有 package 往外层继续装;
- 标签自动打印是否由 operation type 配置触发。
这样基本能把“为什么弹窗”“为什么少装了几行”“为什么直接出标签”这些问题拆开。
最后的结论
Put in Pack 在 Odoo 里从来不是一个“给箱子起名字”的按钮。
它真正负责的是:
- 包前校验目的地;
- 决定是否要求包装规格;
- 以 picked 事实驱动装箱;
- 维护包中包链路;
- 在装箱完成的瞬间输出发运标签。
所以你如果想把装箱流程做稳,关注点不该只放在 result_package_id,而要放在整条从拣货到发运包裹固化的工作流上。
DISCUSSION
评论区