手动补货

Odoo 手动补货为什么不是“临时建一条补货规则”:product.replenish 向导和即时 procurement 讲透

很多人把 Odoo 的 Replenish 按钮理解成“帮我临时建一条 orderpoint”。但官方源码里它更像一次即时 procurement 触发器:直接跑 stock.rule、按路线倒推计划日期、根据预测库存给默认补货量。本文把这层语义讲清楚。

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

先说结论

Odoo 里的 Replenish 按钮,不是“临时建一条补货规则”

它真正更像是:

面向某个产品、某个仓库、某个时间点,手动发起一次即时 procurement 请求。

也就是说,它不是在长期配置层定义“以后低于多少就补”;而是在当前上下文里说:

  • 我现在就要补这件货
  • 用这个仓库视角
  • 走这条 route
  • 计划日期按这条路线的延迟来推

这就是为什么手动补货和 orderpoint 虽然都能触发补货,但源码层语义完全不同。


为什么很多人会把它误解成 orderpoint

因为业务结果看起来有点像:

  • 都可能生成采购 / 调拨 / 制造动作;
  • 都和 route、warehouse、预测库存有关;
  • 都在解决“库存不够怎么办”。

但 orderpoint 在回答的是:

  • 长期补货策略是什么
  • 何时自动触发
  • 低于阈值时要补到多少

而手动补货在回答的是:

  • 我现在决定补这件货
  • 请系统立刻把这次需求跑进补货引擎

一句话区分:

orderpoint 是规则,replenish wizard 是请求。


源码抓手:addons/stock/wizard/product_replenish.py

这篇文章的关键入口主要在:

  • default_get
  • _compute_forecasted_quantity
  • _get_date_planned
  • launch_replenishment
  • _prepare_run_values
  • _get_route_domain

只要把这几个方法串起来,手动补货的真实定位就很清楚了。


第一步:向导默认值就已经在表达“即时请求”,不是“长期规则”

default_get 里会根据上下文补齐很多东西:

  • product_id / product_tmpl_id
  • company_id
  • warehouse_id
  • product_uom_id
  • route_id

这里最值得注意的是两点。

1)仓库默认取当前公司下的仓库

这说明手动补货不是抽象全局动作,而是明确绑定仓库视角。

2)route 会按允许域优先选一个可用路线

这说明 wizard 打开的那一刻,系统就在尝试回答:

  • 这次补货准备走哪条供给路径?

这很像人工下达一条供应请求,而不是建制度。


第二步:默认数量来自预测库存,不来自 min/max 阈值

_compute_forecasted_quantity 里,向导会用:

  • product_id.with_context(warehouse_id=rec.warehouse_id.id).virtual_available

也就是站在该仓库视角看 virtual_available

随后 _onchange_product_id 会做一个很实用的默认逻辑:

  • 如果预测库存小于 0,默认补货量取其绝对值;
  • 否则默认给 1。

这段逻辑很有代表性。

因为它表达的不是:

  • 安全库存低于多少
  • 最小补货量是多少
  • 最大补货量是多少

而是:

先看当前预测缺口,如果已经欠货,就先把坑补平;如果没欠货,那就至少给一个最小人工请求量。

这明显是“即时操作”思维,不是“长期补货参数”思维。


第三步:计划日期不是随便填的,而是按 route 的 rule delay 倒推出来

_get_date_planned 的逻辑非常直接:

  • 取当前时间 now
  • 如果有 route,就把 route 下所有 rule.delay 累加
  • 最后得到 date_planned

这说明手动补货虽然是人工触发,但并不是“拍脑袋立刻到货”。

Odoo 仍然在问:

  • 这条 supply 路要经过哪些规则层?
  • 每层规则会吃掉多少天?
  • 最终这笔补货应该以什么时间进入执行?

所以更准确的说法不是“手动补货跳过规则”,而是:

手动补货跳过的是自动触发机制,不是补货规则本身。

这一点特别容易被误解。


第四步:真正的核心在 launch_replenishment ——它直接跑 stock.rule.run

这就是全文最关键的地方。

launch_replenishment 里没有去创建 orderpoint,也没有把这次请求先存成一条长期规则。

