先说结论
在 Odoo 的渠道伙伴场景里,“转派线索给 partner”绝不是发一封邮件那么简单。
website_crm_partner_assign 的 action_forward() 至少同时做了四件事:
- 按合作伙伴把多条 leads 聚合成一批;
- 根据 partner 是否已在 portal 里,给模板不同上下文;
- 把 lead 的
partner_assigned_id和user_id一起改写; - 自动把合作伙伴订阅到线索消息流里。
所以这个 wizard 真正解决的是交接闭环,不是通知动作本身。
一、为什么先按 partner 聚合,而不是一条线索发一封邮件
action_forward() 会先把 assignation_lines 重组为 partners_leads,也就是:
- 同一个 partner 负责的多条 lead,被并成一组;
- 模板一次发给这个 partner;
- 邮件上下文里放入该 partner 对应的多条 leads 链接。
这一步非常体现业务思维。
因为渠道伙伴接收转派时,关心的往往不是“系统给我发了几封邮件”,而是:
- 这次有多少条机会给到我;
- 我能从哪里统一进去看;
- 这些机会现在是不是都已经真正分到我名下。
如果按 lead 一条条散发,协同成本会很高,还容易出现同一 partner 收到一堆碎片通知却无法形成批量接收心智。
二、为什么要区分 partner 是否已经在 portal
源码会检查:
- 这个 partner 的联系人里是否有
user_ids; - 这些用户是否属于
base.group_portal。
然后把 partner_in_portal 放进模板上下文。
这说明 Odoo 很清楚,渠道伙伴不是同质对象:
- 有些伙伴已经在 portal 里,可以直接点链接进入;
- 有些伙伴还没有 portal 身份,需要先把信息作为邮件告知。
也就是说,forward wizard 处理的不只是“谁来接 lead”,还包括对方接单能力处在哪个数字化成熟度阶段。
这比很多系统“默认外部伙伴都能点系统链接”更现实。
三、为什么转派后要同时改 partner_assigned_id 和 user_id
这是整个流程里最不能省的一步。
源码在发信后,会对相应 leads 执行:
partner_assigned_id = partner_iduser_id = partner.user_id.id
很多团队只理解第一步,觉得“既然已经指定合作伙伴,系统知道是谁接就够了”。
其实不够。
partner_assigned_id
表达的是:
- 这条线索对外是转给哪家合作伙伴;
- 报表和关系归属如何统计;
- 渠道伙伴维度的机会量怎么算。
user_id
表达的是:
- 这条线索在 Odoo 里现在由哪个具体用户跟进;
- 后续活动、提醒、个人待办、过滤器怎么生效。
如果只改 partner 不改 user,会出现“外部归属对了,内部执行人还是旧负责人”的半交接状态。Odoo 显然不接受这种模糊状态,所以源码把关系归属和执行归属一起切过去。
四、为什么自动订阅 partner 不是可有可无的小动作
forward 完成后,源码会:
self.env['crm.lead'].message_subscribe([partner_id])
这一步非常关键,因为转派不是一次性瞬时事件,而是后续协同的起点。
如果不订阅,会怎样?
- 初始通知发出去了;
- 但后续 lead 上的消息、备注、状态变化,partner 不一定持续收到;
- 结果就是“交接发生了,但协同链很快断掉”。
所以自动订阅并不是锦上添花,而是把渠道交接从“单次发送”变成“持续参与”的关键机制。
五、为什么 get_lead_portal_url() 用的是 /my/<type>/<id> 而不是后台链接
wizard 里构造的 lead link 是:
lead.get_base_url()- 拼
/my/<lead.type>/<lead.id>
也就是说,它优先发的是 portal 视角下的链接,而不是后台 form view。
这背后的产品假设很明确:
- 渠道伙伴首先是外部协作者;
- 不是所有人都该直接进入内部后台;
- 把链接建在 portal 语义上,更符合 partner 的最小权限原则。
这也是为什么“partner 是否已在 portal”要提前算出来——链接本身和对方的可见性能力是一组设计,不是互相独立的两个细节。
六、为什么机会数量统计要同时看 partner_assigned_id 和 partner_id
res.partner 那边的 _get_contact_opportunities_domain() 与 _compute_opportunity_count() 也很值得一起看。
它不是只统计:
- 当前 partner 直接作为
partner_assigned_id的机会;
还会把:
- partner 层级上的
partner_id机会; - 子联系人 / 父级公司链路;
一起纳入视角。
这说明 Odoo 在渠道伙伴场景里,不希望把机会看成“孤立地挂在某个 contact 上”,而是更偏向伙伴层级视图。
这和 wizard 里的批量转派思路是一致的:
- 外部协同对象通常是一家公司,不只是一个单联系人;
- 统计和交接都应尽量保持在伙伴组织层级上。
七、实施里最容易误判的几个点
1. 以为 forward 就是发通知邮件
实际上还伴随负责人改写和消息订阅。
2. 以为 partner_assigned_id 改了就代表交接完成
不对,user_id 也得一起切过去,执行层才真正变更。
3. 以为 portal 只是一个展示界面
不对,链接构造、模板上下文和外部协同能力都围绕它展开。
4. 以为合作伙伴后续消息天然会收到
如果不订阅,很多跟进动作并不会持续进到对方视野里。
八、排错顺序建议
渠道伙伴转派后效果不对时,我建议按这个顺序查:
assignation_lines是否正确分配到 partner;- 目标 partner 是否有邮箱;
- partner 是否已有 portal 用户;
- 转派后
partner_assigned_id与user_id是否都已更新; - partner 是否已被自动订阅 lead 消息流;
- portal 链接
/my/<type>/<id>是否对目标伙伴可访问。
一句话记忆
Odoo CRM 的渠道伙伴转派不是“发出去一封信”,而是把 portal 可见性、负责人归属和后续消息协同一起切换过去。
DISCUSSION
评论区