先说结论
很多团队看到 Odoo 的 Blanket Order,会自然把它理解成:
- 已经签好的采购合同
- 后续 PO 只是按合同执行
这个理解并不完全对。
从官方源码看,Odoo 更接近这样做:
- 用
purchase.requisition表达一段采购协议 - 在确认 Blanket Order 时,把行信息写进
product.supplierinfo - 后续 RFQ / PO 再通过这些供应商条件参与取价和关联
所以 Blanket Order 在 Odoo 里不是“合同系统的硬约束投影”,而更像:
采购协议 + 供应商条件注入 + 后续 PO 引用框架
这也是为什么很多实施团队会产生错觉:
- 明明有协议,为什么 PO 还能改?
- 明明是长期协议,为什么表现得像供应商价目增强版?
因为源码设计本来就不是把它做成不可偏离的法务合同引擎。
这篇为什么不是已有采购协议文章的重复
站里已有文章讲 Blanket Order 和招标在采购治理上的分工。
那篇重点是:
- Agreement 是什么
- Blanket Order 和比价招标有什么不同
这篇更落到实现边界:
- Blanket Order 如何落地成
supplierinfo - 后续 PO 到底是“继承协议”还是“继续正常采购”
- 为什么它更像系统内采购框架,而不是严格合同执行器
也就是说,这篇讲的是 实现语义边界,不是产品概念介绍。
源码第一关键点:确认 Blanket Order 时会创建 supplierinfo
在 /home/ubuntu/odoo-temp/addons/purchase_requisition/models/purchase_requisition.py 里,action_confirm() 对 blanket_order 做了几步很关键的校验:
- 行上必须有价格
- 行上必须有数量
- 然后逐行执行
_create_supplier_info()
这一步意义巨大。
它说明 Odoo 并没有把 Blanket Order 只当作一份静态协议文档,而是主动把协议行转成供应商条件记录。
在 _create_supplier_info() 中,系统会创建:
partner_idproduct_idproduct_uom_idproduct_tmpl_idpricecurrency_idpurchase_requisition_line_id
也就是说,协议一确认,它就被灌进了供应商选价体系。
为什么这很像“把协议写进采购主数据”
supplierinfo 本来就是 Odoo 采购选供应商、取采购价、比起订量和交期的重要主数据结构。
Blanket Order 确认后把协议行写进去,意味着系统的设计不是:
- 每次下 PO 都单独回头查协议文本
而是:
- 把协议结果转译成日常采购引擎也能直接使用的供应商条件。
这非常实用,但也埋下了边界问题:
- 如果你把 Blanket Order 当“合同本身”
- 就会低估
supplierinfo在后续实际采购中的影响力
第二关键点:后续卖方准备时,会按 requisition 过滤 supplierinfo
在 /home/ubuntu/odoo-temp/addons/purchase_requisition/models/product.py 中,product.product._prepare_sellers() 被扩展了。
逻辑是:
- 如果当前上下文里带
order_id - 而这个订单有
requisition_id - 那就只保留:
- 没有
purchase_requisition_id的 seller - 或者属于当前这张 requisition 的 seller
这句非常关键。
它说明 Odoo 在协议场景里,并不是把所有供应商记录平铺混用,而是:
当 PO 明确关联某个协议时,允许该协议注入的 supplierinfo 进入候选集,并把别的协议信息隔开。
这就是“协议影响后续采购”的真正技术落点。
为什么这仍然不等于“PO 完全被合同锁死”
这是现场最常见的误会。
虽然协议行会生成 supplierinfo,也会影响后续 PO,但从源码看,PO 依然是独立业务对象。
在 /home/ubuntu/odoo-temp/addons/purchase_requisition/models/purchase.py 里,purchase.order 仍然有自己的一套逻辑:
requisition_iddate_ordercurrency_idnote- 采购行生成
- 后续修改
而且协议行 _prepare_purchase_order_line() 也只是准备初始采购行数据:
nameproduct_idproduct_qtyprice_unitdate_planned
这更像“按协议起草采购”,而不是“合同系统直接锁单”。
所以 Blanket Order 与 PO 的关系更贴近:
- 协议提供约束框架和默认条件
- PO 依然是实际执行与变更载体
第三个关键点:协议关闭时,还会把相关 supplierinfo 清掉
action_done() 和取消逻辑里,Odoo 会把关联的 supplier_info_ids 删除。
这说明协议生成的供应商条件不是永久主数据,而是:
- 带生命周期的、与协议关联的供应商条件注入
这个设计非常值得注意。
因为它再次说明 Blanket Order 更像一套“有效期内的采购框架机制”,而不是永远写死在产品主数据里的合同价格。
为什么说它不像传统采购合同
传统采购合同在企业语义里,往往强调的是:
- 法律约束
- 偏离审批
- 合同版本
- 违约边界
- 强制执行口径
而 Odoo 这里的 Blanket Order,更偏系统执行层:
- 确认前校验价格和数量
- 确认后生成
supplierinfo - 通过
requisition_id影响后续 RFQ/PO - 协议结束时清理注入数据
这当然非常有用,但它解决的是“系统内采购框架与取价协作”,而不是完整法务合同管理。
如果实施时把它硬当合同系统,后面一定会在审批、锁价、偏离控制上提出超过源码边界的期待。
业务上最容易误解的 5 个点
1. 以为确认 Blanket Order 后,后续 PO 就绝对不能改
源码里更像是带默认条件与约束框架,而不是绝对锁死。
2. 以为协议只是文档,不会影响选价
实际上它会直接生成 supplierinfo 并进入采购主链路。
3. 以为协议结束后,价格条件会永久留在产品上
相关 supplierinfo 是可以在协议结束时被清理掉的。
4. 以为所有协议供应商条件会和普通供应商条件混在一起
当 PO 带 requisition_id 时,源码会做定向过滤。
5. 以为 Blanket Order 等同法务合同模块
它更像采购执行框架,不是完整合同治理引擎。
开发和实施时最该注意什么
1. 如果客户要求“合同不可偏离”,不要只靠 Blanket Order 原生逻辑
你大概率还需要补:
- 价格偏离审批
- 数量偏离阈值
- 合同版本留痕
- 锁字段或锁状态机制
2. 定制取价逻辑时,要意识到协议生成的 supplierinfo 会参与日常采购选价
如果你忘了这层,后面很容易出现:
- 平时采购价怎么突然变了
- 为什么某个协议一确认,PO 选价表现就不同了
3. 协议关闭/取消的生命周期要测试
因为 supplierinfo 的创建与清理,直接影响后续采购候选集。
4. 报表设计时,最好同时展示 requisition_id 与 purchase_requisition_line_id
这样才能把“协议框架”与“实际采购执行”连起来看清楚。
一句话记忆法
Odoo 的 Blanket Order 更像把协议条件注入 supplierinfo 并引导后续 PO,而不是把 PO 完全锁死成一份不可偏离的采购合同。
DISCUSSION
评论区