先说结论
Odoo 的 Call for Tenders 确实支持:
- 把多张候选 RFQ 组织在一起
- 按产品比较不同供应商的总价、单价、到货日期
- 在你确认其中一张时,提醒你处理其他替代 RFQ
但它不会直接帮你完成“自动授标”。
更准确地说,源码的立场是:
系统负责把候选方案排好、比好、摆到你面前;真正的授标决定,仍然是采购职责,不是算法职责。
这也是为什么很多实施项目会误会:
- 明明已经能比较 best lines,为什么不自动选最便宜那家?
- 明明已经有 alternative POs,为什么确认一张时还要我手工处理其他几张?
答案就在 purchase_requisition 的实现边界里。
这篇和站里已有“招标 vs Blanket Order”不是一回事
站里已有文章已经讲过:
- Call for Tenders 与 Blanket Order 的产品定位不同
- alternative PO、purchase group、授标提醒这些机制如何把两者分开
那篇重点是协议类型差异。
这篇改讲一个更细的落点:
- 比较功能到底比较了什么
- 为什么 best lines 只是决策辅助,不是自动中标逻辑
- 替代供应商在系统里为什么是“并列候选”,不是“系统自动淘汰”
也就是说,这篇不再回答“招标和框架协议有什么不同”,而是回答:
既然已经有候选、比较和提醒,Odoo 为什么还是故意把授标最后一步留给人?
第一层:Odoo 先把候选 RFQ 组织成一个技术组
在 addons/purchase_requisition/models/purchase.py 里,官方定义了:
purchase.order.grouppurchase_group_idalternative_po_ids
其中 purchase.order.group 的描述就很直白:
- Technical model to group PO for call to tenders
而 alternative_po_ids 的帮助文本也很直接:
- Other potential purchase orders for purchasing products
这两句话其实已经把设计意图写出来了。
Call for Tenders 不是“系统帮你选唯一答案”,而是:
- 允许多个潜在采购单并列存在
- 让这些候选单在同一业务语义下被统一查看
- 再由采购员做授标动作
所以第一步不是自动选中标,而是维护候选集合。
第二层:关联协议时,系统先生成“可比”的采购行,但不替你决定真实数量
_onchange_requisition_id() 里有一个很关键的细节:
- 如果
requisition_type == 'purchase_template',PO 行会带协议数量 - 否则
product_qty = 0
这说明在招标语境下,系统并不想过早把执行数量锁死。
它更像是在做这件事:
- 把候选商品、说明、税、价格基础先铺出来
- 保留后续谈判、议价、授标时再落实数量的空间
这就解释了为什么 Odoo 没有把“最佳行”直接等同于“最终中标行”。
因为在真实采购里,授标不只看一个静态数字,还会看:
- 最终落单数量
- 供应风险
- 交付承诺可信度
- 是否允许拆分中标
- 是否要保留备选供应商
源码显然不想在这个层面替企业拍板。
第三层:系统能比较,但比较结果只是辅助,不是裁决
在同一文件里,Odoo 提供了两个很有代表性的方法:
action_compare_alternative_lines()get_tender_best_lines()
action_compare_alternative_lines() 会把当前单据和 alternative_po_ids 的所有明细行放到比较视图里。
而 get_tender_best_lines() 真正做的,是按产品维度统计三类“最佳”:
- 总价最低 的行
- 单价最低 的行
- 日期最早 的行
并且它用的是 price_total_cc,也就是转成公司币后的金额比较。
这很重要,因为它说明:
- Odoo 承认多币种候选单需要先落到可比较口径
- Odoo 也承认“最优”不是只有一种定义
- Odoo 允许同一产品在不同维度上出现不同赢家
比如:
- A 供应商总价最低
- B 供应商单价最低
- C 供应商交期最早
那系统该自动选谁?
源码没有替你回答。
因为这本来就不是一个纯技术题,而是组织采购策略题。
第四层:确认某张 PO 时,Odoo 故意弹窗,而不是偷偷取消其他候选
button_confirm() 在招标场景里还有一个特别关键的动作。
如果当前订单存在仍处于:
draftsentto approve
的 alternative_po_ids,系统不会直接确认,而是先打开一个警告窗口:
- What about the alternative Requests for Quotations?
这个弹窗的意义非常强。
它说明 Odoo 认为:
- 你现在做的是授标动作
- 授标不是“确认赢家”这么简单
- 还必须明确处理剩余候选
也就是说,系统不帮你偷偷把其他候选一刀切掉。
原因很现实:
- 有些团队要立即取消落标 RFQ
- 有些团队要暂时保留第二供应商作为备选
- 有些团队允许按品类拆分授标
- 有些团队还要走内部审批后才敢关掉其他 RFQ
因此 Odoo 只负责提醒,不负责替你承担采购责任。
第五层:替代单的“组关系”会维护,但不会变成自动中标引擎
源码里,create()、write() 和 _merge_alternative_po() 都在维护 purchase_group_id 与 alternative_po_ids 的关系:
- 新建替代 RFQ 时,可加入原来的组
- 手工改 alternative 关系时,会自动建组、删组或合组
- 合并 RFQ 时,也会把替代关系继续带过去
这说明官方非常重视候选集合的一致性。
但你会发现,整条链路里没有“自动关单最佳供应商并完成授标”的实现。
这不是遗漏,而是边界选择。
Odoo 想做的是:
- 候选组织层
- 比较视图层
- 授标提醒层
而不是:
- 企业评审规则引擎
- 自动打分权重系统
- 自动中标审批系统
实施里最容易误解的 4 个点
1)以为 best lines 就是系统推荐中标人
不是。
best lines 只是把不同维度的优势行筛出来,方便你做判断。
2)以为 alternative PO 本质就是重复单
也不是。
它们是被系统显式承认为同一招标语义下的候选单。
3)以为确认一张单时系统应该自动取消剩余单据
源码故意不这么做,因为剩余候选的处理属于采购治理责任。
4)以为最低价一定等于最佳授标结果
系统同时比较总价、单价、交期,就已经在告诉你:
- “最佳”不是单一维度。
实战建议
如果你要在 Odoo 上做更强的招标授标能力,最好把自定义放在这几个层级上:
- 比较结果展示增强:把总价、单价、交期、质量评分一起展示
- 授标决策记录:增加中标原因、备选原因、审批痕迹
- 落标单处理策略:取消、保留、延期关闭
- 多供应商拆分中标:让“按产品线授标”有清晰界面
不要误以为 Odoo 原生已经内置完整的自动授标逻辑。
它提供的是非常好的基础骨架,但最后的采购责任仍然在企业自己手里。
一句话记忆
Odoo 的 Call for Tenders 会帮你组织候选、比较行项、提醒处理替代 RFQ,但不会替你自动完成授标,因为“中标”本质上是采购责任,不只是计算结果。
DISCUSSION
评论区