CRM 深度

Odoo CRM 为什么一合并线索就“只剩一个来源”:dedup、merge 主记录与 UTM 归因边界讲透

很多人以为 Odoo CRM 的 merge 只是“把两条重复线索收纳到一起”。源码其实更像“选一个主记录,再把其他记录的依赖搬过去”。一旦你把不同来源的线索合并,campaign / medium / source 往往只会保留主记录那一套。

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

先说结论

在 Odoo CRM 里,合并重复线索不是“做一个集合并集”,而更像:

先选一个主记录,再把其他记录的部分字段和依赖转移过来,最后删掉尾部记录。

这个结论一旦成立,很多现象就突然解释得通了:

  • 为什么合并后只剩一个 campaign_id / medium_id / source_id
  • 为什么有些跟进历史、附件、日程还能保留
  • 为什么丢单原因不会总是保留
  • 为什么不同来源的线索一 merge,归因口径就开始模糊

所以这篇不讲“怎么点按钮”,而讲更本质的问题:

Odoo CRM 的 dedup / merge,到底保留了什么,又丢掉了什么。


一、Odoo 先怎么判断“可能重复”

源码里有两套相关逻辑,很多人会混在一起。

第一套:界面上的潜在重复提示

_compute_potential_lead_duplicates() 主要用来算:这条记录看起来是不是有潜在重复。

它用到的标准包括:

  • email_domain_criterion / 邮箱域相关匹配
  • phone_sanitized 精确匹配
  • 同一个 commercial_partner 体系

源码注释里写得很直白:

  • 邮箱域精确匹配
  • 清洗后的手机号精确匹配
  • 同一商业实体

这套逻辑的定位更偏提醒和发现

第二套:真正用于分配和去重的重复搜索

_get_lead_duplicates() 则更偏“拿来处理”的重复判断。

它主要基于:

  • email_normalized
  • partner_id

然后再过滤:

  • 默认只看 won_status = pending
  • 只看 active = True

也就是说,默认情况下:

  • 已赢单的不参与这类重复清理
  • 已归档、不可操作的记录也不纳入主要 dedup 结果

这就很业务。

因为 Odoo 想处理的是当前还在推进中的重复线索,而不是把历史全翻出来一起搅。


二、自动去重不是孤立动作,而是会嵌进线索分配流程

crm_team.py 里,销售团队分配 lead 时,会顺手做 deduplicate。

你能看到:

  • _allocate_leads_deduplicate()
  • duplicates_cache
  • _merge_opportunity()

这意味着 Odoo 的思路不是:

  1. 先把所有 lead 分完
  2. 以后再单独去重

而更像:

  1. 先判断候选 lead
  2. 在分配前看有没有明显重复
  3. 如有必要,直接 merge,再把结果交给团队

这个设计背后的业务直觉非常强:

如果不先去重,同一个客户可能同时被分给不同销售,CRM 脏得会非常快。

所以 dedup 在 Odoo 里不是可有可无的小工具,而是 pipeline 清洁度的一部分。


三、合并时,谁才是“活下来”的那条记录

源码里 _merge_opportunity() 不会随机挑一条记录当结果。

它先调用 _sort_by_confidence_level(reverse=True) 排序。

排序启发式大致是:

  1. 没丢的优先
  2. opportunitylead 更可靠
  3. stage.sequence 越靠后越优先
  4. probability 越高越优先
  5. 记录越新越优先

然后:

  • 排序后的第一条记录,成为 opportunities_head
  • 其余都是 opportunities_tail

翻成人话:

Odoo 默认认为“更像已经被验证、更接近成交”的那条,最适合当主记录”。

这不是技术细节,而是 merge 结果的根。

因为后面大量字段保留逻辑,都依赖这个 head。


四、为什么合并后 UTM 只剩一套

最值得写进脑子里的地方在这里。

CRM_LEAD_FIELDS_TO_MERGE 里明确包含:

  • campaign_id
  • medium_id
  • source_id

_merge_data() 对大多数普通字段的策略是:

  • 取排序后第一条记录里的第一个非空值

这就意味着,如果你把下面两条记录合并:

  • A:Google Ads / Website / Search
  • B:Newsletter / Email / Mailing

最终主记录里通常不会保留“两套来源”,而是:

  • 先看排序后谁是 head
  • 再保留 head 那条的非空 UTM 值
  • 另一条来源语义只会留在历史消息或被删除记录里,不再作为主字段存在

这就是很多运营同学会觉得“合并以后归因变脏了”的根本原因。

不是报表写错,而是数据模型本来就不是多来源归因模型。

这件事最容易被误会成什么

