先说结论
Odoo Event 里的签到,不是“把报名记录打个勾”这么简单。
从 /home/ubuntu/odoo-temp/addons/event/models/event_registration.py 来看,event.registration 同时承担了五件事:
- 为每个参会人生成可扫描的唯一条码
- 维护报名、到场、取消这些状态
- 校验报名属于哪个活动、哪个场次、哪种票
- 在真正到场时自动写入
date_closed - 在扫码不匹配时给出人工确认兜底路径
所以它真正解决的问题不是“有人来了没有”,而是:
现场签到时,系统如何在尽量自动化的前提下,既防止误签到,又给运营人员留下足够的人工处理空间。
第一层:为什么 Odoo 要给报名记录单独发条码
event.registration 里有一个很醒目的字段:barcode。
它不是依赖邮件地址,也不是拿报名 ID 直接凑,而是通过 _get_random_barcode() 生成一串伪随机 8 字节数字。
源码注释写得很直白:
- 用十进制字符串虽然更长
- 但能生成更紧凑的条码表示形式
- 8 字节长度也更兼容常见扫码设备
这背后的产品判断很务实。
活动现场最怕的不是“能不能标记到场”,而是:
- 不同设备扫不扫得出来
- 网络抖动时是不是还能快速识别
- 同名参会人会不会被误认
- 工作人员能不能不用输入一长串复杂字符
所以 Odoo 没把签到标识绑死在邮箱、手机号、订单号这种业务字段上,而是专门设计了一个对扫码场景友好的独立识别码。
这说明官方把签到看成一个硬件参与度很高的场景,而不是纯后台操作。
第二层:为什么签到状态机要分成 draft、open、done、cancel
state 不是简单的布尔值,而是:
draft:未确认open:已报名done:已到场cancel:已取消
这套状态设计很关键,因为活动报名在现实里并不只有“来 / 没来”两种结果。
例如:
- 销售单还没付款完成,报名可能先处于待确认
- 报名成功了,但活动当天还没到场
- 已取消的人不应该再被普通扫码放进去
- 已签到的人再次扫码,要能提示“已经签到过了”
如果只用一个 attended = true/false,这些现场动作会全部挤在一起,工作人员只能靠经验判断。
而 Odoo 的状态机把真正要表达的业务区别拆开了:
- 能不能参加
- 是不是已经来过
- 是否需要再次确认
这样扫码动作才有了明确语义。
第三层:扫码为什么不是扫到就直接算成功
register_attendee(barcode, event_id) 是整条链里最值得看的方法之一。
它的逻辑大致是:
- 先按条码找报名记录
- 找不到,返回
invalid_ticket - 如果报名已取消,返回
canceled_registration - 如果还在
draft,返回unconfirmed_registration - 如果活动已经结束,返回
not_ongoing_event - 如果还没到场:
- 若扫码端传了
event_id,且与报名所属活动不一致,返回need_manual_confirmation- 否则直接action_set_done(),返回confirmed_registration - 如果本来就是
done,返回already_registered
请注意这里最重要的一点:
Odoo 并不是“扫到条码就一定通过”,而是先把扫码结果转成一组可解释的业务状态。
这很成熟。
因为现实活动里,经常会出现:
- 同一组织多场活动同时进行
- 志愿者扫错入口
- 老邮件里的条码被拿来重复用
- 取消报名的人拿着旧码到场
- 已经签到过的人再次尝试入场
如果系统没有先返回业务状态,而是直接写 done,现场就会很难控制风险。
第四层:为什么 event_id 不匹配时不是直接拒绝,而是人工确认
register_attendee() 里对 event_id 不一致的处理尤其值得琢磨。
它没有直接报废这次签到,而是返回 need_manual_confirmation。
这说明 Odoo 默认承认一种现实:
- 扫码终端所在活动,和报名记录上的活动,不一定百分百一致
- 但这不代表条码一定是假的
- 很可能只是入口、设备或工作人员选错了上下文
如果系统一刀切拒绝,现场会非常僵。
但如果不做拦截,又会把错误签到放进数据库。
Odoo 在这里选了中间路线:
- 机器先发现疑点
- 不自动放行
- 交给工作人员做最后判断
这就是很典型的“自动化 + 人工兜底”设计。
在现场系统里,这比“全自动”更可靠。
第五层:为什么到场时间不是单独填,而是由状态驱动
date_closed 的计算方式也很说明问题。
_compute_date_closed() 会根据 state 判断:
- 如果状态变成
done,且还没有date_closed - 就写当前时间
- 如果不是
done,则清空
这意味着 Odoo 把“到场时间”理解为签到状态的结果,而不是一个独立输入项。
这么做有两个好处:
1. 避免现场重复输入
工作人员只需要完成“确认此人已到场”这个动作,不必再额外填一遍时间。
2. 让报表语义更稳定
后续做签到率、到场时间分布、活动热度统计时,date_closed 代表的是明确的状态转移时刻,而不是一堆手工录入值。
换句话说,Odoo 想保存的不是“某人声称几点到”,而是“系统何时确认他已到场”。
第六层:为什么场次和票种校验要放在报名对象上
event.registration 里不仅挂了 event_id,还可能挂:
event_slot_idevent_ticket_id
并且有明确约束:
- 场次必须属于同一个活动
- 票种必须属于同一个活动
- 多场次活动下,场次是必选项
这代表 Odoo 不把签到对象仅仅理解成“一个活动的一位参会者”,而是进一步细化成:
这位参会者,以哪种票、参加哪一个场次,进入了这次活动。
这在多场次活动里特别重要。
因为实际现场里经常会有:
- 同一活动上午下午分场
- 不同票种开放不同区域
- 同一活动有 workshop slot 和主会场 slot
如果报名对象没有把这些上下文收进去,签到系统就只能做到“这个人来过”,却无法知道“这个人来的是哪一段流程”。
第七层:为什么报名信息还会从联系人自动同步
name、email、phone、company_name 这些字段,都可以从 partner_id 计算与同步。
这表面看像小细节,其实很重要。
因为活动报名记录是一个介于“主数据”和“现场快照”之间的对象:
res.partner代表长期联系人event.registration代表这次活动下的执行记录
这样设计的好处是:
- 后台能复用联系人资料,减少重复输入
- 现场名单仍然能保留当次报名时的展示信息
- 即使联系人后续发生变更,活动记录仍有自己的上下文
所以 event.registration 不是纯 join table,而是一张真正可执行、可展示、可追踪的活动工单。
最容易误解的四个点
误区一:签到只是把状态改成 done
不对。它还牵涉条码识别、活动上下文校验、重复签到识别和人工确认兜底。
误区二:条码只是打印方便
也不对。条码是整条签到链的独立身份标识,目的是降低现场识别成本和误判率。
误区三:活动不匹配时应当直接报错
源码没有这么做,而是给了 need_manual_confirmation,说明现场管理需要弹性。
误区四:到场时间是一个普通表单字段
其实它是状态驱动的结果字段,真正代表系统确认到场的时刻。
实战上怎么把签到链路配得更稳
如果你在实施 Odoo Event,我建议按下面的顺序理解这套机制:
- 先把报名状态流转梳理清楚,明确什么情况下会留在
draft - 现场签到尽量用条码,不要回退到人工模糊搜索
- 多活动、多入口环境下,把扫码端上下文选对
- 给工作人员培训几类返回状态的处理办法,而不是只教“扫一下”
- 多场次活动务必把 slot 和 ticket 语义讲清,不要把所有参会人混成一个池
最后总结
Odoo Event 的签到链路,最值得学习的地方不是“会自动把人标记成已到场”,而是它非常清楚地知道现场会发生什么混乱:
- 有假码和旧码
- 有重复扫码
- 有错活动、错入口
- 有需要人工拍板的灰区
所以它把条码、状态机、活动上下文校验和人工确认兜底收成了一条完整链路。
这也是为什么真正把活动签到跑稳,关键从来不是扫码枪本身,而是:
系统能不能把“扫到了什么”翻译成“现场该怎么处理”。
DISCUSSION
评论区