供应商选中

Odoo 供应商为什么总“选错人”:生效日期、最小起订量与 vendor 选中链路讲透

很多团队以为 Odoo 采购时是在“取一个价格”,其实系统先做的是“筛掉不合格供应商,再从候选里挑一个”。本文把生效日期、最小起订量、单位换算、公司与伙伴层级这些隐藏门槛讲清楚。

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

先说结论

很多人把采购取价问题理解成:

  • 系统从若干报价里挑了一个价格

但源码里的真实顺序更接近:

  1. 先把 不合格的 supplierinfo 排除掉
  2. 再按排序规则形成候选集
  3. 最后才从候选集中取一个“当前最合适的供应商记录”

也就是说,先是资格判断,再是供应商选择,最后才是价格落地

这也是为什么很多现场问题表面像“价格不对”,根因却是:

  • 生效日期没命中
  • 最小起订量没达到
  • 采购单位和供应商单位口径不一致
  • 你指定的是子联系人,但系统允许父级伙伴一起匹配
  • 当前公司上下文和供应商记录的公司边界不一致

这篇为什么不是“supplierinfo 价格机制”的重复题

站里已经有一篇讲 supplierinfo、数量阶梯和采购取价。

那篇重点是:

  • 采购价为什么和你想的不一样
  • 多条供应商价格怎么参与取值

这篇换了一个更容易踩坑、但常被忽略的视角:

系统到底先把哪些供应商记录视为“有资格参与这次采购”?

如果资格判断错了,后面的价格讨论根本立不住。


源码主链路:不是直接 _select_seller(),而是“准备 → 过滤 → 排序 → 取一条”

/home/ubuntu/odoo-temp/addons/product/models/product_product.py 里,这条链路很清楚:

  • _prepare_sellers()
  • _get_filtered_sellers()
  • _select_seller()

第一步:_prepare_sellers() 先准备可用卖方记录

它会先拿产品上的 seller_ids,并调用供应商过滤逻辑,再按下面的维度排序:

  • sequence
  • -min_qty
  • price
  • id

这里已经埋了一个很关键的信号:

Odoo 不是简单按最低价排序。

它会先尊重供应商顺序,再考虑数量阶梯和价格。

所以现场里“为什么没有选最便宜那家”并不一定是 bug,而可能是你前面的优先级更高。

第二步:_get_filtered_sellers() 做资格过滤

这一步最值钱,因为它把很多“看似存在的供应商”排除掉了。

源码会依次检查:

  • date_start / date_end
  • force_uom 与供应商 UoM 是否兼容
  • partner_id 是否等于目标供应商,或目标供应商的父级伙伴
  • 数量换算后是否达到 min_qty
  • 供应商记录是否绑定到了别的变体 product_id

这就解释了很多现实困惑。

第三步:_select_seller() 才在候选里选一条

默认它还是会围绕 price_discounted 做排序,但如果调用方指定别的优先字段,也可能改变主排序逻辑。

而且源码里还有一个很容易被忽略的处理:

候选集里如果已经选中了某个 partner,后面只继续收同一个 partner 的记录。

这意味着系统并不是“所有供应商混排后取全局第一”,而更像是:

  • 先找到满足条件的一组供应商记录
  • 再在同一供应商上下文里选最优那条

这对理解多联系人、多报价层级尤其重要。


生效日期为什么经常看起来像没生效

_get_filtered_sellers() 里,Odoo 会直接做:

  • seller.date_start > date → 跳过
  • seller.date_end < date → 跳过

所以日期逻辑不是“谁更近就选谁”,而是很硬的资格门槛:

  • 未到开始日,不能选
  • 超过结束日,不能选

最容易误解的地方在于:

1. 你以为自己在看“今天的采购”

但采购规则可能传进来的是计划日期 date_planned 推出来的采购日期。

也就是说,业务用户看的是“今天在下单”,源码判断的可能是“为了赶上计划收货,这笔采购在业务上应该对应哪一天的供应商条件”。

2. 你以为日期只是价格条件

其实在 Odoo 里,日期首先是“供应商记录有没有资格参加本次选择”。

一旦日期不在范围内,这条记录连价格比较都进不去。


最小起订量为什么不能只按采购数量字面比较

