企业版 Frontdesk

Odoo 企业版 Frontdesk 为什么不是“访客填个表单”而已:kiosk token、预约访客窗口与多通道通知边界讲透

很多人以为 Odoo 企业版 Frontdesk 就是一个访客签到页。翻 `frontdesk` 源码会发现,官方真正设计的是一套“公开 kiosk 入口 + 临时移动二维码 + 预约访客快签 + 负责人/接待人/饮品责任人分层通知”的前台协调机制,还额外处理了 host 不在线、跨公司 public user 访问和来访后勤协作。

企业 协同办公
进阶 开发者 4 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

很多人第一次看到 Odoo 企业版 Frontdesk,脑子里的理解都很朴素:

  • 前台放一个二维码;
  • 访客填姓名、电话、拜访谁;
  • 系统给员工发个提醒;
  • 完事。

这个理解不能说错,但太低估官方了。

如果你把 /home/ubuntu/odoo-temp/enterprise/frontdesk 里的 models/frontdesk_frontdesk.pymodels/frontdesk_visitor.pycontrollers/main.py 连起来看,会发现它真正要解决的不是“怎么做个签到页面”,而是下面这些更现实的问题:

  1. 公开 kiosk 怎么放出来,又不至于任何人随便伪造入口?
  2. 大屏二维码和访客手机自助签到,为什么不能共用一个永不过期的链接?
  3. 预约访客为什么不是简单 list,而是只暴露一个时间窗口内的 planned visitors?
  4. 通知为什么要拆成 frontdesk 负责人、host、饮品责任人三层,而不是统一发一条消息?
  5. host 没有系统账号、人在休假、或者访客还要点饮品时,链路怎么兜底?

所以 Frontdesk 的本质不是“访客表单”,而是“把公开签到入口、接待协作和内部通知责任拆清楚的前台编排层”。

这也是它比很多“扫码登记小工具”更像企业系统的原因。


一、先说结论:Frontdesk 不是单个页面,而是三层对象协作

从模型看,核心至少有三层:

  • frontdesk.frontdesk:前台站点/签到台配置;
  • frontdesk.visitor:一次具体来访记录;
  • frontdesk.drink:可选饮品与饮品责任人。

再加上控制器里的公开路由,整个系统其实分成两条世界:

外层:公开 kiosk 世界

这里解决的是:

  • 访客能不能进入签到页;
  • 能看到哪些 host;
  • 能不能用手机接管签到;
  • 预约访客能不能快速点选自己。

内层:员工协作世界

这里解决的是:

  • 谁该被通知;
  • 通过 discuss、邮件还是短信通知;
  • host 不在线时是否有替代通道;
  • 饮品等后勤事项怎么分发。

中间:visitor 记录作为衔接对象

Odoo 没把签到理解成“提交一个网页表单”。

它真正落地的是一条 visitor 业务对象生命周期:

  • planned
  • checked_in
  • checked_out
  • canceled

也就是说,页面只是入口,visitor 记录才是真正的业务事实。


二、公开 kiosk 不是裸开放,而是 access token + 临时 token 双层校验

frontdesk.frontdesk 上有一个 access_token,默认就是 UUID。

控制器里 /kiosk/<frontdesk_id>/<token>/kiosk/<frontdesk_id>/mobile/<token> 这两条公开路由,都会先走 _verify_token()

它并不是只接受一个固定 token,而是支持两类令牌:

  • 固定 kiosk token:来自 frontdesk.access_token
  • 临时 mobile token:由 _get_tmp_code() 生成的 HMAC + 当前时间拼接值。

这就很有意思。

为什么要双 token?

因为大屏 kiosk 和手机自助签到不是同一个安全场景。

固定 kiosk token

适合:

  • 前台固定平板;
  • 墙上长期二维码;
  • 内网接待机。

它强调的是“站点身份稳定”。

临时 mobile token

适合:

  • 访客从大屏扫二维码切到自己手机;
  • 二维码短时间有效;
  • 避免截图长期传播后仍能反复打开。

控制器对临时 token 的处理是:

  • 验证 HMAC 是否匹配;
  • 验证时间戳距当前是否不超过 1 小时。

所以官方不是简单把 kiosk 链接复制到手机,而是专门加了一层“可过期的移动入口”。

这说明它把移动签到看成“公开入口的二次授权”,不是同一个安全平面。


三、为什么 request.session.logout(keep_db=True) 很关键

_get_additional_info() 里第一句就是:

request.session.logout(keep_db=True)

这行很容易被忽略,但它很企业级。

因为 kiosk 是公开入口:

  • 可能跑在共享平板上;
  • 上一个人可能刚登录过后台;
  • 浏览器可能残留内部会话。

如果不先 logout,公开签到页就可能带着内部身份上下文继续渲染,严重时会把权限和数据边界搞混。

所以 Odoo 先做的不是“渲染页面”,而是:

把当前 HTTP 会话先降回公共态,再允许进入前台界面。

这类细节,往往才是真正区分“企业模块”和“演示功能”的地方。


