先说结论
Odoo 的活动报名不是一张“报名表”,而是一条把售卖、报名、签到、场次容量和参会人信息连起来的业务链。
从 /home/ubuntu/odoo-temp/addons/event/models/event_registration.py 与 addons/event_sale/models/sale_order.py 来看,event.registration 至少承担了五件事:
- 绑定具体活动
event_id - 在多场次活动里绑定
event_slot_id - 在售票场景下绑定
event_ticket_id - 同步联系人姓名、邮箱、电话、公司信息
- 用状态与条码支撑签到和席位控制
所以它真正解决的问题不是“有人来报名”,而是:
每一个报名人,到底报了哪个活动、哪个场次、哪种票、占不占座位、现场怎么识别、后续如何统计。
第一层:报名记录为什么必须挂在活动、场次和票种上
event.registration 里最关键的三组字段就是:
event_idevent_slot_idevent_ticket_id
这说明 Odoo 从一开始就不把“报名”理解成一个泛联系人清单,而是理解成:
- 某人报名了某个活动
- 如果活动分多个场次,还要知道他具体去哪一场
- 如果活动分票档,还要知道他买的是哪种票
这三个维度一旦没分清,后面的运营动作都会变得含糊:
- 剩余席位算不准
- 不知道哪一场超卖
- 统计营收时票档无法拆分
- 现场签到时容易把不同场次的人混在一起
源码里 _check_event_slot 和 _check_event_ticket 也正是在守这个边界:
- 场次必须属于当前活动
- 票种也必须属于当前活动
- 多场次活动里,场次不能空着
这不是“字段校验很严格”,而是在防止业务对象串台。
第二层:为什么报名信息会自动继承联系人资料
很多实施项目里都会遇到一个问题:
- 报名时的联系人已经存在于通讯录
- 但活动报名又想记录参会人的姓名、邮箱、电话、公司
- 如果全都人工再填一遍,体验会很差
Odoo 在 event.registration 里用一套 _compute_name、_compute_email、_compute_phone、_compute_company_name 来处理这个问题。
核心思路不是简单地“复制 partner 字段”,而是通过 _synchronize_partner_values() 去找更贴近联系人的地址对象,尽量拿到适合活动沟通的值。
这背后的产品判断很实用:
- 联系人主数据负责长期身份
- 报名记录负责这次参会上下文
所以报名记录会参考联系人,但又不完全等于联系人本身。
这就是为什么活动运营里常见的这些场景能成立:
- 公司联系人统一采购门票
- 实际参会人资料再按报名记录细化
- 后续邮件通知仍然能基于报名记录发出
第三层:为什么系统还要给每个报名生成 barcode
barcode 字段默认来自 _get_random_barcode(),而且源码注释写得很清楚:
- 它不是随便生成一个字符串
- 还考虑了条码序列化后在扫描器里的兼容性
这意味着 Odoo 的报名模型默认就考虑到了现场签到。
也就是说,报名不只是后台台账,还面向现场执行:
- 提前邮件发送凭证
- 现场扫码确认到场
- 避免只靠姓名搜索导致重名和误判
很多人会低估这件事,以为活动系统只要把名单导出来就行。 但从源码角度看,官方显然希望你把活动当成一条完整闭环:
- 报名前台采集
- 中台分场次与票种
- 现场条码识别
- 报名状态回写
这比“Excel 名单 + 手工勾选”高一个层级。
第四层:席位校验为什么要在报名对象上做
_check_seats_availability() 很值得看。
它会筛出:
- 状态为
open或done - 且仍然 active 的报名
然后按活动去聚合当前报名占用,再调用活动侧的 _verify_seats_availability()。
这表示 Odoo 的席位逻辑不是只在“卖票时”检查一次,而是把报名对象本身当作席位占用的真实来源。
这样设计有几个好处:
1. 已签到和已注册都算席位占用
只要状态是有效参会状态,就应当占容量。
2. 取消或归档的报名可以被排除
活动主办方临时释放席位时,逻辑上更清楚。
3. 多场次 + 多票档可以一起核算
因为聚合键里已经考虑了 slot 和 ticket 的组合。
这也是为什么活动扩容、补票、人工调报名时,实施顾问不能只盯前台售票页面,更要看报名记录状态有没有被正确维护。
第五层:销售订单为什么会反向影响报名记录
在 event_sale/models/sale_order.py 里,sale.order.write() 有一个很有代表性的动作:
- 如果订单里存在活动类行
- 且订单联系人变了
- 系统会去把相关
event.registration的partner_id一起更新
这说明在 Odoo 看来,活动报名和销售订单不是松耦合关系,而是同一笔业务的两个视角:
- 订单负责交易
- 报名负责参会执行
此外,action_confirm() 里还会做两件关键事:
- 校验活动相关销售行是否都配置了事件
- 调用
_init_registrations()正式生成报名记录
换句话说:
买票不是报名的旁路,而是报名生成链路的一部分。
这对做收费活动非常关键,因为它保证了“卖了几张票”和“生成了几个有效参会名额”之间有明确对应关系。
最容易误解的三个点
误区一:把报名当静态联系人表
如果把 event.registration 当成一次性联系人列表,你会忽略:
- 状态
- 场次
- 票种
- 席位
- 条码
最后现场执行和统计都会出问题。
误区二:觉得联系人信息改了,报名自然全都对
报名会参考联系人,但它是活动语境下的快照对象。 如果历史报名、代报名或企业团购很多,不能指望“改一处 partner 就永远正确”。
误区三:只在售票环节看容量
真正决定容量是否被占用的,是报名记录及其状态,而不只是电商下单动作。
实战上该怎么理解这张表
可以把 event.registration 理解成活动业务里的“执行单”。
它向前接:
- 官网报名
- 销售订单
- 票种与价格
它向后接:
- 签到
- 到场状态
- 报表分析
- 邮件通知
因此做活动项目时,最稳的做法不是只盯前台报名表,而是顺着这条链检查:
- 活动是否配置了正确场次和票种
- 销售行是否能正确生成报名
- 报名是否同步到合适联系人
- 席位是否按有效状态正确占用
- 条码和签到流程是否能闭环
最后一句
Odoo 活动报名的价值,不在于它帮你“收集名单”,而在于它把一场活动拆成了可执行、可控制、可统计的对象。
当你把 event.registration 看成这条链的中心节点,很多设计就不再奇怪了:
- 为什么要有场次
- 为什么要有票种
- 为什么要校验席位
- 为什么要有条码
- 为什么订单和报名要互相联动
因为活动管理真正难的,从来不是“有人报名”,而是报名之后怎么不乱。
DISCUSSION
评论区