它做的是:

  • self.env['stock.rule'].with_context(clean_context(...)).run(...)
  • 传进去一个 Procurement(...)

这个 Procurement 里包含:

  • 产品
  • 数量
  • 单位
  • 仓库主库存位 warehouse_id.lot_stock_id
  • 名称 / 来源:Manual Replenishment
  • 公司
  • 以及 _prepare_run_values() 里的运行参数

这已经很说明问题了:

Replenish 按钮本质上是手动把一条 procurement 请求直接塞进规则引擎。

不是先定义规则再等 scheduler 发现它,而是此刻就执行


_prepare_run_values() 说明:它不是低配版补货,而是完整走 route / warehouse / date 语义

这个方法会带上:

  • warehouse_id
  • route_ids
  • date_planned
  • force_uom = True

也就是说,手动补货并不是“临时 shortcut”。

它并没有绕开:

  • 仓库语义
  • 路线语义
  • 时间语义
  • 单位语义

它跳过的只是“由系统自己监控并自动触发”的那一层。

这也是为什么手动补货和自动补货能复用同一套 supply engine。


为什么补货后还能弹出新单据提示

launch_replenishment 里会记录一个时间点 now,然后用 _get_record_to_notify(now) 去找刚刚生成的 stock.move,最后拼一个通知。

如果 move 有对应 picking,就给你一个跳转链接。

这说明 Odoo 对手动补货的交互预期是:

  • 你刚刚手动下达了一次 supply 请求
  • 系统已经把它转成了真实执行对象
  • 你应该能立刻跳过去看结果

这和 orderpoint 的体验也很不一样。

orderpoint 更像后台机制; manual replenish 更像前台操作入口。


为什么它不是“创建 orderpoint 后再跑一次”

源码里虽然还留了 _prepare_orderpoint_values(),但注释已经写明:

  • TODO: to remove in master

而且真正执行补货的路径并不依赖它。

这其实已经给了我们一个很明确的信号:

官方设计正在更明确地区分“手动即时补货”与“长期 orderpoint 配置”。

所以项目里如果有人打算把 Replenish 按钮魔改成“自动落一条 orderpoint”,要非常小心。

因为这会把一次性操作,硬生生改成长期策略,常常会留下后患:

  • 下次 scheduler 又自动补一遍;
  • 用户只是临时补货,结果系统以后一直按这条规则跑;
  • 补货语义从 request 变成 policy,责任边界变得很混乱。

实战里最容易误解的 5 个点

1)手动补货不是绕开 route

它不是不走 route,而是人工指定 / 触发一次 route 驱动的 procurement

2)手动补货不是自动补货的弱化版

两者共用很多底层规则,但一个是人工请求,一个是系统监测触发

3)默认数量不是“建议采购量”

默认数量只是基于当前负预测库存给你的一个操作起点,不等于完整补货策略。

4)计划日期不是 UI 装饰

它反映 route 规则延迟,影响后续链路节奏。

5)不要把一次性操作偷偷变成永久规则

这是最常见、也最伤系统可解释性的定制错误。


什么时候该用手动补货,什么时候该用 orderpoint

更适合手动补货的场景

  • 临时缺货,需要立刻发起一次补货
  • 运营人员明确知道这次要走哪条 route
  • 你想先人工确认,而不是让系统后台自动跑
  • 某产品不是稳定消耗品,不适合长期阈值配置

更适合 orderpoint 的场景

  • 周期性消耗稳定
  • 希望系统自动监控库存并补货
  • 需要 min/max 或规则化补货机制
  • 不想每次人工点一次 Replenish

小结

看完源码后,最实用的记法其实很简单:

  • orderpoint = 持久化补货政策
  • product.replenish = 一次性补货请求入口

所以,Odoo 的 Replenish 按钮之所以不能简单理解成“临时建一条补货规则”,是因为它真正做的是:

直接把当前产品、仓库、路线和计划日期打包成一条 procurement,请规则引擎立刻执行。

这也正是它在实战里非常好用的原因:

  • 有规则的严谨
  • 但保留人工操作的灵活

它不是 orderpoint 的替身,而是另一种完全不同的补货入口。

DISCUSSION

评论区

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