其他深度

Odoo 活动售票为什么不能只看“有人下单了”:销售订单、参会人状态与 UTM 归因联动讲透

很多人以为 Odoo Event 接上 Sales 之后,只是多了一个卖票入口。但从 event_sale 模块里的 sale.order、sale.order.line 和 event.registration 代码看,真正关键的是“订单状态如何驱动参会人状态、票种与场次如何回写报名、UTM 如何继承到参会记录”。这条链看懂了,活动营收和报名运营才不会各算各的。

其他
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

Odoo 的活动售票,核心不是“把活动票放进销售订单里卖出去”,而是把三套原本容易断开的业务对象真正绑在一起:

  • sale.order / sale.order.line:负责交易
  • event.registration:负责参会人
  • event.event.ticket / event.slot:负责活动资源

/home/ubuntu/odoo-temp/addons/event_sale/models/ 这组源码来看,官方真正想解决的是:

一张订单成交以后,系统怎么知道哪些人可以算正式参会、他们对应哪种票、哪个场次、又该把流量来源算到哪里。

所以 event_sale 不是简单“售票插件”,而是一层把销售、运营、归因口径统一起来的联动层。

第一层:为什么报名状态不能脱离销售订单单独判断

event.registration 在接入 event_sale 后,最关键的变化之一就是 _compute_registration_status()

这段逻辑不是只看报名记录自己,而是会连带看:

  • sale_order_id.state
  • 订单总额是不是 0
  • 当前报名是否已被取消

然后把报名状态和销售状态拆成两层:

  • state:比如 draftopencancel
  • sale_status:比如 freeto_paysold

这非常重要。

因为活动场景里,“有人提交了报名意向”和“这个人是正式参会者”根本不是一回事。

举个很典型的例子:

  • 免费票:下单后可以直接算 open
  • 付费票但订单还没确认:报名仍可能只是 draft
  • 订单取消:报名也要同步变 cancel

也就是说,Odoo 不把参会资格只看成表单填写结果,而是看成交易状态的业务投影

第二层:为什么 sale order line 要强制带 event、ticket,必要时还要带 slot

sale_order_line.py 里,官方对 event 类服务有非常明确的约束:

  • service_tracking == 'event'
  • 就必须有 event_id
  • 必须有 event_ticket_id
  • 如果活动支持多时段,还必须有 event_slot_id

否则直接报错。

这说明在 Odoo 的设计里,活动票不是“普通服务商品换个名字”,而是一个带上下文的业务商品。

系统真正需要的不只是卖出去一条 line,而是确认:

  • 卖的是哪场活动
  • 卖的是哪种票
  • 卖的是哪个时间段

如果这几件事不在销售订单层就钉死,后面就会出现一串口径混乱:

  • 参会人数算不到具体场次
  • 同一张票卖给了错误活动
  • 报名人信息无法回到正确票种

所以这个约束看似严格,其实是在前面多做一次校验,换后面整条链路稳定。

第三层:为什么确认订单时不是立刻把所有报名人都当成最终有效

SaleOrder.action_confirm() 里会做两件很关键的事:

  1. 检查 event 类订单行是不是都已配置完整
  2. _init_registrations() 去补齐对应的报名记录

_init_registrations() 还有个很细的判断:

  • 如果是后台确认单笔订单,且这条 line 有金额
  • 新建出来的 registration 默认会先是 draft
  • 让后续仍有空间补完 attendee details

这个细节很像真实业务。

因为对付费活动来说,订单确认并不总是意味着报名资料已经完整。

很多公司流程是:

  • 商务先下单
  • 人数先锁住
  • 具体参会人姓名、邮箱、手机号后补

如果系统一确认订单就把所有参会人直接当作完全就绪,反而会让运营误判。Odoo 在这里明显是把“销售成交”和“报名资料齐全”拆开处理。

第四层:为什么 registration 会主动同步销售订单上的 UTM

event.registration 里新增了:

  • utm_campaign_id
  • utm_source_id
  • utm_medium_id

而且都是根据 sale_order_id 来计算同步。

