CRM 深度

Odoo CRM 线索补全不是“点一下 enrich 就完了”:IAP 补全、字段保守写入与批处理边界讲透

很多人把 Odoo CRM 的 lead enrichment 理解成“自动查公司资料并填满线索”。源码其实保守得多:它会过滤邮箱类型、控制批量与锁、尽量只补空字段,还会用 iap_enrich_done 和 reveal_id 避免重复补全。

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

先说结论

Odoo CRM 的 lead enrichment,不是“给我一个邮箱,系统就把公司资料全灌进去”。

源码真正表达的是一套很克制的策略:

  • 不是所有线索都允许补全
  • 不是所有邮箱都值得补全
  • 不是所有字段都会被覆盖
  • 不是一次失败就无限重试
  • 不是一批线索一起跑时就不考虑锁和事务

所以如果你在线上看到这些现象:

  • 某些 lead 明明有邮箱,却没有出现 enrich 按钮
  • 某些线索被补全了国家、公司名,却没有覆盖你原来手填的电话
  • 某批 enrich 跑到一半后没有把所有线索都处理完
  • 合并后新主记录看起来“继承了补全状态”

这些都不是零散行为,而是 Odoo 在源码层面有意识地做了保护。


一、按钮能不能点,先看这条线索有没有资格被补全

crm_iap_enrich/models/crm_lead.py 里,show_enrich_button 不是单纯看“有没有邮箱”。

_compute_show_enrich_button() 里至少同时判断了这些条件:

  • 线索必须是激活状态 active
  • 必须有 email_from
  • 邮箱质量不能是 incorrect
  • 不能已经 iap_enrich_done
  • 不能已经有 reveal_id
  • 不能已经 probability == 100

这很关键。

它说明 Odoo 的 enrich,不是一个“想点就点”的信息查询按钮, 而是一个只对仍在推进中的、还没补过的、邮箱看起来可靠的机会对象开放的动作

翻成人话:

已经赢单、已经补过、邮箱本身无效,或者记录已经不活跃时,系统默认认为再 enrich 一次的性价比不高。

这也是为什么 enrichment 更像 pipeline 中的“资格补充步骤”,而不是万能资料抓取器。


二、自动触发不是即时补全,而是“创建后唤醒 cron”

很多人以为自动 enrich 是在 create() 里直接调用外部服务。

源码没有这么激进。

create() override 里,Odoo 做的是:

  1. 先正常创建 lead
  2. 看参数 crm.iap.lead.enrich.setting
  3. 如果是 auto,就去触发 crm_iap_enrich.ir_cron_lead_enrichment

注意,是 trigger cron,不是同步请求外部 enrich 服务。

这背后的设计很实用:

  • 创建 lead 不会因为外部 enrich 网络波动而变慢
  • enrich 可以批量处理,不必每条线索单独打服务
  • 后续还能复用 cron 的进度提交、锁控制和异常处理能力

所以业务上更稳的理解应该是:

自动 enrich 不是“录入即补全”,而是“录入后进入一个异步补全过程”。

这和很多人脑补的同步调用完全不是一回事。


三、cron 也不是逮到邮箱就补,它先做了一轮资格筛选

_iap_enrich_leads_cron() 里默认会按这些条件找候选线索:

  • iap_enrich_done = False
  • probability < 100 或为空
  • email_from 不为空
  • reveal_id = False
  • create_date 还在延迟窗口内
  • active = True

这里有两个很容易被忽略的点。

1. 这是一个“最近创建 lead”的过程

它不是无限回头扫描历史全库。

enrich_hours_delay 默认 24 小时,本质上是在说:

  • 系统更关注刚进入 pipeline 的新线索
  • 太老的记录默认不在自动 enrich 的主要处理面里

这非常符合实际。

因为 enrichment 的价值最大的时候,通常正是线索刚入池、还没分配、还没推进的时候。

2. reveal_id 是强保护位

一旦已有 reveal_id,系统会认为这条线索已经拿过 enrich 结果,不再重复处理。

所以如果你做了导入、合并、或其他外部同步,看到 reveal 信息已经存在, 但按钮不再出现、cron 也不再碰它,这完全符合源码设计。


四、真正开始 enrich 前,系统先过滤掉“没必要查”的邮箱

iap_enrich() 里最业务化的一段,不是请求外部接口,而是先决定哪些线索值得送去查

你会看到两层筛选:

第一层:邮箱能不能规范化

如果 email_normalize() 后还是拿不到可靠邮箱,系统直接:

  • iap_enrich_done 设为 True
  • 留一条 note,说明没有可用邮箱

这表示:

没法标准化的邮箱,不会进入 enrich 重试地狱,而是明确记为“这个入口已经处理过,但没有结果”。

第二层:是不是通用邮箱提供商

源码会拆邮箱域名,如果命中 _MAIL_PROVIDERS 里的通用邮箱服务商,就直接跳过 enrich。

比如这类域:

  • gmail
  • outlook
  • yahoo
  • 其他公共邮箱服务商

为什么?

因为 Odoo enrich 的目标不是“查个人邮箱主人是谁”,而是根据企业域名推公司信息

如果邮箱是公共服务商地址,查出来的公司画像通常没有业务意义。

