招标比价

Odoo 招标为什么不会自动帮你“选中标人”:比价行、替代供应商与授标边界讲透

很多团队以为 Odoo 既然能比较 Call for Tenders 的替代 RFQ,就应该顺手帮你自动选出中标供应商。源码其实不是这么设计的。它负责组织候选单、按产品比较总价/单价/交期,并在确认时把“其他替代单怎么办”推回给采购员。

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

先说结论

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.group
  • purchase_group_id
  • alternative_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,也就是转成公司币后的金额比较。

这很重要,因为它说明:

  1. Odoo 承认多币种候选单需要先落到可比较口径
  2. Odoo 也承认“最优”不是只有一种定义
  3. Odoo 允许同一产品在不同维度上出现不同赢家

比如:

  • A 供应商总价最低
  • B 供应商单价最低
  • C 供应商交期最早

那系统该自动选谁?

源码没有替你回答。

因为这本来就不是一个纯技术题,而是组织采购策略题。


第四层:确认某张 PO 时,Odoo 故意弹窗,而不是偷偷取消其他候选

button_confirm() 在招标场景里还有一个特别关键的动作。

如果当前订单存在仍处于:

  • draft
  • sent
  • to approve

alternative_po_ids,系统不会直接确认,而是先打开一个警告窗口:

  • What about the alternative Requests for Quotations?

这个弹窗的意义非常强。

它说明 Odoo 认为:

  • 你现在做的是授标动作
  • 授标不是“确认赢家”这么简单
  • 还必须明确处理剩余候选

也就是说,系统不帮你偷偷把其他候选一刀切掉。

原因很现实:

  • 有些团队要立即取消落标 RFQ
  • 有些团队要暂时保留第二供应商作为备选
  • 有些团队允许按品类拆分授标
  • 有些团队还要走内部审批后才敢关掉其他 RFQ

因此 Odoo 只负责提醒,不负责替你承担采购责任。


第五层:替代单的“组关系”会维护,但不会变成自动中标引擎

源码里,create()write()_merge_alternative_po() 都在维护 purchase_group_idalternative_po_ids 的关系:

  • 新建替代 RFQ 时,可加入原来的组
  • 手工改 alternative 关系时,会自动建组、删组或合组
  • 合并 RFQ 时,也会把替代关系继续带过去

这说明官方非常重视候选集合的一致性

但你会发现,整条链路里没有“自动关单最佳供应商并完成授标”的实现。

这不是遗漏,而是边界选择。

Odoo 想做的是:

  • 候选组织层
  • 比较视图层
  • 授标提醒层

而不是:

  • 企业评审规则引擎
  • 自动打分权重系统
  • 自动中标审批系统

实施里最容易误解的 4 个点

1)以为 best lines 就是系统推荐中标人

不是。

best lines 只是把不同维度的优势行筛出来,方便你做判断。

2)以为 alternative PO 本质就是重复单

也不是。

它们是被系统显式承认为同一招标语义下的候选单。

3)以为确认一张单时系统应该自动取消剩余单据

源码故意不这么做,因为剩余候选的处理属于采购治理责任。

4)以为最低价一定等于最佳授标结果

系统同时比较总价、单价、交期,就已经在告诉你:

  • “最佳”不是单一维度。

实战建议

如果你要在 Odoo 上做更强的招标授标能力,最好把自定义放在这几个层级上:

  1. 比较结果展示增强:把总价、单价、交期、质量评分一起展示
  2. 授标决策记录:增加中标原因、备选原因、审批痕迹
  3. 落标单处理策略:取消、保留、延期关闭
  4. 多供应商拆分中标:让“按产品线授标”有清晰界面

不要误以为 Odoo 原生已经内置完整的自动授标逻辑。

它提供的是非常好的基础骨架,但最后的采购责任仍然在企业自己手里。


一句话记忆

Odoo 的 Call for Tenders 会帮你组织候选、比较行项、提醒处理替代 RFQ,但不会替你自动完成授标,因为“中标”本质上是采购责任,不只是计算结果。

DISCUSSION

评论区

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