这说明官方并不满足于“订单上有归因就行”,而是希望流量来源一路沉到参会人层。

这对活动运营很关键。

因为你真正经常会问的问题不是:

  • 哪个广告带来了多少订单?

而是:

  • 哪个渠道带来了多少真实参会人?
  • 哪个 campaign 带来的付费参会人转化更好?
  • 哪个 source 虽然有单,但最后取消率很高?

如果 UTM 只停留在销售订单,活动团队看参会名单时就断层了。把 UTM 带到 registration,报表口径才有机会统一。

第五层:为什么改票种或场次时,Odoo 会专门创建 warning activity

event.registration.write() 里还有个特别值得注意的动作:

如果你改了:

  • event_slot_id
  • event_ticket_id

系统会给对应销售订单安排一条 warning activity。

这说明官方默认认为:

报名数据一旦和销售订单挂钩,后续改票、改场次都不是“静默小改动”,而是需要被业务方看到的异常变更。

这非常合理。

因为改票种可能影响:

  • 价格
  • 座位/名额统计
  • 票面权益
  • 财务对账

改场次则可能影响:

  • 签到名单
  • 时间资源分配
  • 会务通知内容

与其假装这是普通字段修改,不如明确给销售或活动负责人一个提示:这里有业务变更,需要确认。

第六层:为什么 partner 同步发生在订单层,而不是只在报名页里自己维护

SaleOrder.write() 里有个常被忽略但很实用的逻辑:

只要订单里有 event 类 line,且订单 partner_id 变了,系统就会把相关 registration 的 partner_id 一起同步。

这背后的思路很清楚:

  • 销售订单是商业主体
  • registration 是活动主体
  • 两者不能长期脱节

尤其在网站下单或商务代客下单场景里,客户信息会在后续地址页、确认页继续被修正。

如果 registration 不跟着更新,就会出现:

  • 订单客户和参会联系人不是一套数据
  • CRM / 营销归因跟活动报名无法对上
  • 后续发票、联系人分析、客户旅程被拆开

第七层:为什么事件总销售额不是简单求和,而是按币种和已确认状态汇总

event_event.pysale_price_total 的计算也很说明问题。

它只统计:

  • 已确认的 sale.order.line
  • 非 0 金额
  • 且会做币种换算

换句话说,活动销售额并不是“所有 line 总价粗暴加一起”,而是一个带财务边界的运营指标。

这说明 Odoo 很清楚,活动团队需要看的销售额必须满足两个条件:

  1. 足够接近真实成交
  2. 可跨订单币种汇总

否则这个数字在国际公司、多币种公司里根本没法用。

最容易误解的三个点

误区一:有订单就等于参会已成立

不一定。 对免费票、付费票、取消单,registration 的业务状态完全不同。

误区二:UTM 只要挂在订单上就够了

不够。 活动团队最终分析的是参会人、签到人、取消人,不只是订单。

误区三:改票种和场次只是报名侧的小修小补

不是。 它会影响销售、资源、通知和统计口径,必须当成业务变更处理。

实施时怎么用最稳

如果你在做 Odoo 活动售票实施,我更建议这样理解和配置:

  1. 把 event 类商品看成“带业务约束的服务商品”,不要随便绕开 event/ticket/slot 绑定
  2. 把 registration state 和 sale_status 分开看,别拿一个字段解决所有判断
  3. 营销分析时优先下钻到 registration 级别,而不是只看 SO 报表
  4. 对票种、场次改动建立审批或提醒习惯,避免现场运营被静默改乱
  5. 网站售票、商务售票、免费报名要分别测试状态流转,不要只测 happy path

最后总结

Odoo Event 接上 Sales 之后,真正建立起来的不是“卖票能力”,而是:

  • 订单负责交易成立
  • registration 负责参会身份成立
  • ticket / slot 负责资源归属成立
  • UTM 负责营销归因成立

这四层一起打通,活动团队、销售团队和市场团队看到的才会是同一场活动,而不是三套互相解释不清的数据。

DISCUSSION

评论区

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