源码不会直接拿采购行数量去和 min_qty 硬比。

它先会做一个动作:

  • 如果采购单位 uom_id 和供应商单位 seller.product_uom_id 不一样
  • 先把数量换算成供应商单位
  • 再比较是否达到 min_qty

这意味着很多“明明买了 10,为什么没命中 5 起订量”的问题,本质上是:

  • 你脑子里按“件”理解
  • 供应商记录按“箱”理解
  • 系统是按供应商单位去判断门槛

这是非常合理的业务设计。

因为 min_qty 表达的不是你的内部计量口径,而是供应商那边的采购条件。


为什么指定了联系人,系统还可能命中父级供应商

源码里有一句很关键:

  • seller.partner_id not in [partner_id, partner_id.parent_id] 才跳过

这说明 Odoo 允许你传入某个联系人或子伙伴时,仍然命中父级供应商记录。

它想解决的是现实世界里很常见的一种情况:

  • 采购沟通人是某个联系人
  • 但真正挂在产品供应商价目上的,是供应商公司主体

如果不允许父级一起匹配,很多正常采购都会“找不到供应商”。

但这也带来一个排错误区:

你以为系统“选错了联系人”,其实它是在用父级伙伴完成合法匹配。


为什么变体级 supplierinfo 会让人觉得系统在“忽略模板级记录”

过滤逻辑里还有一句:

  • 如果 seller.product_id 有值,且不等于当前变体,就跳过

这说明:

  • 变体级供应商记录优先表达“只给某个变体用”
  • 模板级供应商记录表达“整个模板都能用”

因此你在同一个产品模板下看到很多 supplierinfo,不代表每条都对当前变体有效。

如果某条记录绑死在别的变体上,它根本不会进入候选集。


采购自动化里还有一个更隐蔽的回退机制

/home/ubuntu/odoo-temp/addons/purchase_stock/models/stock_rule.py_get_matching_supplier() 里,Odoo 先尝试:

  • 用显式 supplierinfo_id
  • 用补货规则上的 supplier_id
  • 否则走产品 _select_seller()

如果都没拿到,还会有一个 fallback:

  • _prepare_sellers(False) 里拿当前公司可见的第一条供应商记录

源码注释写得很直白:

这不理想,但比直接阻塞用户更好。

这条回退很重要,因为它解释了另一类现场现象:

  • 系统不是完全找不到供应商
  • 但用的并不是你以为那条“有效报价”

根因常常就是前面的日期、起订量或单位条件没过,于是系统退回到了一个“能用但不完美”的供应商。


业务上最容易误解的 5 个点

1. 把“价格没命中”误判成“系统算价有问题”

很多时候还没到算价这一步,供应商资格就已经被筛掉了。

2. 只看产品卡上的供应商列表,不看当前计划日期

供应商存在,不代表在这次采购日期上有效。

3. 忽略供应商单位

min_qty 是按供应商单位比较的,不是按你眼前采购行显示单位比较的。

4. 以为指定联系人就只会看该联系人

父级伙伴也可能合法命中。

5. 以为没命中就一定报错

自动补货链路里,系统有时会走回退供应商,而不是立刻失败。


开发和实施时最该注意什么

1. 不要把 supplierinfo 当“纯价格表”

它同时承载:

  • 时间范围
  • 起订量
  • 单位
  • 供应商层级
  • 公司边界
  • 变体边界

只改价格,不校验这些字段,结果常常更乱。

2. 定制 _select_seller() 前,先确认是不是该改 _get_filtered_sellers()

很多客户真正想改变的是资格门槛,而不是排序规则。

如果资格都没进来,你改排序毫无意义。

3. 排错时一定打印“被过滤掉的原因”

这是最省时间的诊断方式。

否则团队会一直围绕“为什么价格不对”打转,却看不到日期、UoM 或 min_qty 才是根因。

4. 多公司环境别只测单公司

供应商记录对公司是否可见,会影响最终候选集。开发时如果只在单公司数据库验证,很容易低估这个问题。


一句话记忆法

Odoo 采购不是先挑价格,而是先做供应商资格审查;生效日期、起订量、单位和伙伴层级没过,根本轮不到比价。

DISCUSSION

评论区

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