CRM 深度

Odoo CRM 的概率为什么会自己跳:automated_probability、阶段赢单和 expected revenue 的边界讲透

很多人以为 Odoo CRM 里的 expected revenue 会跟着概率一起“改金额”。源码其实不是这么设计的:系统自动算的是概率,金额本体通常不动,真正被联动的是 prorated revenue、won/lost 语义和关闭时间。

CRM Odoo 开发
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 30 阅读

先说结论

如果你在 Odoo CRM 里看到概率自己变化,不要第一反应就觉得系统在“偷偷改商机金额”。

源码表达的其实是另一套逻辑:

  • 系统主要自动计算的是 probabilityautomated_probability
  • expected_revenue 通常还是你录入的业务金额本体
  • 真正随概率联动的,是 prorated_revenue 这种折算后的预测口径
  • 一旦进入 won / lost 语义,概率、关闭时间、归档状态会一起被改写

所以 Odoo CRM 里最容易搞混的一件事,不是“概率怎么算”,而是:

概率字段、阶段字段、赢单/丢单动作、expected revenue、prorated revenue,到底谁是原值,谁是预测值,谁又是业务语义开关。


一、先把几个名字翻成人话

addons/crm/models/crm_lead.py 里,你会看到几组关键字段:

  • probability
  • automated_probability
  • is_automated_probability
  • expected_revenue
  • prorated_revenue
  • won_status
  • lost_reason_id

可以把它们理解成:

  • expected_revenue:这笔机会如果做成,大概值多少钱
  • probability:你现在认为它有多大概率成单
  • automated_probability:系统根据历史样本、当前阶段和若干特征算出的“机器建议概率”
  • is_automated_probability:当前界面显示的概率,是不是还跟系统建议保持同步
  • prorated_revenue:不是合同金额,而是“金额 × 概率”之后的预测口径
  • won_status:这条记录在系统语义上到底是 pending、won 还是 lost

这套拆分非常重要。

因为 Odoo 没把“金额”和“预测值”混成一个字段,而是明确分成业务原值预测折算值


二、概率为什么会“自己跳”

源码里 probabilityautomated_probability 都挂在 _compute_probabilities() 上。

关键逻辑很值得注意:

  1. 系统先通过 _pls_get_naive_bayes_probabilities() 算出一个建议概率
  2. 这个结果先写进 automated_probability
  3. 只有在 was_automated 为真的情况下,才会把 probability 一起更新

was_automated 的判断依赖:

  • 当前记录是激活状态 lead.active
  • 并且 lead.is_automated_probability 为真

翻成人话就是:

只要你还没有手动偏离系统建议值,系统就继续帮你自动更新;一旦你人工改过,系统会继续更新建议值,但不会强行覆盖你的手工概率。

这就是很多人看到的现象:

  • 有些商机拖动阶段后,概率自动变了
  • 有些商机却不再自动变

不是系统不一致,而是它在区分:

  • 这条记录现在还处于“自动驾驶”
  • 还是已经进入“人工接管”

三、is_automated_probability 其实就是“你有没有手动干预”的标志

源码里 _compute_is_automated_probability() 的判断非常直接:

  • 如果 probabilityautomated_probability 相等
  • 就认为当前概率仍然是自动计算结果

也就是说,这不是单独存一条“是否人工改过”的日志位,而是通过两个数值是否一致来判断。

这个设计很朴素,但也非常实用。

因为它让 Odoo 保留了两层东西:

  • 一层是系统的最新建议
  • 一层是业务当前采用的实际概率

于是销售经理可以做两件事:

  • 参考系统建议
  • 在特殊项目里明确覆盖系统建议

这也是为什么界面上你会感觉 Odoo 并不是简单地“自动评分”,而是在做建议值 + 实际采用值双轨并存。


四、expected revenue 为什么看起来“没跟着变”

这是 CRM 使用里一个非常高频的误解。

很多人会直觉地认为:

  • 金额 10 万
  • 概率从 30% 变成 60%
  • 那 expected revenue 应该自动从 3 万变成 6 万

但源码不是这样。

expected_revenue 本身并不会因为概率变化而自动改写。

系统单独计算的是:

prorated_revenue = expected_revenue * probability / 100

也就是说:

  • expected_revenue 更像“订单如果拿下来的面值”
  • prorated_revenue 才是“按当前概率折算后的预测值”

这两个字段如果你在认知里混成一个,就会马上出错。

一个很常见的业务误判

销售说:

  • 这个商机预计 50 万
  • 现在只有 20% 把握

