很多团队第一次看到 Odoo 企业版里的 Convert to Ticket / Convert to Lead,直觉都会很简单:
- 这不就是把一条业务记录换个类型吗?
- 最多把客户、描述、负责人带过去就好了吧?
但 enterprise/crm_helpdesk 的源码并不是这个思路。
它真正做的,是一套非常克制的 “新建目标对象 + 搬迁上下文 + 归档原对象 + 保留追溯关系” 机制。
换句话说,官方并不把这件事理解成:
把同一条记录从 A 表改成 B 表。
而是理解成:
确认当前业务已经不适合留在原漏斗,于是创建一个更合适的新对象承接后续流程,同时把历史沟通和来源信息安全地接过去。
这篇就专门把 crm_helpdesk 里最值得读的边界讲清楚。
一、先说结论:这不是“改类型”,而是“重建承接对象”
无论方向是哪边,crm_helpdesk 都不是原地改模型。
lead → ticket
wizard/crm_lead_convert2ticket.py 会:
- 基于 lead 组装新的
helpdesk.ticket值; sudo().create()新 ticket;- 给 ticket 写入来源消息;
- 把 lead 的消息线程挪到 ticket;
- 把 lead 附件改挂到 ticket;
- 在 lead 上留日志;
- 归档原 lead。
ticket → lead / opportunity
wizard/helpdesk_ticket_to_lead.py 会:
- 基于 ticket 组装新的
crm.lead值; - 按权限决定建的是 lead 还是 opportunity;
sudo().create()新商机对象;- 给新对象写来源消息;
- 把 ticket 的消息线程挪到 lead;
- 把 ticket 附件改挂到 lead;
- 归档原 ticket;
- 再回 ticket 上补一条“已转换”的消息。
所以最重要的一句话是:
Odoo 企业版的双向转换,本质是“新对象承接流程”,不是“旧对象变身”。
这会直接影响你对审计、二开和统计口径的理解。
二、为什么官方宁可“新建 + 归档”,也不做原地变形
这套设计看起来麻烦,其实非常合理。
因为 lead 和 helpdesk ticket 虽然都长得像“客户问题单”,但它们服务的是两条完全不同的业务链:
crm.lead面向销售资格判断、商机推进、团队分配;helpdesk.ticket面向服务受理、客服协作、工单处理。
如果强行让一条记录原地换模型,马上就会遇到一堆问题:
- 原模型字段怎么处理?
- 消息线程和附件算谁的历史?
- 旧报表到底要不要把它继续算进来?
- 权限与菜单上下文是否还成立?
而“新建 + 归档”的好处是很稳定的:
- 流程切换清晰:后续操作发生在新对象上;
- 历史仍可追溯:原对象没消失,只是归档;
- 消息链不断裂:讨论和附件被迁过去;
- 统计边界更干净:CRM 看 CRM,Helpdesk 看 Helpdesk。
官方实际上是在用结构设计告诉你:
转换不是字段编辑,而是业务归属迁移。
三、lead 转 ticket 时,哪些数据会被带过去
action_lead_to_helpdesk_ticket() 里准备 ticket 值的逻辑很克制,核心包括:
namedescriptionteam_idpartner_idcampaign_idmedium_idsource_idpartner_namepartner_phonepartner_email
这里最值得注意的是三层语义。
1)问题内容会继承
lead 的标题和描述直接进入 ticket,说明官方认为:
- 这条咨询本身没变;
- 变的是它现在更应该由服务台处理。
2)客户上下文会尽量补齐
如果 lead 已经有 partner_id,直接带过去。
如果没有,但存在 partner_name 或 contact_name,向导会先调用:
lead._handle_partner_assignment(create_missing=True)
也就是说,在需要落 helpdesk ticket 前,系统会优先把客户关系补成一个正式 partner,而不是让 ticket 只挂一堆散乱文本。
3)UTM 来源不会丢
campaign_id、medium_id、source_id 会原样延续到 ticket。
这点非常关键,因为它表明官方把 ticket 看成这条业务来源链的后续承接对象,而不是与营销来源完全断开的客服孤岛。
所以你后面追“这张工单原本从哪个活动/渠道来”,在转换后依然有据可查。
四、lead 转 ticket 时,客户信息并不是简单复制字段
这条链路里最容易被忽略的,是 partner_name / partner_phone / partner_email 的处理顺序。
源码逻辑大致是:
- 优先用 lead 上已经确定的
partner_id; - 若还没有 partner,但有联系人或客户名,就先触发 partner assignment 创建/匹配客户;
partner_phone优先取 lead.phone;- 没有 lead.phone 时,再退回 partner.phone;
partner_email优先取 lead.email_from。
这说明 ticket 侧拿到的不是“原字段原样复制”,而是经过一次 客户对象优先、散字段兜底 的整理。
所以 ticket 上最终保存的,是更适合客服处理的客户入口信息,而不是 CRM 那套半结构化线索文本的照抄。
五、为什么 lead 转 ticket 后,ticket 默认不继承负责人
这个设计非常有意思。
在新 ticket 的 vals 里,源码明确写的是:
user_id: None
也就是说,哪怕原 lead 上已经有销售负责人,转成 helpdesk ticket 后,官方也不会自动把销售员继续当成工单负责人。
这背后的业务判断很成熟:
- lead 的负责人,是销售跟进责任;
- ticket 的负责人,是服务处理责任。
两者并不天然等价。
如果系统默认沿用 lead 的 user_id,很容易把销售误变成客服受理人,造成队列污染。
所以官方宁可只保留 team_id,让工单进入正确 helpdesk 团队,再由服务流程重新分配。
一句话概括:
lead 转 ticket 时,团队上下文可以继承,但个人处理责任不会偷着继承。
六、ticket 转 lead / opportunity 时,哪些数据会被带过去
反方向的 action_convert_to_lead() 同样很克制,创建 crm.lead 时主要带过去:
namepartner_idteam_iduser_iddescriptionemail_cccampaign_idmedium_idsource_id- 如果没有 lead 权限,则额外设
type='opportunity'
这里有两个特别重要的点。
1)helpdesk ticket 进 CRM 时,官方保留的是销售可用上下文
比如:
- 客户是谁
- 这条需求来自哪个渠道
- 内容描述是什么
- 应该归哪个销售团队
- 谁来跟
这说明 ticket 转 CRM 的目标,不是保存工单一切细节,而是把它整理成 销售可继续推进的商业对象。
2)“转成 lead 还是 opportunity”由权限组决定,不是单纯 UI 文案差异
action_convert_ticket_to_lead_or_opportunity() 会根据用户是否拥有 crm.group_use_lead:
- 有:按钮显示 Convert to Lead
- 无:按钮显示 Convert to Opportunity
并且在真正创建时,没 lead 权限的用户会把 type 直接设为 opportunity。
所以这不是“文案不同但数据一样”,而是 CRM 流程粒度真的不同。
七、ticket 转 lead 时,客户匹配规则比很多人想得更保守
models/helpdesk_ticket.py 里的 _find_matching_partner() 很值得读。
它大致按这个顺序找客户:
- 如果 ticket 已有
partner_id,直接用它; - 否则若有
email_cc,按标准化后的邮箱匹配res.partner.email_normalized; - 否则若有
partner_phone,按电话找 partner; - 如果还是没有,且要求
force_create=True,才创建新客户; - 找到客户后,可按需要把缺失的 email / phone 回填到 partner。
也就是说,官方态度不是“只要转换就一定造一个客户”。
而是:
- 先认已有客户;
- 再按邮箱/电话谨慎匹配;
- 只有明确需要时才创建。
这种保守做法非常重要,因为服务台最容易产生:
- 一次性邮箱来件
- 抄送邮箱
- 非标准电话
- 名称写法不稳定
如果这里太激进,CRM 会被迅速污染出大量重复客户。
八、消息线程为什么要整体迁移,而不是只写一条来源备注
双向转换里最值钱的设计,其实不是字段搬运,而是:
message_change_thread()
无论 lead → ticket 还是 ticket → lead,源码都会把消息线程整体迁过去。
测试也专门验证了:
- 原对象上发过的评论消息,转换后应该出现在新对象的
message_ids中。
这件事意义非常大。
因为业务转换时,真正不能丢的常常不是标题和客户,而是:
- 客户到底说过什么;
- 团队内部讨论过什么;
- 哪一步已经答复;
- 哪些附件、邮件、评论构成上下文。
如果只新建目标对象再写一句“来源于 XXX”,用户还得来回翻旧对象找上下文,协作就断了。
所以 Odoo 这里做的是更强的承诺:
不是只保留来源引用,而是把讨论上下文一起带去。
这也是这套功能最有企业价值的地方之一。
九、附件为什么也要跟着改挂
源码还会专门搜索:
ir.attachment上res_model、res_id指向原对象的附件;- 然后把它们统一改挂到新对象。
这说明官方非常清楚:
- 转换不是“复制一份附件引用”;
- 而是要让目标对象真正成为后续工作的主容器。
否则你会遇到很麻烦的情况:
- 新 ticket / lead 打开后看不到历史附件;
- 团队以为资料丢了;
- 还得回旧对象翻证据。
把附件改挂过去后,新的承接对象才能真正独立工作。
而原对象归档,就更像一张“历史转出凭证”。
十、归档不是删除,而是把“当前主战场”切走
双向转换最后都会把原对象归档:
- lead 被转成 ticket 后,lead
active=False - ticket 被转成 lead/opportunity 后,ticket
active=False
这一步特别容易被误解成“旧对象没用了”。
其实不是。
归档的真实含义是:
- 它退出当前主流程;
- 但历史仍然保留;
- 统计和默认列表也不会继续把它当成活跃待办。
这是一种很成熟的运营边界:
- 业务焦点移交给新对象;
- 历史审计仍然存在;
- 但团队不会因为两个活跃对象并存而重复处理同一件事。
十一、team 与 user 的处理,体现了“团队先于个人”的设计顺序
helpdesk.ticket.to.lead 向导里还有两个很关键的 compute:
_compute_team_id()_compute_user_id()
你会发现官方在 ticket → lead 时强调的是:
- 先定销售团队;
- 再看 ticket 原负责人是否属于该销售团队;
- 如果不属于,再决定是否给默认销售员。
而且当规则分配启用时,系统不会草率替你塞默认 user_id。
这说明在 CRM 侧,官方优先保证的是:
- 记录先进入正确团队池;
- 再由团队规则或负责人机制承接;
- 而不是把 helpdesk 当前处理人直接硬塞成销售。
这和前面 lead → ticket 时“不要偷继承负责人”是同一种哲学。
跨流程迁移时,团队上下文比个人归属更稳定。
十二、这套转换机制最容易踩的 5 个误区
误区 1:以为转换后原对象会继续正常活跃
不会。官方默认就是归档原对象,避免双边并行处理。
误区 2:以为负责人会自动无缝继承
不会。尤其 lead → ticket 时,源码刻意把 user_id 置空。
误区 3:以为只迁字段,不迁讨论历史
不是。消息线程和附件都是迁移重点。
误区 4:以为 ticket 转 lead 一定创建新客户
不是。先匹配已有 partner,必要时才建。
误区 5:以为 UTM 到了客服就没意义
恰恰相反。campaign / medium / source 双向都能保留,说明官方认可来源链在客服与销售之间持续有效。
十三、对实施和二开的真正启发
1)不要把这类需求做成“直接改模型字段”
官方方案已经说明,真正稳的是新建承接对象,而不是把原对象结构打穿。
2)如果你要自定义转换,优先守住三样东西
- 消息线程
- 附件
- 归档边界
这三样比多搬几个普通字段更重要。
3)负责人自动继承一定要谨慎
销售负责人 ≠ 客服负责人。强行继承往往比手动分配更危险。
4)来源字段值得保留
哪怕业务从销售转到客服,或从客服回到销售,UTM 仍然是跨漏斗分析的重要线索。
5)把“转换”理解成流程重新归属,而不是数据重写
一旦这样理解,你的报表、权限、自动化和审计设计都会更稳。
一句话记忆法
Odoo 企业版 crm_helpdesk 的 lead/ticket 双向转换,本质不是改类型,而是新建承接对象、迁移消息与附件、保留来源与客户上下文,并归档原对象。
参考源码
enterprise/crm_helpdesk/models/helpdesk_ticket.pyenterprise/crm_helpdesk/wizard/helpdesk_ticket_to_lead.pyenterprise/crm_helpdesk/wizard/crm_lead_convert2ticket.pyenterprise/crm_helpdesk/tests/test_crm_helpdesk_convert_lead.pyenterprise/crm_helpdesk/tests/test_crm_helpdesk_convert_ticket.pyenterprise/crm_helpdesk/views/crm_lead_views.xmlenterprise/crm_helpdesk/wizard/helpdesk_ticket_to_lead_views.xmlenterprise/crm_helpdesk/wizard/crm_lead_convert2ticket_views.xml
DISCUSSION
评论区