所以这里的策略不是“尽量多查”,而是“先把低价值请求砍掉,省信用、少噪音”。


五、字段写入策略非常保守:优先补空,不轻易覆盖人工值

_iap_enrich_from_response() 是最值得实施顾问细读的地方。

Odoo 并不是把外部返回值整包覆盖进 lead。

它的策略更像:

  • 如果 partner_name 为空,才写 name
  • 如果 street/city/zip 为空,才补地址
  • 如果 phone 为空,才取返回的第一个电话
  • 如果 country_id/state_id 为空,才按代码去映射国家和州

这意味着:

enrich 在默认语义上是“补洞”,不是“校正主数据”。

为什么这点重要?

因为很多团队会误以为 enrich 是更权威的数据源, 但 Odoo 的默认设计并不支持这个结论。

在官方实现里:

  • 外部 enrich 结果只是参考
  • 人工已经录入的值,优先级更高
  • 系统宁可保守,也不轻易替你推翻现有信息

这正是线上系统更稳的做法。


六、批量 enrich 不是“all or nothing”,而是按批提交和回滚

iap_enrich() 对批量处理的事务策略,也很像成熟生产代码。

关键点有三个:

1. 支持批大小

默认 batch_size=50

它不是一次把全部 self 打出去,而是分批处理。

2. cron 模式下会提交进度

from_cron 时会调用 _commit_progress(), 意味着这个任务被设计成可以被 cron 安全地长时间推进,而不是必须一口气跑完。

3. 非 cron 模式下会按批 commit

即使是手动 enrich,多批处理中每批也可能单独 commit; 遇到异常则回滚当前批,不让整批历史全丢。

这背后其实是在平衡两个东西:

  • 外部信用消耗已经发生,不能因为后面一条异常就把前面全抹掉
  • 但也不能让某个错误把整轮 enrich 卡死

所以它故意不是纯粹的单事务完美主义,而是偏向生产可恢复性


七、记录锁是显式考虑过的,不是谁先点谁赢

在批处理中,Odoo 会对待处理 lead 使用 try_lock_for_update(limit=batch_size)

如果一批记录拿不到锁:

  • 系统会打日志
  • 并把 cron 往后再触发 5 分钟

这其实说明官方默认预期:

  • 有可能有人正在编辑 lead
  • 也有可能另一轮作业正在处理同一批数据
  • enrichment 不应该因为抢锁失败就把记录写坏

所以 enrich 在并发设计上并不天真。

它不是“我拿到 self 就默认能改”,而是明确接受:

CRM 线索在生产环境里就是一个会被销售、自动分配、合并流程同时触碰的对象。


八、为什么有些线索 enrich 失败后,看起来“以后也不再补了”

这里要分情况看。

情况 1:明确不适合 enrich

比如:

  • 邮箱无法规范化
  • 命中通用邮箱服务商
  • 接口返回 not found

这几种情况下,源码通常会把 iap_enrich_done 设为 True。

语义不是“数据补全成功”,而是:

这条线索的 enrich 尝试已经走完,不需要无意义重试。

情况 2:外部异常

如果是 credits 不足、服务异常、JSONRPC 问题等, 系统更偏向:

  • 发通知
  • 打日志
  • 保留后续继续处理其他批的可能

这时是否重跑,要看异常点和批次进度,不是每次都一刀切。

所以实施上一定别把 iap_enrich_done 理解成“百分百补全成功”, 它更准确的意思是:

这个 enrich 生命周期已经处理过。


九、合并时为什么 enrich 状态也会“继承”

crm_iap_enrich 还 override 了 _merge_get_fields_specific()

它把 iap_enrich_done 的合并策略写成:

  • 只要被合并的几条 lead 里有任意一条为 True
  • 合并后的主记录就记为 True

这个细节很重要。

因为它代表官方的判断是:

既然这批重复线索里至少已有一个 enrich 过程跑过,就没必要把 merge 结果当成“全新、从未补全过”的 lead 再来一遍。

这和普通字段简单取主记录值不一样, 它显然是在避免 merge 后重复消耗 enrich 资源。

这也是为什么如果你把“待 enrich lead”与“已 enrich lead”合并后, 新主记录未必还会重新出现 enrich 按钮。


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

1. 把 enrich 当成主数据主来源

默认实现更像补空策略,不是覆盖策略。

2. 用公共邮箱期待拿到企业画像

官方直接跳过这类邮箱,本来就不是 enrich 的理想输入。

3. 误把 iap_enrich_done 当“补全成功”标志

它更接近“流程已处理”。

4. 以为自动 enrich 是同步执行

实际上是创建后唤醒 cron,再批处理。

5. merge 后还期待再次自动 enrich

合并策略已经尽量避免重复消耗。


最后一句

Odoo CRM 的 lead enrichment,最值得学的地方不在“能查到什么资料”, 而在它的默认哲学非常稳:

只补值得补的 lead,只补值得补的字段,只在合适的事务和并发边界里补。

如果你把它理解成一个保守、异步、补空优先的资料增强步骤, 你对 Odoo CRM 这条链路的很多疑惑都会自然消失。

DISCUSSION

评论区

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