正确理解应该是:

  • expected_revenue = 500000
  • probability = 20
  • prorated_revenue = 100000

如果你把 expected revenue 直接改成 10 万,等于把“商机面值”和“概率折算值”混成一个字段,后面所有分析都会越来越乱。


五、Won / Lost 为什么不只是看一个标签

源码里 won_status 的计算很严格:

  • probability == 100stage_id.is_wonwon
  • not activeprobability == 0lost
  • 否则 → pending

这说明 Odoo 并没有把 won / lost 当作普通枚举状态,而是当成由多个字段共同表达出来的业务语义

赢单语义

action_set_won() 的核心行为是:

  • 先取消归档
  • 找到合适的 won stage
  • 写入 stage_id = won_stageprobability = 100

而在 write() 逻辑里,如果阶段本身 is_won=True,系统还会补:

  • active = True
  • probability = 100
  • automated_probability = 100

所以赢单不是“打一个勾”,而是:

进入赢单阶段 + 概率强制到 100 + 关闭时间落下去。

丢单语义

action_set_lost() 更直接:

  • 先归档 action_archive()
  • 再写入 probability = 0
  • 同时写 automated_probability = 0

所以 lost 的本质不是某个 stage 名字叫 Lost,而是:

归档 + 概率归零

这也是为什么源码注释里直接写:

  • Won semantic: stage.is_won
  • Lost semantic: probability = 0 AND active = False

六、阶段切换为什么会顺带影响关闭时间

write() 里还有一段很容易被忽略,但对报表口径非常关键:

  • 如果 probability >= 100active = False,设置 date_closed
  • 如果 probability > 0,则清空 date_closed
  • 如果阶段变化但不是赢单阶段,且没有显式改概率,也会清空 date_closed

这意味着:

  • 进入赢单或丢单语义时,系统认为这条商机“关单了”
  • 回到正常推进状态时,它又不应该继续带着旧的关闭时间

很多公司报表里“明明恢复跟进了,却还算历史已关闭商机”的混乱,往往就是没理解这套语义联动。


七、为什么把某个阶段设成 Won,会影响整批商机

crm_stage.py 里对 crm.stage.write() 有一个很重要的重写。

如果你把某个 stage 的 is_won 改成 True:

  • 这个阶段里的所有 lead / opportunity 都会被写成 probability = 100
  • automated_probability 也一起变成 100

反过来,如果把一个原来的 won stage 取消掉:

  • 系统会对这些记录重新跑 _compute_probabilities()

源码注释还特意提醒了一句:

用户如果先把阶段设成 won,又马上改回来,原来人工改过的概率也可能丢失。

这句话很值钱。

因为它说明“阶段定义”不是纯配置,它会反向改业务数据。

所以在生产环境里,不要把 is_won 当作无害的显示配置开关


八、Restore 为什么和 Unarchive 不是一回事

这是另一个很容易踩坑的点。

action_unarchive()

重新激活记录后:

  • 会清掉 lost_reason_id
  • 会重算概率
  • 但不一定把 probability 对齐回系统建议值

action_restore()

在 unarchive 之后,又额外做了一步:

  • lead.probability = lead.automated_probability

这意味着:

  • Unarchive 更像“把记录从归档箱拿回来”
  • Restore 更像“恢复正常生命周期,并重新交回自动概率控制”

如果你只是把丢单记录重新激活,但没有 restore,你会发现一些记录的概率看起来“怪怪的”,原因就在这里。


九、实战里最该避免的 4 个误区

误区 1:把 expected revenue 当成加权预测金额

错。

加权预测要看 prorated_revenue,不是直接改 expected_revenue

误区 2:以为阶段越往后,概率就一定纯手写

不一定。

只要 probability == automated_probability,系统仍然会继续自动联动。

误区 3:以为 lost 只是一个标签

不是。

lost 是 active=Falseprobability=0 的组合语义。

误区 4:以为 Won stage 只是看板颜色或列名

也不是。

把阶段配置成 won,会反向改整批商机的概率与关闭语义。


十、一句话记忆这套边界

可以记成一句:

expected revenue 是面值,prorated revenue 是预测值;probability 可以自动也可以人工接管;won/lost 不是标签,而是阶段、概率、归档状态一起组成的业务语义。

如果你把这句话吃透,Odoo CRM 里关于“为什么概率变了、为什么金额没变、为什么关单口径又变了”的大部分困惑,基本都会消失。

DISCUSSION

评论区

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