先说结论
Odoo 里的 Replenishment Information,不是一个“把预测库存换个图画出来”的小工具。
它更像是:
给 orderpoint 提供一个可解释的补货决策面板。
这个面板同时回答四类问题:
- 这条补货规则当前为什么会建议补货;
- 这件货的 lead time 是怎么算出来的;
- min/max 区间在当前需求速度下大概能撑多久;
- 如果本仓缺货,别的供给仓有没有 route 可以补。
所以它的核心价值不是“展示”,而是把 orderpoint 这套补货判断解释给人看。
为什么很多人会误解它只是预测图
因为界面上最显眼的部分通常是:
- 一条补货曲线;
- 一个 lead time 区块;
- 若干可选补货来源。
看起来像一个 BI 小窗。
但源码顺序告诉你,它不是先画图,再补一些说明;而是先把 orderpoint 的关键判断拆出来,再把这些判断序列化成前端可读 JSON。
也就是说,这个向导的思路是:
- 先复用 orderpoint 的业务语义;
- 再把“为什么建议这样补”翻译给用户看。
这和单纯报表完全不是一回事。
入口在哪里:action_stock_replenishment_info()
入口在 addons/stock/models/stock_orderpoint.py 的 action_stock_replenishment_info()。
它不是直接丢一个通用 dashboard,而是:
- 以当前 orderpoint 为上下文;
- 创建一条
stock.replenishment.infotransient 记录; - 把弹窗标题改成“某产品在某仓的补货信息”;
- 打开这个特定 record。
这说明一个非常关键的边界:
补货信息不是独立规划对象,它是 orderpoint 的解释层。
如果没有 orderpoint,这个弹窗也没有稳定的业务锚点。
第一步:它先把 lead days 解释出来
在 stock_replenishment_info.py 里,_get_lead_days_and_description() 会调用:
orderpoint._get_lead_days_values()orderpoint.rule_ids._get_lead_days(...)
然后 _compute_json_lead_days() 再把结果整理成 JSON。
这里最重要的不是“拿到了一个天数”,而是它会尽量保留描述链条:
- 今天是什么时候;
- lead horizon date 是哪天;
- 中间每一段延迟是怎么累计出来的;
- 当前 trigger 是 manual 还是 auto;
- 当前预测量、建议补货量、min/max 分别是多少。
这层设计很有 Odoo 风格:
- 业务引擎内部还是规则计算;
- 但面向用户时,不只给结论,也给解释路径。
所以当用户问“为什么建议今天就下采购”时,这个向导理论上就是给你拆解释链的。
virtual 标记揭示了一个常被忽略的细节
_compute_json_lead_days() 里还有个很容易被忽视的字段:
virtual = orderpoint.trigger == 'manual' and orderpoint.create_uid.id == SUPERUSER_ID
这不是无意义的小标记。
它在暗示:
- 有些 orderpoint 并不是长期维护的正式规则;
- 它可能是系统为了某些手动补货场景临时产生的“虚拟补货视角”。
换句话说,补货信息向导并不只服务于“长期稳定 orderpoint”,它也能解释某些临时补货上下文。
这正是它和纯静态规则页不同的地方。
第二步:它不是看未来需求,而是用历史区间估算日需求
_get_period_of_time() 和 _compute_json_replenishment_graph() 组成了图表核心。
它支持多种基准区间:
- 最近 7 天;
- 最近 30 天;
- 最近 3 个月;
- 最近 12 个月;
- 去年同期 / 下月 / 下下月 / 上年季度。
然后它对 stock.move 做 _read_group() 聚合:
location_dest_id.usage = customer视为发出去的量;location_id.usage = customer视为从客户退回来的量。
最后用:
quantity_out - quantity_returned- 再除以天数
- 再乘上
percent_factor
得到 daily_demand。
这说明它做的不是 MRP 那种精细未来计划,而是一种历史驱动的补货速度估算。
所以如果业务方把这张图当成“未来订单承诺表”,一定会误解。
这张图真正想表达什么
算出 daily_demand 后,源码会继续:
- 计算 min/max 差值;
- 推一个
ordering_period; - 构造 max 线、min 线、以及锯齿形补货曲线。
这里的图形不是库存事实回放,而是一个策略可视化模型:
- min/max 差距多大;
- 按当前日消耗速度,大概多久就会从 max 掉到 min;
- 如果继续按这个节奏补,补货周期会是什么形状。
所以它回答的是:
你的 min/max 设置,在当前需求速度下看起来合不合理。
不是:
未来哪一天一定会剩多少库存。
这个边界非常重要。
为什么它会主动修正 max < min
在 _compute_json_replenishment_graph() 里,源码直接处理了:
- 如果
product_max_qty < product_min_qty,就把product_max_qty提到product_min_qty。
这不是为了“帮你自动改配置”,而是为了让解释层至少保持自洽。
因为如果 max 小于 min:
- 图没法画;
- 补货周期没有业务意义;
- 用户会看到一套内部矛盾的展示。
这也告诉我们:
- 补货信息向导是解释器;
- 解释器会尽量做展示层保护;
- 但它不能替代你修 orderpoint 配置。
第三步:它把“能从哪里补”也放进来了
很多人没注意到,_compute_wh_replenishment_options() 会根据:
orderpoint_id.warehouse_id.resupply_route_ids
为每条 route 创建 stock.replenishment.option 临时记录。
随后每个选项再计算:
- 对应供给仓的
free_qty; - 这条 route 对当前产品的
lead_time; - 如果供给仓可用量不够,给出 warning message。
这一步非常关键。
因为它让补货信息向导不只是“解释为什么缺”,还进一步解释:
- 如果要补,可能该从哪补;
- 这些路线里,哪个仓更快、哪个仓量更够。
这就是它比单纯 forecast 报表更实用的原因。
这里的 route 提示为什么不是实际执行结果
stock.replenishment.option 做的是“提示”,不是“承诺”。
因为它只是:
- 根据 route 找 rule;
- 算出 free_qty;
- 算出 lead time;
- 给出不足提醒。
它没有在这里真正跑 procurement。
真正执行补货动作,还是 orderpoint 或 replenish 逻辑去调用 stock.rule.run()。
所以这里的 route 卡片应该被理解为:
面向人类的补货候选解释。
而不是:
系统已经决定一定这么补。
实施里最常见的三个误区
1. 把它当作“未来库存承诺表”
不对。
它基于历史区间和比例因子估算日需求,更偏策略参考。
2. 看到 lead time 就以为采购一定会按这个日期落地
也不对。
lead time 是规则链的计算结果,真实执行还要看:
- route 命中;
- 供应商 / 规则完整性;
- 调度器是否执行;
- 是否有人手工改过日期。
3. 看到跨仓选项就以为系统会自动改走那条 route
仍然不对。
这个向导只做解释和提示,不在这里直接改补货决策。
调试时应该怎么用这张弹窗
如果你遇到“系统建议补货,但业务觉得不合理”,建议按这个顺序看:
qty_forecast和qty_to_order是否已经偏离认知;lead_horizon_date是否被 lead days 拉得过远;based_on与percent_factor是否让日需求被放大或缩小;- route 选项里的
free_qty与warning_message是否暴露跨仓供给问题; - 再回到 orderpoint 本体,看 min/max、route、trigger 是否配错。
也就是说,先用补货信息定位解释层问题,再回到规则层修配置。
一句话总结
Odoo 的 stock.replenishment.info 不是一个“漂亮图表弹窗”,而是一个把 orderpoint 补货逻辑拆成:
- lead days 解释、
- 历史需求估算、
- min/max 策略可视化、
- 跨仓 route 候选提示
的可解释补货面板。
如果你把它看成简单 forecast 图,就会低估它。
如果你把它看成真实执行引擎,又会高估它。
最准确的理解是:
它站在 orderpoint 与人工判断之间,负责把“系统为什么这样想”翻译出来。
DISCUSSION
评论区