先说结论
如果你只用一句话概括:
- Blanket Order 更像“先谈好一段时间内的采购框架,再按框架分批执行”
- Call for Tenders 更像“围绕同一批需求,同时放出多个候选 RFQ,再做授标决策”
但源码里两者的差异,远不只是“长期合作”和“竞争比价”的业务口号。
真正的分水岭在于:
- Blanket Order 确认时会把协议行落成
supplierinfo - Call for Tenders 更强调 同一采购语义下的多个替代 PO
- 招标场景里,系统会把这些 PO 组织进
purchase.order.group - 你确认其中一张单时,Odoo 还会提醒你:其他替代 RFQ 你打算怎么处理
所以更准确的理解是:
Blanket Order 是“协议条件注入采购引擎”,Call for Tenders 是“候选报价并列存在、等待授标决策”。
这篇为什么不是站里已有“采购协议总览”的重复
站里已有一篇文章,重点在讲:
- Purchase Agreement 不是普通 PO
- Blanket Order 与招标在产品定位上的分工
那篇偏概念框架。
这篇换成实现落点:
- Call for Tenders 在系统里为什么更像“替代 PO 组”
- 为什么它天然适合比较与授标
- Blanket Order 为什么反而不强调并行候选 PO
- 这两条链最终如何影响后续采购行为
也就是说,这篇不再停留在“它们都属于 Agreement”,而是往下讲 系统到底如何让两者走成完全不同的采购动作。
第一关键点:Blanket Order 在确认时就把“协议结果”灌进采购主数据
在 /home/ubuntu/odoo-temp/addons/purchase_requisition/models/purchase_requisition.py 里,action_confirm() 对 blanket_order 有非常硬的要求:
- 每行必须有价格
- 每行必须有数量
- 然后逐行执行
_create_supplier_info()
这一步的含义特别重要。
它说明 Blanket Order 的设计重点不是“先挂起多个候选报价”,而是:
- 先把协议内容定下来
- 再把它转译成采购系统能消费的供应商条件
所以 Blanket Order 的核心动作,是 沉淀协议条件。
这和 Call for Tenders 完全不一样。
第二关键点:Call for Tenders 更强调“同源候选单并存”
在 /home/ubuntu/odoo-temp/addons/purchase_requisition/models/purchase.py 里,官方专门加了一个技术模型:
purchase.order.group
字段帮助文本写得很直白:
- 这是用来分组 call to tenders 的 PO
在 purchase.order 上,相关关键字段是:
purchase_group_idalternative_po_ids
而 alternative_po_ids 的帮助说明也非常明确:
- Other potential purchase orders for purchasing products
这已经把招标的系统语义说透了:
Call for Tenders 的重点不是先生成一个“正式协议”,而是保留多个可竞争、可比较、可淘汰的候选采购单。
也正因为如此,它更像授标机制,而不是价格框架机制。
第三关键点:关联 Agreement 时,PO 行的生成方式也不一样
_onchange_requisition_id() 里有一个很容易被忽略的细节:
- 如果
requisition_type == 'purchase_template',PO 行会带上协议数量 - 否则
product_qty = 0
换句话说,在非模板式协议里,系统并不是强迫你一挂协议就把真实执行数量直接灌进订单。
这特别符合招标思路:
- 先拿到同一需求框架下的多个报价
- 再在后续比较和谈判中决定最终落多少量、给谁、何时下
而 Blanket Order 则更偏:
- 协议本身已经在价格和数量边界上较为明确
- 后面是按框架引用和释放,不是并列授标
所以两者即便都叫 Agreement,PO 生成语义也明显不同。
第四关键点:Call for Tenders 天然带“比较”能力
同一文件里,Odoo 还给出了两组非常招标化的方法:
action_compare_alternative_lines()get_tender_best_lines()
它会在一组候选单中按产品比较:
- 总价更低的行
- 单价更低的行
- 到货日期更早的行
这就说明官方对 Call for Tenders 的预期不是:
- 你自己导 Excel 另算
而是:
- 系统层面就承认“同一需求下存在多个候选采购单”
- 并为你准备了比较入口
这跟 Blanket Order 的设计方向再次分道扬镳。
Blanket Order 的问题是:
- 这份协议如何影响后续采购条件
Call for Tenders 的问题是:
- 这些候选报价里,谁更值得被授标
第五关键点:确认某张候选 PO 时,系统会逼你面对“其他候选怎么处理”
button_confirm() 在招标场景下还有一个非常像授标关口的行为。
如果这张 PO 还有 alternative_po_ids 处于:
draftsentto approve
系统不会直接闷头确认,而是弹出一个警告窗口:
- What about the alternative Requests for Quotations?
这句话非常有业务味。
它不是在做技术校验,而是在提醒你:
你确认了这一张,就意味着你正在做授标动作;那剩下的替代 RFQ,到底是保留、取消,还是继续谈?
这也就是为什么 Call for Tenders 不能被理解成“多发几张 RFQ 而已”。
它在确认环节就体现出:
- 候选单并存
- 授标要做取舍
- 系统知道这些候选单是同一批采购语义下的竞争者
为什么 Blanket Order 反而不强调这套“替代单决策”
因为 Blanket Order 解决的不是“谁赢得这次竞价”,而是:
- 某供应商的一段合作条件如何进入后续采购流程
从源码看,它更像:
- 协议确认
- 生成
supplierinfo - 后续 PO 若带
requisition_id,就在卖方准备与过滤时优先使用该协议上下文 - 协议结束或取消时,再把相关
supplierinfo清掉
所以 Blanket Order 更偏 框架生效 / 生命周期管理。
而 Call for Tenders 更偏 候选存在 / 比较 / 授标 / 淘汰。
实施里最容易搞混的 5 个点
1. 以为两者差别只是“长期采购”和“一次比价”
这只是业务表象。
源码层真正的差别,是一个强调 supplierinfo 注入,一个强调 alternative_po_ids 并行比较。
2. 以为 Call for Tenders 选中一家后,其他 RFQ 只是普通未用单据
不对。系统明确把它们视为替代候选,并在确认时提醒你处理其他候选单。
3. 以为 Blanket Order 也应该天然带多家供应商横向比较
它主要不是为这个设计的。它更像某家供应商的协议条件载体。
4. 以为 Agreement 一挂上,PO 数量就一定来自协议
源码里并不是所有类型都直接灌数量。招标场景更强调候选结构,而不是立即定量执行。
5. 以为两者都只是采购前置页面
实际上,一个影响后续选价引擎,一个影响授标决策流程,它们都在“改变采购系统语义”。
开发和实施时最该注意什么
如果你的目标是“长期协议价 + 分批释放”
优先理解 Blanket Order:
- 它会不会生成
supplierinfo - 后续 PO 是否必须带
requisition_id - 协议结束时相关主数据是否会被清理
如果你的目标是“多供应商竞争 + 明确授标”
优先理解 Call for Tenders:
- 候选 PO 如何成组
- 是否需要使用替代 RFQ 比较入口
- 确认其中一单后,其他候选单由谁来收口
如果客户同时想要“长期协议 + 每次仍多家竞价”
这往往已经超过标准源码直觉。
标准 Odoo 并没有把两套治理模型硬揉成一个对象。实施时最好明确:
- 哪一步是协议管理
- 哪一步是竞价管理
- 哪一步才是真正的执行下单
不然用户会同时期待:
- 既像合同
- 又像招标
- 还像自动授标引擎
最后三边都不满意。
一句话记忆法
Blanket Order 管的是“协议条件如何进入采购引擎”,Call for Tenders 管的是“多个候选报价如何并存、比较并完成授标”。
DISCUSSION
评论区