四、为什么预约访客只暴露前后 45 分钟窗口,而不是全量 planned

frontdesk_frontdesk.py 里定义了:

  • PLANNED_VISITOR_TIME = 45

_get_planned_visitors() 会只读取:

  • 当前时间前 45 分钟;
  • 当前时间后 45 分钟;
  • 状态是 planned
  • 且属于当前 station。

这不是一个随手写的常量,而是很明确的业务判断。

如果把所有 planned visitor 全放出来,会怎样?

会立刻出现三个问题:

  1. 隐私暴露过多:路过 kiosk 的人能看到更晚的来访计划;
  2. 误签到风险变高:访客可能误点到当天稍后甚至更晚的预约;
  3. 前台界面噪音太大:大量历史/未来预约会冲掉真正临近的来访对象。

所以官方的思路不是“让预约数据尽量全”,而是:

只把“当前前台真正可能发生”的那一小段预约窗口暴露给公开端。

这其实是把 kiosk 当成“现场接待界面”,而不是“访客数据库查询器”。


五、host 选择为什么受公司、联系方式和白名单三重约束

公开端取 host 时,不是直接把 hr.employee 全表扔给访客。

hosts_infos()get_departments() 都做了明显限制:

  • 只能看当前 frontdesk 所属 company_id
  • host 必须至少有 work_emailwork_phone
  • 如果 frontdesk 配了 host_ids 白名单,就只能从白名单里挑;
  • 搜索结果还会分页返回,并只给前端必要字段。

这说明官方在这里想回答的不是“访客能不能搜员工”,而是:

前台界面里哪些人应该被当作可接待对象公开出来。

为什么没联系方式的员工要被过滤掉?

因为访客选完 host 以后,系统马上要进入通知链路。

如果一个 host:

  • 既没有 user;
  • 也没有邮箱;
  • 也没有工作电话;

那他在 Frontdesk 里就几乎不可达。

把这种人展示给访客,只会制造“能选但通知不到”的伪能力。

所以 Odoo 干脆在入口层就收窄候选集合。


六、visitor 创建不是纯建单,而是默认就把状态推进到 checked_in

控制器 prepare_visitor_data() 有两条路径:

路径 1:已有预约 visitor

如果传了 visitor_id,系统会:

  • 直接把该 visitor 写成 checked_in
  • 如果传了 drink_ids,先补饮品,再通知饮品责任人。

路径 2:现场新建 visitor

如果没有 visitor_id,系统会:

  • 新建 frontdesk.visitor
  • 带上 station、name、phone、email、company、host_ids;
  • 直接写 state='checked_in'
  • 同时把 check_in 设为当前时间。

这很说明问题:

公开端不是“申请来访”,而是“确认已到场”。

所以 visitor 在 kiosk 提交之后,业务状态不是 planned,而是立刻进入现场态。

这也解释了为什么通知在这里立刻发生,而不是等人工审批。


七、通知为什么要拆成三类角色,而不是群发

frontdesk.visitor._notify()_notify_to_people() 把接收方拆得很细:

  1. frontdesk 负责人 responsible_ids
  2. 来访 host host_ids
  3. 饮品责任人 drink.notify_user_ids

这个拆分很重要。

1)frontdesk 负责人

他们收到的消息语义是:

  • 某站点有访客到了;
  • 访客是谁;
  • 要见谁。

这是“前台运营视角”的通知。

2)host

他们收到的是“你的访客到了”。

这是“被拜访人视角”的通知。

3)饮品责任人

只有访客选了 drink 时才会触发。

这是“后勤服务视角”的通知。

所以 Frontdesk 不是一条签到消息广播给所有人,而是把一次来访拆成接待、会见、后勤三类责任。

这比“前台群里发一句某某到了”成熟得多。


八、为什么 discuss / 邮件 / 短信不是互斥,而是带兜底逻辑的组合

这块是源码里最值得读的地方之一。

很多产品会把通知通道做成一个 radio:

  • 选 discuss;
  • 或选 email;
  • 或选 SMS。

但 Frontdesk 不是这么干的。

对有系统账号的 host

如果 host 对应 user_id,并且开启 notify_discuss,会优先走 discuss 私聊。

对没有系统账号的 host

如果 host 没有 user:

  • 没法私聊;
  • 但仍可能有 work_email
  • 也可能有 work_phone

于是源码就做了一个很细的 fallback:

  • notify_discuss=True 但 host 没 user 时,
  • 如果没开 email/sms,也会根据 host 的邮箱或手机号兜底通知。

测试里也明确覆盖了这种情况。

这背后的思路非常务实:

系统配置表达的是“优先通道”,不是“如果这个通道不通就彻底不通知”。

企业协作里最怕的不是“通道不统一”,而是“人已经到了,结果一个都没通知到”。

Odoo 这里明显优先保证可达性。


九、host 休假检测说明 Frontdesk 不只是通知系统,还在做接待风险预判

create() 完成后会触发 _check_and_notify_visitor()

