先说结论
如果你在 Odoo CRM 里看到概率自己变化,不要第一反应就觉得系统在“偷偷改商机金额”。
源码表达的其实是另一套逻辑:
- 系统主要自动计算的是
probability和automated_probability expected_revenue通常还是你录入的业务金额本体- 真正随概率联动的,是
prorated_revenue这种折算后的预测口径 - 一旦进入 won / lost 语义,概率、关闭时间、归档状态会一起被改写
所以 Odoo CRM 里最容易搞混的一件事,不是“概率怎么算”,而是:
概率字段、阶段字段、赢单/丢单动作、expected revenue、prorated revenue,到底谁是原值,谁是预测值,谁又是业务语义开关。
一、先把几个名字翻成人话
在 addons/crm/models/crm_lead.py 里,你会看到几组关键字段:
probabilityautomated_probabilityis_automated_probabilityexpected_revenueprorated_revenuewon_statuslost_reason_id
可以把它们理解成:
- expected_revenue:这笔机会如果做成,大概值多少钱
- probability:你现在认为它有多大概率成单
- automated_probability:系统根据历史样本、当前阶段和若干特征算出的“机器建议概率”
- is_automated_probability:当前界面显示的概率,是不是还跟系统建议保持同步
- prorated_revenue:不是合同金额,而是“金额 × 概率”之后的预测口径
- won_status:这条记录在系统语义上到底是 pending、won 还是 lost
这套拆分非常重要。
因为 Odoo 没把“金额”和“预测值”混成一个字段,而是明确分成业务原值和预测折算值。
二、概率为什么会“自己跳”
源码里 probability 和 automated_probability 都挂在 _compute_probabilities() 上。
关键逻辑很值得注意:
- 系统先通过
_pls_get_naive_bayes_probabilities()算出一个建议概率 - 这个结果先写进
automated_probability - 只有在
was_automated为真的情况下,才会把probability一起更新
而 was_automated 的判断依赖:
- 当前记录是激活状态
lead.active - 并且
lead.is_automated_probability为真
翻成人话就是:
只要你还没有手动偏离系统建议值,系统就继续帮你自动更新;一旦你人工改过,系统会继续更新建议值,但不会强行覆盖你的手工概率。
这就是很多人看到的现象:
- 有些商机拖动阶段后,概率自动变了
- 有些商机却不再自动变
不是系统不一致,而是它在区分:
- 这条记录现在还处于“自动驾驶”
- 还是已经进入“人工接管”
三、is_automated_probability 其实就是“你有没有手动干预”的标志
源码里 _compute_is_automated_probability() 的判断非常直接:
- 如果
probability和automated_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 = 500000probability = 20prorated_revenue = 100000
如果你把 expected revenue 直接改成 10 万,等于把“商机面值”和“概率折算值”混成一个字段,后面所有分析都会越来越乱。
五、Won / Lost 为什么不只是看一个标签
源码里 won_status 的计算很严格:
probability == 100且stage_id.is_won→wonnot active且probability == 0→lost- 否则 →
pending
这说明 Odoo 并没有把 won / lost 当作普通枚举状态,而是当成由多个字段共同表达出来的业务语义。
赢单语义
action_set_won() 的核心行为是:
- 先取消归档
- 找到合适的 won stage
- 写入
stage_id = won_stage和probability = 100
而在 write() 逻辑里,如果阶段本身 is_won=True,系统还会补:
active = Trueprobability = 100automated_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 >= 100或active = 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=False 和 probability=0 的组合语义。
误区 4:以为 Won stage 只是看板颜色或列名
也不是。
把阶段配置成 won,会反向改整批商机的概率与关闭语义。
十、一句话记忆这套边界
可以记成一句:
expected revenue 是面值,prorated revenue 是预测值;probability 可以自动也可以人工接管;won/lost 不是标签,而是阶段、概率、归档状态一起组成的业务语义。
如果你把这句话吃透,Odoo CRM 里关于“为什么概率变了、为什么金额没变、为什么关单口径又变了”的大部分困惑,基本都会消失。
DISCUSSION
评论区