先说结论
Odoo 自动采购“找供应商”的逻辑,不是单一步骤,也不是永远找不到就报错。
在 /home/ubuntu/odoo-temp/addons/purchase_stock/models/stock_rule.py 的 _get_matching_supplier() 与 _run_buy() 里,顺序大致是:
- 如果上下文明确给了
supplierinfo_id,优先用它 - 否则,如果来自补货规则且 orderpoint 上指定了供应商,优先用 orderpoint 供应商
- 再不行,走产品的
_select_seller()正常筛选 - 如果还没有,尝试从当前公司可见 supplier 里拿第一条做 fallback
- 但在某些 from_orderpoint 场景下,又会直接抛异常阻止生成采购单
所以看起来“有时失败、有时又继续”的根因,不是系统随机,而是场景分叉不同。
第一优先级:显式指定永远比自动猜测更强
源码把 supplierinfo_id 放在最前面,这其实很合理。
因为一旦上游已经明确告诉系统:
- 就用这条 supplierinfo
那后面的日期、优先级、默认选择都不该再争权。
这代表 Odoo 在自动采购上采用了一个很稳的原则:
显式业务决定 > 自动选择算法。
第二优先级:orderpoint 指定供应商,不只是“建议”
如果值来自 orderpoint_id,而且补货规则上挂了 supplier_id,源码会直接选它。
这说明在自动补货语境下,orderpoint 的供应商不是轻飘飘的推荐项,而是会真正改变采购去向。
这也解释了很多现场现象:
- 产品本身有多个供应商
- 但补货时总走固定那一家
不是 _select_seller() 失灵,而是 orderpoint 在更上层先把结果锁定了。
第三层:正常自动选择,才轮到 _select_seller()
只有前两层都没有明确答案时,系统才会走标准供应商选择链。
这时才会综合考虑:
- 供应商有效期
- 最小起订量
- 单位
- 数量
- 公司边界
所以很多人把自动采购问题直接归咎于 _select_seller(),其实常常是错位排查。
你得先确定是不是早在 supplierinfo_id 或 orderpoint 阶段就已经决定好了。
最容易误解的一层:fallback 供应商并不等于“合理报价供应商”
源码有一句注释写得很坦白:
- 如果正常没找到 supplier
- 就退回到当前公司可见的第一条 seller
- 这不理想,但总比完全阻塞用户更好
这意味着 fallback 的语义不是:
- 找到一个正确供应商
而是:
- 先找一个还能继续走下去的供应商
因此你看到系统“明明没有有效供应商条件,却还是建出了采购单”,不要急着夸它聪明。
它可能只是选择了一个可用但并不精确的兜底答案。
为什么 from_orderpoint 场景反而会报错更硬
在 _run_buy() 里,如果:
- 没找到 supplier
- 并且上下文里是
from_orderpoint
系统会抛出一个明确异常,提示你去产品上补全 vendor 列表。
这看似和前面的 fallback 有矛盾,其实不矛盾。
因为补货规则场景下,系统更强调:
- 自动化补货需要可预期、可维护的供应商条件
如果在 orderpoint 自动补货里也大量使用模糊 fallback,长期会把计划体系带偏。
所以这里宁可失败得更明确。
最容易误解的 5 个点
1. 以为自动采购总是直接 _select_seller()
其实前面还有显式 supplierinfo 与 orderpoint 优先级。
2. 以为 orderpoint 供应商只是建议
源码里它是真优先项。
3. 以为 fallback 说明系统找到了合理供应商
很多时候只是找到了一个能继续跑的兜底候选。
4. 以为找不到供应商时永远同样报错
from_orderpoint 和普通场景处理并不一样。
5. 以为自动采购错供应商一定是价格问题
根因可能在更前面的优先级覆盖。
排错顺序
如果你想查“自动采购为什么选了这个供应商 / 为什么没生成采购单”,建议按这个顺序:
- 是否传入了
supplierinfo_id - 是否来自 orderpoint,且 orderpoint 上有
supplier_id - 正常
_select_seller()会选谁 - 有没有触发 fallback supplier
- 当前上下文是否是
from_orderpoint,从而改成硬报错 - 如果没供应商,相关 move 是被取消了,还是被回退成 MTS
一句话记忆法
Odoo 自动采购选供应商,顺序不是“直接比价”,而是“显式指定 → 补货规则指定 → 正常选择 → 模糊兜底 → 某些场景硬失败”。
DISCUSSION
评论区