这段逻辑会:

  • 取 host 对应的 resource_id
  • 在当前公司 resource_calendar_id 上检查该时刻是否命中 leave interval;
  • 如果 host 在休假,就给 visitor 发邮件或短信提醒。

这个设计很妙。

因为它不是等 host 没响应后再补救,而是 visitor 建单时就先检查:

  • 你要找的人是不是当前不可用;
  • 如果不可用,要不要提前告诉来访者。

邮件正文里甚至会补上:

  • 哪位 host 不可用;
  • 其 manager 是谁;
  • 可以联系 manager 或 frontdesk responsible。

这说明 Frontdesk 真正想做的是“现场接待降摩擦”,而不是“把签到动作数字化”。

否则它没必要管 host 是否正在 leave。


十、为什么 checked_out 时顺手把 served=True

write() 里对 state='checked_out' 做了两件事:

  • check_out 当前时间;
  • 同时把 served=True

这看起来有点奇怪:签出为什么会影响饮品服务状态?

本质上这是一个务实假设:

  • 访客已经离场;
  • 未完成的饮品服务也没必要继续挂着待办;
  • 对 frontdesk dashboard 来说,应把“待送饮品”从现场队列里清掉。

也就是说,served 在这里不只是“真的已送达”,还带一点“现场服务任务已结束”的语义。

如果你后续二开饮品履约逻辑,这个隐含约定要注意。


十一、dashboard 统计为什么只看 planned / checked_in

_compute_dashboard_data() 聚合的是:

  • 当前在场人数 checked_in
  • 待到访人数 planned
  • 未送饮品且未取消的数量;
  • 最近签到时间。

它没有把 checked_outcanceled 混进“当前运营面板”。

这再次说明 Frontdesk dashboard 关心的是:

  • 现场还有多少人;
  • 近期还有谁要来;
  • 后勤还有多少事没处理;
  • 刚刚是不是有新签到。

也就是一个实时接待面板,而不是全历史报表。


十二、跨公司 public user 公司权限这处细节,说明官方很清楚 kiosk 是 public route

_onchange_company_id() 里有个容易被略过的处理:

  • 如果 station 绑定了非当前环境公司;
  • base.public_user 还没被授予该公司;
  • 就把该公司加进 public user 的 company_ids

这段非常关键。

因为 kiosk 路由是 auth='public'

也就是说,公开端读取 frontdesk 数据时,底层仍然要有一个“公共身份”去访问对应公司数据。

如果 public user 没有该公司的上下文,跨公司站点就可能打不开或读不到正确字段。

所以 Frontdesk 连“public user 的 allowed companies”这种底层细节都补上了。

这再次证明它不是临时网页,而是一个认真落地在 Odoo 多公司语义里的模块。


十三、实战里最容易误解的 4 个点

误区 1:Frontdesk 就是一个公开表单

不是。

它背后有:

  • token 校验;
  • session 降权;
  • host 白名单;
  • 预约窗口;
  • 多通道通知;
  • leave 预警。

误区 2:通知开 discuss 就够了

不一定。

如果 host 没 user 账号,真正能兜底的是 email/sms 可达性。

误区 3:planned visitor 就应该全部展示

错。

公开 kiosk 只暴露临近时间窗口,核心是减少隐私泄露和误操作。

误区 4:饮品只是一个 UI 附加项

也不是。

它对应独立责任人和单独通知链,已经属于接待后勤协作的一部分。


十四、对二开最有价值的启发

如果你准备扩展 Frontdesk,最值得沿用的不是页面样式,而是下面这些边界:

1)公开入口和内部对象分离

不要让 kiosk 页面直接拥有后台权限;永远让 token + public route 去承接入口。

2)把“谁该被通知”拆成角色而不是部门大群

frontdesk 负责人、host、后勤责任人,语义完全不同。

3)把“可见的人”限制成“可达的人”

如果一个 host 根本收不到通知,就别在公开端让访客选到他。

4)现场系统优先考虑异常兜底

host 请假、没 user、手机签到二维码过期,这些才是真实企业现场最常见的问题。


总结

frontdesk 源码串起来看,你会发现 Odoo 企业版并不是想做一个“扫码填表”的小功能。

它真正做的是一条完整的前台协作链:

  • 固定 token + 临时 mobile token 管公开入口;
  • 预约时间窗口 控制公开可见范围;
  • visitor 状态机 承接现场事实;
  • 负责人 / host / 饮品责任人 三层通知拆责任;
  • discuss / email / sms fallback 保证可达;
  • leave 检测 提前暴露接待风险;
  • public user company 权限处理 保证多公司可用。

所以 Odoo 企业版 Frontdesk 的本质,不是“访客签到页”,而是“把公开接待入口、安全边界和内部协作责任编排到一起的现场运营模块”。

这才是这套源码真正值得学的地方。


参考源码 - enterprise/frontdesk/models/frontdesk_frontdesk.py - enterprise/frontdesk/models/frontdesk_visitor.py - enterprise/frontdesk/controllers/main.py - enterprise/frontdesk/tests/test_frontdesk.py

DISCUSSION

评论区

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