先说结论
Odoo 里“确认采购单后,产品自动记住这个供应商”并不是隐藏魔法,而是 purchase.order.button_confirm() 明确调用了 _add_supplier_to_product()。
也就是说:
采购确认不仅会推进订单状态,还可能顺手把这次采购经验沉淀成未来取价可复用的 supplierinfo。
这也是为什么有时你第二次下单,价格、供应商产品名、供应商编码 suddenly 就开始“像认识这个供应商了”。
核心入口:button_confirm() 里先补供应商,再谈审批
在 /home/ubuntu/odoo-temp/addons/purchase/models/purchase_order.py 里,button_confirm() 的顺序里有一行很关键:
order._add_supplier_to_product()
而且它发生在审批分流前。
这表示只要订单准备进入正式确认流程,Odoo 就会先看:
- 当前采购行产品
- 当前订单供应商
- 这个产品上有没有相应
seller_ids
如果没有,就尝试补一条。
_add_supplier_to_product() 到底在补什么
源码逻辑很值得细看。
1)不是所有联系人都会直接写成供应商
如果订单上的 partner_id 是联系人地址,源码会优先回到它的父公司:
partner = self.partner_id.parent_id or self.partner_id
这说明 Odoo 试图把供应商知识沉淀在商业主体上,而不是某个偶然被选中的联系人上。
2)如果产品已经认识这个供应商,就不会重复加
源码会用:
- 当前 partner
- 订单 partner
- 产品已有
seller_ids.mapped('partner_id')
做一次交集判断。
如果系统认为“这个供应商已经在列表里”,就不会再创建一条重复 supplierinfo。
3)默认最多只自动补到 10 个供应商
源码里还专门限制:
len(line.product_id.seller_ids) <= 10
原因写得很直白:避免一些通用产品被自动堆出太乱的供应商列表。
这不是技术限制,而是产品设计上的“防脏数据”边界。
价格和单位不是原样照抄
很多人以为自动补录就是把采购行上的单价直接塞进 supplierinfo。其实源码更谨慎。
如果采购行的 product_uom_id 不等于产品模板默认 UoM,Odoo 会先把价格换算回模板 UoM,再写 supplierinfo。
这一步非常关键,因为 supplierinfo 的单位语义是围绕产品模板默认采购参考来建的。
否则你今天按箱买、明天按个买,供应商价就会越来越乱。
已选中的供应商资料还会被继承
如果采购行本身已有 selected_seller_id,源码还会把下面这些信息带过去:
product_nameproduct_codeproduct_uom_id
这意味着自动补录并不只是“记住这个供应商卖过这个货”,而是尽量保留供应商侧命名和单位语境。
这对多供应商、多别名采购尤其有价值。
为什么这个机制容易让人误会
因为表面上看,你只是确认了一张 PO;但系统实际上顺手做了主数据更新。
所以很多团队会出现两种相反误判:
误判 1:系统取价突然变了,是不是有 bug
不一定。很可能是之前那张确认过的 PO,已经悄悄把新 supplierinfo 补进去了。
误判 2:供应商主数据必须完全手工维护
也不完全对。Odoo 的默认设计是:
- 允许人工维护主数据
- 也允许业务执行过程反哺主数据
采购确认就是其中一个典型入口。
和现有“采购取价”文章的边界
这一机制和“采购价为什么不按预期来”不是同一件事。
- 取价文章更关心下单时系统如何选 seller、算价格
- 本文更关心确认后系统如何反过来写 supplierinfo,影响未来的取价基础
一个是“读主数据”,一个是“回写主数据”。
实战上该怎么用
如果你想保留 Odoo 的学习能力,这个机制很有用;但如果你们公司要求供应商资料只能由主数据岗维护,就要特别注意:
- 哪些采购员有确认权限
- 产品是否会被频繁用临时供应商下单
- 是否需要定期审查
seller_ids - 是否要在定制里收紧自动补录条件
否则采购执行层会慢慢改变你的产品供应商主数据。
一句话记忆
Odoo 确认采购单时,不只是“确认这次交易”,还可能把这次交易沉淀成未来可复用的 supplierinfo。
DISCUSSION
评论区