很多人以为 merge 是:

  • 联系人信息取最完整的
  • 历史跟进都保留
  • 来源归因也会自动综合

但源码并没有做“归因融合”。

它做的是:

字段层面以主记录优先,依赖层面尽量搬迁,尾记录最后删除。

所以 UTM 这类单值字段在 merge 之后天然会更像“主记录口径”,而不是“多触点历史口径”。


五、Merge 不是只搬字段,也会搬依赖

虽然主字段会偏向 head,但 Odoo 并不是把 tail 直接粗暴删掉。

源码里 _merge_dependences() 会继续处理:

  • 历史消息 / activities
  • 附件
  • 日历事件

还有 _merge_followers(),会尽量把 followers 合并,避免重复 follower 关系。

所以合并的真实语义是:

  • 主业务字段:以主记录为准
  • 外围历史依赖:尽量迁移到主记录

这也是为什么你合并后常常会觉得:

  • 沟通记录似乎还在
  • 附件似乎也没丢
  • 但来源字段、阶段、负责人这些主属性只剩一套

这是设计使然,不是偶发 bug。


六、为什么 lost reason 不一定保留

_merge_get_fields_specific() 里有一个特别有意思的规则:

  • 如果排序后的第一条 lead probability 非 0,则 lost_reason_id = False
  • 只有当头记录本身已经是概率为 0 的语义时,才会从被合并记录里挑一个 lost reason

翻成人话:

只要合并结果仍然是一个在推进中的机会,系统就不想让“丢单原因”挂在主记录上。

这非常合理。

因为 lost reason 属于失败语义,而 merge 结果如果是一个仍在推进的 opportunity,保留 lost reason 反而会制造歧义。


七、为什么 stage 也不一定照搬你以为的那个

还有一个容易忽视的细节。

合并之后,如果 merged_data 里带了 team_id,Odoo 会检查当前 stage_id 是否属于这个销售团队可用的 stage。

如果不属于,系统会:

  • 重新换成该团队最前面的可用 stage

所以 merge 不只是字段保留问题,还涉及目标团队的管道合法性

这意味着有些用户会看到:

  • 明明原记录在某个较后阶段
  • merge 后阶段却回到了团队下的另一个 stage

并不一定是丢数据,而是 merge 结果必须满足新的团队 stage 约束。


八、为什么官方还限制一次最多合并 5 条

_merge_opportunity() 里默认有 max_length=5,非超级用户超过这个数量会报错。

源码给的理由很直接:

  • To prevent data loss

这其实非常诚实。

因为 Odoo 自己也知道,merge 不是数学上的无损合并,而是一个带优先级取舍的业务动作。

数量一旦太大:

  • 主记录选择更难保证合理
  • 非空值优先策略更可能误保留
  • UTM / 负责人 / 阶段 / 联系信息越容易失真

所以这个限制不是保守,而是对数据语义的自我保护。


九、实战里最容易踩的 5 个坑

坑 1:把 merge 当成“无损归档整理”

不是。

它本质是“选主记录 + 迁依赖 + 删尾记录”。

坑 2:以为 UTM 会自动汇总

不会。

campaign_id / medium_id / source_id 是单值字段,merge 时通常主记录优先。

坑 3:把不同触点的线索太早合并

如果营销还需要看来源效果,太早 merge 可能直接把多来源信息压扁成一套主字段。

坑 4:以为 lost reason 一定会跟着保留

不一定。

只要 merge 结果还是活跃机会,lost reason 往往会被清掉。

坑 5:以为阶段不会受团队影响

会。

team 变化后,stage 还要重新校验是否属于该团队管道。


十、什么时候该 merge,什么时候别急着 merge

我自己的判断是:

适合 merge 的情况

  • 明显同一客户、同一销售机会、只是重复录入
  • 当前更想保证销售协同,不想让多人重复跟进
  • 归因已经不再是核心分析对象

不要急着 merge 的情况

  • 同一客户来自多个营销触点,你还想分析来源效果
  • 你需要保留 first-touch / last-touch / multi-touch 的历史语义
  • 线索尚处于甄别期,重复与否还没完全确认

因为 Odoo CRM 原生 merge 更偏销售主记录清理,不是营销归因仓库。


一句话记忆法

Odoo CRM 的 merge,不是把多条线索平等拼起来,而是选一个最可信的主记录;UTM、阶段、负责人等主字段以主记录优先,消息附件等外围依赖再尽量搬过去。

如果你用这句话去理解 dedup / merge,就不会再把它错当成“自动保留所有来源语义”的工具了。

DISCUSSION

评论区

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