包裹链路

Odoo 包裹字段为什么最容易看反:package_id、result_package_id、package level 与装箱链路讲透

很多人知道 Odoo 有 package,却总把 package_id 和 result_package_id 看成“同一个包的两个名字”。其实一个描述来源容器,一个描述结果容器,中间还夹着 package level 与整包操作语义。本文把装箱链路真正的时间顺序讲透。

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

很多人第一次读 Odoo 包裹相关字段,都会陷入一个看似合理但非常致命的误解:

  • package_id 是包裹;
  • result_package_id 也是包裹;
  • 那它俩大概只是“包裹名称前后不一致”吧。

不是。

这两个字段最关键的区别,不在于“它们都指向 stock.package”,而在于:

它们站在库存流转的不同时间面。

如果你没先把“来源容器”和“结果容器”分开,后面看整包作业、二次装箱、包中包、package level、put in pack 向导,基本都会越看越乱。

一句话先讲透

  • package_id:货现在从哪个包里出来
  • result_package_id:这次处理后,货要进入哪个包里去

前者是来源视角,后者是结果视角。

所以这不是“同一个字段重复建模”,而是 Odoo 明确把一次库存动作拆成:

  • 货从哪里来;
  • 货处理完以后落到哪里。

这正是装箱、拆箱、整包转移能成立的基础。

为什么这两个字段不能合并

想一个最简单的仓库动作:

  • 从散货区拿出 10 件;
  • 装进一个新建纸箱;
  • 纸箱再跟着发运。

在这个动作里:

  • 开始时货可能没有 package_id
  • 结束后却会有 result_package_id

再看另一个动作:

  • 从托盘 A 里拣出一部分;
  • 放进纸箱 B;

这时:

  • package_id = 托盘 A
  • result_package_id = 纸箱 B

如果你只有一个 package 字段,根本没法同时表达“来源容器”和“结果容器”。

所以 Odoo 这两个字段不是啰嗦,而是为了避免把一条 move line 的前后状态糊成一团。

action_put_in_pack() 真正在做什么

很多人把 Put in Pack 按钮理解成“给几行明细贴个箱号”。 源码告诉你,事情没这么浅。

action_put_in_pack() 的核心顺序大致是:

  1. 先决定哪些 move line 这次要参与装箱;
  2. 必要时弹向导,让你先解决目标库位 / 包类型 / 包名等问题;
  3. 创建包裹或复用已有包裹;
  4. 把相关 move line 的 result_package_id 指向这个包;
  5. 如有必要,重新应用 putaway 策略。

注意重点:

  • 它改的是 result_package_id,不是简单写 package_id
  • 它表达的是“本次操作的产出包裹”。

这也是为什么看装箱逻辑时,如果你老从“源包裹”角度读,就会觉得源码很绕。

单行装箱时,目的库位甚至可能跟着包一起变

源码里有个很值得注意的细节:

当只有一条 move line 被装进包裹时,系统会基于这个 package 再跑一次 _get_putaway_strategy()

这背后的业务含义是:

  • 不是只有“产品”决定目的地;
  • 包裹本身也可能影响后续放到哪里。

这对理解以下场景很有帮助:

  • 某些包装类型必须进指定区;
  • 整包上架和散货上架走不同路径;
  • 目的库位不是在行创建时一次性写死的。

也就是说,装箱不是文档美化,而是可能改变真实仓内流向的动作。

package level 解决的不是“又一层包装字段”

不少人第一次看到 package level 会想:

“不是已经有 result_package_id 了吗,怎么又多一层?”

原因在于:

  • result_package_id 解决的是单条 move line 最终落在哪个包
  • package level 解决的是这一组明细是否应被视为一个整体包裹作业单元

也就是说,package level 不是字段堆叠,而是把“多行一起组成整包语义”提到更高层。

这层抽象一旦没有,你就很难优雅表达:

  • 整包移动;
  • 整包校验;
  • 一组行共同代表一个运输 / 拣货包;
  • 包中包和整包追加装箱的连续动作。

为什么 Odoo 要区分“行装箱”和“整包装箱”

因为现实仓库里,这两类动作并不一样。

行装箱

强调的是:

  • 我从若干明细中挑出一些数量;
  • 把它们塞进某个结果包;
  • 结果包是这次动作的输出。

整包装箱 / 包中包

强调的是:

  • 我手里已经有一个包;
  • 我要把整个包再放进更大的包;
  • 关注点不再是明细拆分,而是包树结构。

源码里 stock.package.action_put_in_pack() 正是在处理后者这类语义。它不是再去逐条定义商品,而是在处理 package 到 package 的目标容器关系。

为什么 package 相关 bug 总显得“诡异”

因为很多 bug 不是库存数量错,而是你把包裹字段读反了

例如:

  • 你以为 package_id 应该表示“这次装进去的包”,于是自定义写错字段;
  • 你以为 result_package_id 只是展示用途,结果跳过它,整包逻辑全断;
  • 你没区分来源包和结果包,导致 traceability 看起来像货“瞬移”。

这类问题的共同点是:

数量可能还对, 但容器时序已经错了。

而容器时序一错,后面的:

  • 包裹追踪
  • 整包作业
  • putaway 决策
  • 包中包关系

都会一起变得难解释。

推荐的源码阅读顺序

建议按这个顺序看:

  1. stock.move.linepackage_id / result_package_id 字段定义;
  2. _put_in_pack():看结果包是如何创建与挂接的;
  3. action_put_in_pack():看 UI 入口如何组织待装箱行与已有包;
  4. stock.package.action_put_in_pack():看包中包是怎么建立目标容器树的;
  5. package level / entire pack 相关逻辑:理解“多行 = 一个整包动作单元”的抽象。

这样读,层次会清楚很多。

实战排错:先问的是“从哪来”,还是“要去哪”

只要是 package 问题,我建议先把字段按时间面拆开问:

  1. 这批货现在是从哪个包里来的?对应 package_id
  2. 这次操作结束后,它应该落进哪个包?对应 result_package_id
  3. 这是在处理单行装箱,还是把整个现成包再装进别的包?
  4. 这组行要不要被视为一个 package level / entire pack 操作?

很多看起来复杂的问题,到这里就已经能拆开了。

最后的结论

Odoo 的 package 建模看似字段多,实则是在努力保留库存动作的前后关系:

  • 来源包不是结果包;
  • 行级容器结果不是整包操作语义;
  • 装箱不只是贴标签,而会影响后续 putaway 与包树结构。

所以如果你想真正看懂 Odoo 的包裹链路,第一步不是背 API,而是把这句话刻进脑子里:

package_id 讲的是“货从哪个包出来”,result_package_id 讲的是“货最后进哪个包”。

一旦这条时间线不再混,后面 package level、整包搬运、包中包逻辑都会顺很多。

DISCUSSION

评论区

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