订阅商机真正复杂的地方,是 recurring revenue、项目任务和 postpaid 工时要在销售赢单后持续保持一条上下文链。
主要参考:
- `enterprise/crm_sale_subscription/models/crm_lead.py`
enterprise/project_sale_subscription/models/sale_order_line.pyenterprise/sale_subscription_timesheet/models/sale_order_line.py-
enterprise/project_sale_subscription/tests/test_subscription_task.py
一、这不是单模块按钮,而是一条跨模块链路
很多人把这个功能理解成某个界面上的一个按钮、一个 smart button,或者一次自动创建。但从源码看,真正重要的是:上游对象先保留业务上下文,中间层做状态/域/权限判断,下游对象再接住这个上下文继续工作。只看最后一个界面动作,很容易把问题看窄。
二、核心跨链路是怎么跑通的
- 商机关联销售订单后,
_update_revenues_from_so()会按 non_recurring_total 与 recurring_monthly 更新 expected revenue 和 recurring revenue。 - 如果订阅产品还能生成项目任务,
project_sale_subscription会在创建 task 时按订阅周期或任务模板建立 recurrence,而不是只建一次单。 - 若订阅行按 timesheet 交付计量,
sale_subscription_timesheet会按 last_invoice_date / next_invoice_date 只统计本计费窗口的工时。
这就是为什么我把它归类为“跨模块链路”题:这里至少同时牵涉了业务对象、会计/项目/销售对象,以及状态或权限判断,而不是单个模型内部的小机制。
三、最容易踩错的边界
- 收入自动更新只会在订单币种等于公司币种、且新值更大时推进,避免商机金额被随意回写。
- 订阅型项目利润会在 profitability 中单列 subscriptions,避免和普通 sale items 混在一起。
- refund 会重新放开已计费工时的统计边界,确保 delivered quantity 与 credit note 一致。
这些边界决定了数据是否还能回到正确的模块继续流动。如果边界被自定义绕开,后面最常见的结果就是:报表看起来还能出, drill-down 却已经解释不通。
四、落地时最值得先验的三件事
- 做续费型销售时,不要只看 won stage,还要看 recurring revenue 是否真正挂回 opportunity。
- 周期性服务尽量用 recurring task,不要让客服每月手建任务。
- 若计费窗口总是对不上,先查 subscription 的 next_invoice_date,再查 timesheet domain。
五、结论
赢单只是开始,订阅收入、循环任务和工时交付都要继续挂在同一条商机上下文上。 这也是企业版功能最容易被低估的地方:看上去只是一个入口,实质上是在多个子系统之间持续传递状态、上下文、数据或权限。
- 商机关联销售订单后,
DISCUSSION
评论区