先说结论
很多人把补货规则理解成一个很朴素的东西:
- 库存低了
- 系统补一点
- 然后自动生成采购
但真实的 Odoo 采购侧逻辑更复杂。
对采购来说,stock.warehouse.orderpoint 不只是“触发器”,它还会影响:
- 这次补货默认向谁买
- 这次补货要不要强制用某条供应商价目
- 还有多少采购量已经在路上,不该重复下单
- 这次补货的采购日期和提前期该怎么推
所以很多“补货没生效”或“补货买错供应商”的问题,本质上并不是 scheduler 本身的问题,而是:
orderpoint 已经把采购侧上下文改掉了。
这篇为什么不是“补货规则与 scheduler”旧题重写
站里已有文章讲:
- 为什么 reordering rules 会触发自动补货
- scheduler 如何把库存阈值推进成采购或制造动作
那篇更像在讲“补货系统怎么跑起来”。
这篇专门盯采购侧边界:
- 当补货落到 Buy 路径时,orderpoint 如何参与供应商选择
- Odoo 如何统计“已经在采购路上的量”
- 为什么明明库存低了,却不一定继续下更多 PO
也就是说,这篇是 补货触发采购后的采购语义,不是库存阈值总览。
源码入口:stock.warehouse.orderpoint 不只是库存参数表
在 /home/ubuntu/odoo-temp/addons/purchase_stock/models/stock.py 里,Odoo 给 orderpoint 扩展了几组非常关键的字段:
supplier_id:指定某条product.supplierinfoeffective_vendor_id:当前补货真实会使用的供应商vendor_ids:产品上的可用供应商列表supplier_id_placeholder:如果你没显式指定,系统推断出的默认供应商
这已经说明一个很重要的事实:
在补货语境里,供应商不是产品静态属性,而是 orderpoint 级别的执行上下文。
为什么设置了 orderpoint 的供应商,就会改掉默认采购路线感受
源码里 supplier_id 有一个反向逻辑 _inverse_supplier_id():
- 如果当前 orderpoint 还没 route
- 但你设置了供应商
- 系统会自动给它挂上 buy route
这背后的业务含义非常直接:
- 你一旦在补货规则上明确指定了采购供应商
- Odoo 就会把这条补货理解成“应该走 Buy”
所以很多人以为自己只是“补充了一个建议供应商”,但系统实际接收到的信号更强:
- 这条补货是采购驱动,不只是库存提示
这也是为什么现场里常出现:
- 改了供应商之后,补货表现突然更像采购流程而不是普通库存提醒
默认供应商不是拍脑袋选的,而是 _get_default_supplier() 现算的
在 orderpoint 上,如果你没有显式写 supplier_id,系统会调用 _get_default_supplier()。
而 _get_default_supplier() 又不是随便拿产品第一家供应商,而是交给默认规则 _get_matching_supplier() 去算。
也就是说,它仍然会结合:
- 当前公司
qty_to_order- 补货 UoM
- 供应商生效条件
这就解释了一个常见误区:
同一个产品,在产品页看到的第一供应商,不一定是 orderpoint 补货时真正会用的那家。
补货语境下,系统关心的是“这次补货谁最匹配”,而不是“主数据列表谁排第一眼”。
effective_vendor_id 为什么很关键
effective_vendor_id 的计算很简单,但非常实用:
- 有
supplier_id就用它 - 没有就用
_get_default_supplier()
这说明 Odoo 很清楚地区分了两层语义:
1. 你手工指定的供应商
这是一种强约束。
2. 系统推断的默认供应商
这是一种运行时结果。
很多实施问题就卡在这里。
用户以为:
- “这个产品默认就是 A 供应商”
但补货画面上真正显示的可能是:
effective_vendor_id = B
这时候不是系统自相矛盾,而是:
- orderpoint 上下文、数量、日期或有效规则,已经把默认结果改写了。
为什么有时库存很低,系统却没有继续采购:关键在 _quantity_in_progress()
这条链非常值得讲透。
在 purchase_stock 里,orderpoint 扩展了 _quantity_in_progress(),它会调用产品层的 _get_quantity_in_progress() 去汇总当前已经在采购流程中的数量。
在 /home/ubuntu/odoo-temp/addons/purchase_stock/models/product.py 中,这个“在途数量”不是只看已确认采购单,而是会把满足条件的采购行按 location / warehouse 聚合。
它关注的是:
- RFQ / PO 行状态
- 采购行对应的 orderpoint
- 目标位置
location_final_id - move 链是否已经指向某个目标位置
这说明 Odoo 的问题意识不是:
- 库存低了就继续买
而是:
- 库存低了,但已经有多少量在补、补到哪里、是给哪个位置补,这些量要不要先算进去?
所以现场里经常出现的“库存还没到仓,但系统不再下单”并不奇怪。
对 Odoo 来说,那部分量可能已经被视为:
- 在采购中
- 即将满足该 orderpoint 的补货需求
为什么 orderpoint 会影响采购合并边界
在 /home/ubuntu/odoo-temp/addons/purchase_stock/models/stock_rule.py 和 purchase_order_line.py 里,可以看到两个很关键的设计:
- procurement merge 的 groupby 会把
orderpoint_id考虑进去 _find_candidate()在没有move_dest_ids的情况下,会优先只和同一orderpoint_id或空orderpoint_id的行合并
它想避免的问题是:
- 不同补货规则本来是给不同位置、不同补货逻辑服务
- 结果你把它们随便合并在一行里
- 后面“谁的在途量算给谁”会变得说不清
换句话说:
orderpoint 不只是补货入口,也是在保护采购行和补货来源之间的可追踪性。
为什么“Set Supplier”动作会顺手把数量也改掉
在 product.supplierinfo.action_set_supplier() 里,还有一个非常业务化的动作:
- 把选中的供应商写回 orderpoint
- 如果当前
qty_to_order小于该供应商的min_qty - 就把补货数量直接抬到供应商起订量
这背后体现的原则很成熟:
- 采购规则不只问“库存差多少”
- 还要问“供应商愿不愿按这个量卖给你”
所以补货数量不是纯库存算法,它会被供应商商业条件修正。
这也是为什么很多团队以为系统“多买了”,其实是 Odoo 在帮你满足供应商最小起订量。
业务上最容易误解的 5 个点
1. 以为 orderpoint 只决定补货数量
实际上它也影响供应商、提前期、采购合并和在途量计算。
2. 以为产品默认供应商就等于补货供应商
orderpoint 可以覆盖,系统也可以动态重算。
3. 以为货还没到仓,就等于“不算已补”
在 Odoo 看来,采购中的量很多时候已经算在 in progress 里。
4. 以为供应商只是参考建议
显式 supplier_id 在补货语境里往往是强信号。
5. 以为补货量一定由最小库存差额直接决定
供应商 min_qty 和 UoM 可能会把采购量抬高。
开发和实施时最该注意什么
1. 排查补货异常时,不要只看 scheduler 日志
还要看:
- orderpoint 上有没有
supplier_id effective_vendor_id是谁_quantity_in_progress()是否已把未到货采购量算进去了
2. 做定制补货逻辑时,别轻易破坏 orderpoint_id 追踪
否则你会很难解释:
- 在途量到底算给哪个补货规则
- 为什么某些 orderpoint 一直不再触发采购
3. 如果用户允许临时换供应商,界面上最好同时展示“指定供应商”和“生效供应商”
这能显著减少“系统没按我设定执行”的误会。
4. 多仓多位置场景一定要测 location_final_id 语义
因为在途量汇总是按位置口径来的,不是简单按产品总量来的。
一句话记忆法
Odoo 的 orderpoint 不只是“低库存触发器”,它同时决定向谁买、哪些量已经在补,以及这次采购该不该继续下。
DISCUSSION
评论区