Upsell 与续费边界

Odoo 订阅续费为什么不等于销售 Upsell:renewal 场景与 sale.order 协作边界讲透

很多人把 Odoo 销售里的 Upselling Opportunity 和订阅续费、服务加量、续签报价混成一件事。可从官方 sale 源码看,sale.order 里的 upsell 本质上只是开票状态和待办活动信号,并不等于 subscription renewal 引擎。本文专门讲清 renewal 场景该怎样与 sales order 协作。

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

先说结论

Odoo 销售里的 Upselling Opportunity,和很多人口中的“订阅续费”“续签报价”“合同加购”,并不是一回事。

sale 官方源码看,Upsell 的核心语义其实非常克制:

  • 它首先是 invoice_status 的一种状态
  • 它只在特定条件下成立:按 ordered quantity 开票、实际交付超过原始订购量
  • 它进一步触发一条 mail.activity,提醒销售去处理

也就是说,核心销售模块真正提供的是:

一个‘这张已成交销售单存在追加收费机会’的信号系统。

它并没有直接提供:

  • 完整的订阅续费对象模型
  • 自动 renewal contract 链路
  • 周期账期重建与续费计划排程

所以如果你把 sales 里的 upsell 当成 renewal engine,会对系统职责产生根本误读。


源码里的 Upsell,到底是什么

关键位置在:

  • sale/models/sale_order_line.py
  • sale/models/sale_order.py

行级条件

sale.order.line._compute_invoice_status() 里,只有在这些条件同时成立时,行才会进入 upselling

  • 行状态是 sale
  • 产品 invoice_policy == 'order'
  • qty_to_invoice == 0
  • qty_delivered > product_uom_qty
  • 并且不是普通“还有待开票”的情况

这段逻辑表达得很精准:

Upsell 不是“可能还能卖点什么”,而是“这条线已经发生超出原始订购量的实际履约,但当前不会自动再开票”。

典型场景就是:

  • 服务按订购数量先开票
  • 项目实际投入超出合同
  • 团队暂时没打算直接把超出部分自动计费
  • 系统于是给你一个 upselling opportunity

这和“订阅到期该续费了”根本不是同一类事件。


订单级 Upsell 只是行级状态的汇总,不是独立业务对象

sale.order._compute_invoice_status() 会把订单状态汇总成:

  • to invoice
  • invoiced
  • upselling
  • no

其中 upselling 的成立条件是:

  • 订单行状态里不存在还要正常开票的主线
  • 所有 relevant line 都已经 invoicedupselling

这说明订单级 invoice_status='upselling' 只是:

  • 对订单行状态做汇总
  • 给列表、筛选和操作界面一个总标签

它不是一个新的续费实体,也不是新的订单版本。

所以你不能把这个状态理解成:

  • 系统自动帮你生成 renewal quotation 了
  • 或者下一期订阅已经被建模好了

它只是提醒你这张已成交订单还有额外商业化空间


Odoo 核心销售模块对“续费”的真正态度:给信号,不接管全部生命周期

更有意思的是 _compute_field_value('invoice_status') 的覆写。

当订单从非 upselling 切到 upselling 时,系统会:

  • 找到负责人 order.user_idpartner_id.user_id
  • 调用 _create_upsell_activity()
  • 创建一条 TODO 活动:Upsell <order> for customer <customer>

这条设计非常值得玩味。

因为它没有做下面这些更“重”的动作:

  • 不会自动复制原订单
  • 不会自动起一张 renewal quotation
  • 不会自动重置订购周期
  • 不会自动写入新的 recurring contract

它只发出一条销售待办。

这说明在核心 sale 里,Odoo 对 upsell 的定位很明确:

这是销售动作提醒,不是续费主引擎。


为什么这和 subscription / renewal 场景刚好构成边界

用户在实施中最常见的误会是:

  • 订阅客户超用了服务
  • 销售单变成 upselling
  • 那系统应该自动把它接成 renewal 或追加周期账单

但从当前公开 sale 源码看,核心销售模块只知道:

  • 某些行超量交付了
  • 这些超量在当前计费策略下没有自动生成新 invoice
  • 应该提醒销售去跟进

而“订阅续费”真正需要解决的却是另外一组问题:

  • 新周期从什么时候开始
  • 续费是同价续签、升配还是降配
  • 旧合同与新合同如何衔接
  • 发票周期和服务周期如何滚动
  • 是否要重新出报价、重新签署、重新授权支付

这些都不是 sale.order.invoice_status = 'upselling' 能承载的。

所以正确边界是:

Upsell

  • 处理 超量履约后的追加商业机会
  • 核心动作是提醒销售跟进

Renewal

  • 处理 下一周期商业关系如何续接
  • 需要更完整的订阅/合同/周期建模

两者可以协作,但不能互相替代。


sale.order 在 renewal 场景里到底应该扮演什么角色

如果把职责摆正,sale.order 在订阅/续费类场景里最适合扮演的是:

1. 商业确认单据

当你决定:

  • 升配
  • 降配
  • 续签
  • 加购 seats / 工时 / 服务包

新的商业承诺仍然很适合落到一张 sales order / quotation 上。

2. Upsell 触发入口

现有订单在履约过程中触发 upselling,会提醒销售:

  • 该和客户谈扩容了
  • 该把超量服务转成正式商业承诺了

3. 后续 renewal 的谈判载体

真正的 renewal 策略可能来自订阅模块、实施规则或 CRM 流程,但最后形成正式报价时,sale order 仍然是最自然的承载体。

也就是说:

sale.order 很适合承接 renewal 的商业结果,但不应该被误认为 renewal 生命周期本身。


一个非常关键但经常被忽略的点:Upsell 依赖 invoice policy

源码把 upsell 绑得很死:

  • product_id.invoice_policy == 'order'

这意味着如果你的产品按 delivered quantities 开票,那么超出原订购量本来就会通过正常 qty_to_invoice 进入常规开票,不需要再走 upsell 提醒逻辑。

所以能进入 upselling 的,通常是这种业务设定:

  • 先按订购量或合约量开票
  • 但实际交付可能超出
  • 团队不想系统自动强行把超出部分转成新发票
  • 而是让销售先判断要不要追收、续签、扩容

这本质上是一种人工商业决策保留机制

也正因为如此,它天然更像“销售提醒”,而不是“订阅引擎”。


实战里最容易犯的 4 个错

1. 把 upselling 菜单当成 renewal 列表

那里面聚合的是“超量但尚未转化的销售机会”,不是“即将到期的续费合同”。

2. 看到 upsell 就期待系统自动生成下一期账单

核心 sale 并不会这么做,它只会安排 activity。

3. 忽略负责人分配

如果订单没有 user_id,系统会退回 partner_id.user_id。负责人没配好,upsell 提醒就会落空或落错人。

4. 用错误的 invoice policy 期待 upsell

按 delivered 开票的产品,本来就更可能走正常开票而不是 upselling 状态。


一句话总结

Odoo 核心销售模块里的 Upselling Opportunity,本质上是:

  • sale.order.line 超量履约触发的 invoice_status
  • sale.order 汇总成订单级状态
  • 再通过 mail.activity 把“该跟客户谈扩容/补差/追加收费了”这件事提醒给销售

它可以和 subscription renewal 协作,但它自己并不是 renewal engine。

所以最稳的理解应该是:

Upsell 负责发出商业化信号,renewal 负责管理下一周期关系,而 sale.order 负责承接最终谈成的商业结果。

DISCUSSION

评论区

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