补货异常

Odoo 补货异常为什么不是“报一条错就结束”:stock.rule.run 的异常聚合与排查顺序讲透

很多人排库存补货问题时,只看到界面上一句“找不到规则”或“无法补货”,于是沿着单条错误去追。可在 stock_rule.py 里,run() 的职责其实是把一批 procurement 统一分流、统一执行、统一收集失败项,最后再决定抛给用户什么信息。本文结合 stock_rule.py 与 stock_orderpoint.py 把这条链路讲透。

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

先说结论

Odoo 的补货异常处理,不是“碰到一条错就立刻停下并吐一句日志”。

stock.rule.run() 更像一个调度入口,它会先:

  • 接收一批 procurement 需求
  • 为每个需求找规则
  • 按动作分组
  • 执行 _run_pull() / _run_push() 等具体实现
  • 把过程中出现的失败统一收集
  • 最后再决定是抛 UserError,还是抛 ProcurementException

所以你在界面上看到的一句补货报错,背后经常不是“一条记录出问题”,而是:

一整批 procurement 在同一轮 run 里被聚合后的结果。


run() 的第一层职责:先把需求标准化

addons/stock/models/stock_rule.py 里,run() 开头会先给每条 procurement 补一些默认值,例如:

  • company_id
  • priority
  • date_planned

然后还会通过 _skip_procurement() 跳过本来就不该继续处理的情况,比如:

  • 不是 consumable / storable 范畴
  • 数量按 UoM 精度看等于 0

这说明:

进入规则匹配之前,Odoo 先确保“这是一条值得处理的补货需求”。

如果你排错时连这一层都没看,就很容易把“需求压根被跳过”误判成“规则没触发”。


第二层:找不到规则时,错误会先累积,不一定立刻抛

run() 会对每条 procurement 调 _get_rule()

如果某条需求根本匹配不到规则,系统不会马上炸掉整个 Python 栈,而是先把这条错误追加进:

  • procurement_errors

典型报错文本就是:

  • 在某个 location 找不到 replenishment rule
  • 请检查产品路线配置

这一步很关键,因为它说明 Odoo 在这里的设计目标是:

尽量把一批问题一次性收集齐,再统一告诉你。

对于实施和运维来说,这比“一次只报一条”更适合补货场景。


第三层:按 action 分组,再调用 _run_*

当规则找到了以后,run() 不会直接对每条 procurement 各自处理到底,而是先按照 rule action 分组:

  • pull
  • push
  • pull_push(会转成 pull 主链处理)

然后再统一调用对应的:

  • _run_pull()
  • _run_push()
  • 或其他扩展模块注入的 _run_xxx()

这意味着一个很重要的排错原则:

不要只盯报错那条 procurement,本轮还有可能有同 action 的其他需求一起被处理。

也正因为如此,某一轮 scheduler 或 orderpoint 补货失败,往往会伴随多条关联异常一起出现。


ProcurementException 的意义:不是普通报错,而是“批量失败容器”

源码里 ProcurementException 不是简单字符串,而是带着:

  • procurement_exceptions

也就是一组 (procurement, error_message) 元组。

这说明它被设计出来,就不是为了表达“单次失败”,而是为了表达:

这轮补货里,有若干具体需求失败了,各自对应什么错误。

于是 run() 在调用 _run_pull() 等方法时,会把子过程抛出的 ProcurementException 再汇总回主错误列表。

这一步特别重要,因为它解释了为什么有些补货失败信息看起来像“好几类错误混在一起”:

  • 有的缺 route
  • 有的缺 vendor
  • 有的缺 BOM
  • 有的只是 location 配置不通

它们可能真的是同一轮里一起失败的。


orderpoint 调度为什么更像“边跑边剔失败项”

stock_orderpoint.py 里,scheduler 对 orderpoint 的处理又往前走了一步。

它会:

  • 构造 procurements 批次
  • 在 savepoint 里调用 stock.rule.run()
  • 如果捕获到 ProcurementException
  • 就把失败的 orderpoint 挑出来,记录异常,再把失败项从当前批次剔除

然后剩下能跑的继续跑。

这套设计的价值非常现实:

一个补货点坏了,不应该把整批其他正常补货全部拖死。

所以你看到 scheduler 最终留下了一堆 warning activity,并不代表整个调度完全没做事;更可能是“成功的已经成功,失败的被单独标出来了”。


实战里最稳的排查顺序

当你遇到 replenishment / scheduler / orderpoint 异常时,建议按下面顺序排:

1. 先确认 procurement 本身有没有被跳过

数量是不是 0,产品类型是不是不进入当前补货逻辑。

2. 再看 _get_rule() 能不能找到规则

重点检查:

  • 产品 route
  • 类别 route
  • 仓库 route
  • location
  • 公司上下文

3. 再看规则对应的 action 会走哪条 _run_*

不同 action 下,失败根因完全可能不同。

4. 最后才去看界面上的最终报错文案

因为那句文案常常已经是聚合结果,而不是第一现场。


最常见的误区

误区 1:看见一条错误,就以为只有一条 procurement 出问题

实际上很可能是一批。

误区 2:把 scheduler failure 理解成“整批都没跑”

很多时候只是部分失败,其他批次已经成功落地。

误区 3:只查前端报错,不看 rule action 分流

这样会把 route、vendor、BOM、location 这几类根因混在一起。


最后的判断句

在 Odoo 里,stock.rule.run() 的本质不是“立刻执行一条补货规则”,而是:

把一批 procurement 需求做统一分流、统一执行、统一收集失败,并把失败结果组织成可回溯的异常集合。

理解了这一点,你排库存补货问题时就不会再只盯着最后那一句报错,而会回到更可靠的源头:

  • 需求有没有成立
  • 规则有没有命中
  • 命中的 action 是什么
  • 哪些失败是同一轮被一起聚合出来的

这才是补货问题真正稳定的排法。

DISCUSSION

评论区

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