先说结论
在 Odoo 里,UTM 进入 CRM 不是“URL 参数直接写模型字段”。
真实链路分两步:
ir.http._set_utm()在请求结束时把utm_campaign / utm_source / utm_medium写进 cookie;- 继承了
utm.mixin的模型在default_get()时,再从 cookie 里把这些值回填到字段。
但这里还有一个很重要的边界:
- 销售用户默认跳过这套自动注入。
源码目的很明确:不要让内部销售自己在后台点来点去时,把前台归因污染掉。
一、为什么 Odoo 要先写 cookie,不直接即时落字段
因为很多场景下:
- 用户先访问站点页面;
- 过一会儿才提交表单;
- 或者经过几次页面跳转后才真正创建记录。
如果不先把 UTM 存起来,参数很容易在中途丢失。
所以 ir.http._set_utm() 会在请求后处理里检查 URL 参数,并把它们写成 odoo_utm_* cookie,保存约 31 天。
这让归因从“单次请求瞬时变量”变成“跨页面会话上下文”。
二、为什么 default_get() 才是模型真正取数的地方
utm.mixin.default_get() 会遍历 tracking fields:
utm_campaign -> campaign_idutm_source -> source_idutm_medium -> medium_id
如果字段在本次默认值请求里,它就尝试从 cookie 拿值;
如果 Many2one 对应的是字符串,还会 _find_or_create_record(),把对应的 UTM 维表记录建出来或找出来。
这说明 UTM 不只是“显示给前台看”,而是模型创建前的正式默认值来源。
三、为什么销售用户会被排除
default_get() 里有一段很关键的保护:
- 如果当前不是 superuser;
- 且当前用户属于销售组;
- 直接返回,不做自动 UTM 注入。
这背后的业务考虑很合理:
销售在后台打开记录、测试页面、自己点链接,不应该反过来改写客户来源。
否则很容易出现:
- 真实客户来源本来是广告;
- 销售后来打开了一个带 UTM 的内部链接;
- 新建记录时 UTM 被错误带成内部测试来源。
四、为什么“URL 带了参数,但 CRM 里没值”不一定是 bug
至少有四种正常可能:
- cookie 没成功写入;
- 创建记录时没有请求这些字段默认值;
- 当前会话是销售用户,被保护性跳过;
- 该模型没真正继承或使用到对应 tracking field。
所以不要一上来就怪罪表单模板。
五、最容易踩的误区
1. 以为 UTM 是即时从 URL 灌进 CRM
实际是 URL → cookie → default_get → 模型字段。
2. 以为任何用户都能触发自动回填
销售用户默认不会。
3. 以为没找到 campaign/source/medium 就会报错
不会,Many2one 字符串场景下会查找或创建。
六、排错顺序
UTM 没进 CRM 时,先查:
- 响应里有没有写
odoo_utm_*cookie; - 后续创建动作是不是同一浏览器上下文;
- 创建者是不是销售用户;
- 模型是否真的走了
default_get()并请求了这些字段。
一句话记忆
Odoo CRM 的 UTM 归因不是直接吃 URL,而是先记 cookie、再由模型默认值回填;销售用户默认被排除,是为了防止内部行为污染归因。
DISCUSSION
评论区