先说结论
Odoo CRM 的 Lead Mining 不是“把筛选条件丢给 IAP,然后等外部服务吐线索”。
crm.iap.lead.mining.request 在真正请求远端前,先做了三层业务约束:
- 把筛选条件收敛到服务能稳定理解的形状;
- 提前估算 credits 成本,避免用户误以为联系人是白送的;
- 把结果要落到哪个 team、哪个 salesperson、按 lead 还是 opportunity 建单先定好。
所以这个模型本质上更像一个有预算意识的请求编排器,而不是单纯的 API 包装层。
一、为什么“州过滤”不是所有国家都开放
源码里最容易被忽略的是 _compute_available_state_ids()。
它并不是简单按 country_ids.state_ids 把所有州/省都列出来,而是只对白名单国家开放州过滤。原因写得很直白:
- 某些国家在 reveal 服务侧的州数据覆盖率很差;
- 如果把这些州也放给用户选,查询结果会被意外压缩;
- 用户会误以为“这个国家没有目标客户”,其实只是外部数据不完整。
这意味着 Odoo 的设计取向不是“让筛选器看起来更强”,而是优先保证筛选器不会误导业务判断。
站在实施角度,这个细节很重要:
如果你在某些国家看不到州过滤选项,不一定是权限问题,也不一定是字段没配,而是产品故意收窄了过滤维度。
二、credits 为什么不是“公司数 + 联系人数”这么简单
_compute_tooltip() 的口径非常值得讲,因为它决定了销售和管理层对 IAP 成本的预期。
模型里有两组常量:
CREDIT_PER_COMPANY = 1CREDIT_PER_CONTACT = 1
但真正的总成本不是简单相加,而是:
- 找公司本身消耗
lead_number个 credits; - 每家公司再额外追踪
contact_number个联系人; - 所以联系人成本会按公司数放大。
也就是说,如果你请求:
- 20 家公司,
- 每家公司最多 5 个联系人,
那联系人成本不是 5,而可能是 20 × 5 = 100 的量级,再叠加公司本身的成本。
这就是为什么 tooltip 里会把“找公司”和“找联系人”拆开解释。Odoo 在这里防的不是技术错误,而是预算错觉。
很多团队第一次开通 lead mining 时,最容易踩的坑就是:
- 以为联系人只是“附带返回”;
- 实际上联系人是按公司粒度放大的增量消耗。
三、search_type 和 lead_type 是两条不同维度,不要混为一谈
源码把两个概念拆得很清楚:
search_type:外部要找“公司”还是“公司 + 联系人”;lead_type:回到 Odoo 后,要落成lead还是opportunity。
这两个字段看起来都在讨论“线索类型”,但其实根本不是一回事。
search_type
决定 payload 里是否追加:
contact_numbercontact_filter_typepreferred_roleother_rolesseniority
也就是说,它控制的是外部服务要挖多深。
lead_type
决定结果回库时,crm.iap.lead.helpers.lead_vals_from_response() 最终建成 lead 还是 opportunity。
也就是说,它控制的是内部销售流程怎么接。
很多项目里会把这两个选项一起解释成“你想不想要商机”,这是不准确的。一个更靠谱的理解是:
一个决定你向外买什么数据,一个决定你把买回来的数据放进哪条内部漏斗。
四、为什么 team 会在提交前就先回填,而不是建出来再分配
_compute_team_id() 的逻辑很像 Odoo 在很多业务模型里的典型风格:先限制合法落点,再允许用户提交。
它会根据:
- 当前
user_id - 当前
lead_type - team 是否启用 leads / opportunities
去挑一个默认 team。
如果你已经手工选了一个 team,且该用户确实属于这个 team 的负责人或成员,源码会保留你的选择;否则会重新找一个合法 team。
这背后的设计思路非常清晰:
- Lead Mining 不是纯数据采购;
- 它买回来的结果必须马上能被销售组织接住;
- 所以组织归属不能拖到最后。
这也解释了为什么很多人感觉“我改个 salesperson,team 怎么跟着变了”:
因为 team 在这里不是展示字段,而是销售归属约束。
五、请求真正发出去之前,Odoo 已经做了哪些收口
_prepare_iap_payload() 做的事情比表面看上去多:
- 把国家转成国家 code;
- 把州过滤压成对应国家下的 state code 列表;
- 行业标签把多个
reveal_ids拆开并合并; - people 模式下再补联系人角色或 seniority;
- 只在启用 size filter 时发公司规模上下限。
也就是说,Odoo 发给 IAP 的不是一份“表单原始值”,而是一份已归一化的业务查询描述。
这对排错非常关键。
如果结果异常,不要只盯着界面字段显示对不对,而要问:
- 这个字段有没有真正进 payload?
- 是不是因为模式不同根本不会发?
- 行业 reveal ids 是否被正确展开?
六、为什么错误处理只区分 credits 和 no_result
error_type 只保留两个显式枚举:
creditsno_result
这看起来很“粗”,但其实是产品层的刻意简化。
对业务用户来说,最关键的分支通常就两个:
- 钱不够,买不了;
- 钱够,但这组条件没有命中结果。
至于底层网络波动、服务端异常,源码会包成 UserError 抛回去,而不把这些技术细节长期保存在业务状态字段里。
这说明 Lead Mining 模型的状态机并不试图完整映射外部 API 的所有失败类型,而是只保留业务可决策的失败口径。
七、action_submit() 为什么先拿序列号,再决定是否重开当前表单
action_submit() 有个很像 Odoo 风格的小细节:
- 第一次提交时先分配 request 编号;
- 请求成功则建线索并跳到结果 action;
- 请求失败且当前在 modal 中,则重新打开同一条记录;
- 非 modal 下则直接刷新当前表单。
这背后的重点不是 UI,而是让失败仍然有上下文可追溯。
也就是说,哪怕一次挖掘没有产出,这条 request 也不是一次性临时动作,而是一条可以回看、改条件、重新提交的业务记录。
八、实施里最容易误判的几个点
1. 看不到州过滤,就以为国家数据没维护
不一定,可能是产品为了避免低覆盖率误导,直接没开放州过滤。
2. 联系人成本被低估
很多团队只看“每联系人 1 credit”,却忘了它会按公司数放大。
3. 改 salesperson 后,team 自动变化被当成 bug
很多时候这是 _compute_team_id() 在帮你把记录拉回合法销售组织。
4. 没有结果就去怀疑 IAP 服务坏了
也可能只是条件太细、州过滤过窄、或 people 模式把结果集压扁了。
九、排错顺序建议
如果 Lead Mining 的结果和预期不一致,我会按这个顺序查:
search_type是 companies 还是 people;lead_number/contact_number有没有被自动裁到上下限;country_ids对应国家是否允许 state filter;- 行业标签的
reveal_ids是否完整; user_id变化后 team 是否被重算;- 错误属于 credits、不命中结果,还是底层异常。
一句话记忆
Odoo CRM Lead Mining 真正管的不是“怎么搜公司”,而是“用什么预算、以什么组织归属、向 IAP 发一份不会误导业务的请求”。
DISCUSSION
评论区