POS 自助点餐

Odoo POS 自助点餐为什么不是“扫个码就能下单”:入口鉴权、桌台 token 与 kiosk/mobile 边界讲透

很多人把 POS Self Order 看成一个公开 H5 菜单,但 Odoo 源码真正做的是“配置入口校验 + 运行模式分流 + 桌台上下文绑定 + 默认用户降权执行”。本文结合 pos_self_order 控制器与模型,讲清 access_token、table_identifier、kiosk/mobile 两套入口、默认用户执行上下文,以及为什么自助点餐不是一个彻底匿名的开放接口。

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

很多人第一次看 Odoo POS Self Order,会把它理解成:

一个公开菜单页,顾客扫码后自己下单。

这描述太轻了。 如果它真的只是“公开页面 + 下单接口”,那你很快就会遇到一连串问题:

  • 任何人拿到链接是不是都能替这家门店下单;
  • 同一个链接到底代表整店、某张桌还是某个 kiosk;
  • 顾客请求是以谁的身份在 Odoo 里执行;
  • 订单能不能随便改桌、删单、改价格。

结论先说:Odoo POS Self Order 不是彻底匿名的开放 API,而是一套“入口校验 + 模式分流 + 桌台上下文 + 默认用户降权执行”的受控自助下单机制。 它看起来是公开网页,实际上后台一直在小心收紧边界。

入口校验的第一层:不是知道 config_id 就算有权限

controllers/self_entry.py_verify_entry_access() 里,系统先检查:

  • config_id 是否存在且为数字;
  • 对应 pos.config 是否存在;
  • self_ordering_mode 是否不是 nothing
  • 如果带 access_token,token 是否和配置匹配。

这说明自助点餐入口不是“只要猜到 config_id 就能进”。

更关键的是,源码把 config token 分成两种状态:

  • 带 token 访问;
  • 不带 token 访问。

而最终是否真正保留 token 能力,还要看配置模式与会话状态。也就是说:

token 不是装饰参数,而是入口能力的一部分。

为什么请求不是用 public 用户直接到处跑

虽然 route 是 auth="public",但 _verify_entry_access() 并没有让后续逻辑一直以匿名用户执行。

它会取:

  • company = pos_config_sudo.company_id
  • user = pos_config_sudo.self_ordering_default_user_id

然后构造:

with_company(company).with_user(user).with_context(allowed_company_ids=company.ids, ...)

这层非常关键。它表示 Odoo 的想法不是“匿名顾客直接操作业务对象”,而是:

顾客通过公开入口触发流程,但真正业务执行要落到一个受控的默认用户上下文里。

好处很明显:

  • 权限边界更可控;
  • 公司上下文更稳定;
  • 多公司下不容易串库;
  • 数据读取与写入范围不必完全暴露给 public 用户。

mobile 和 kiosk 为什么不是同一个入口换皮

很多实施会把 mobile 和 kiosk 理解成“一个手机版、一个大屏版”。其实源码里的差别更深。

_verify_entry_access()process_order() 里,至少有四个关键区别:

  1. mobile 模式要求 active session 才真正可用;
  2. mobile 模式会解析 table_identifier,并尝试绑定到 restaurant.table
  3. kiosk 模式不走桌台绑定,而更像公共自助终端;
  4. tracking number 前缀不同:kiosk 用 K{config.id}-,mobile 用 S

这说明 Odoo 并不是只在前端 UI 上区分设备,而是在后端订单语义上就区分:

  • 这是桌边扫码单;
  • 还是门店公共终端单。

为什么 table_identifier 不是可有可无的前端参数

对于 mobile 自助点餐,table_identifier 不是“顺便传个桌号文本”,而是会参与:

  • 桌台对象查找;
  • parent table 回退;
  • 订单 self_ordering_table_id 绑定;
  • 后续 draft 单聚合和用户取单逻辑。

process_order() 里,订单会写:

  • self_ordering_table_id = table.id
  • source = 'mobile''kiosk'
  • floating_order_name
  • tracking_number

这意味着桌台上下文一旦绑定,不只是前端显示“桌 12”,而是会变成订单结构的一部分。

所以如果你在现场看到“顾客扫了 A 桌二维码,订单却跑到 B 桌”这种问题,优先别怀疑 UI 文案,而要去查:

  • table_identifier 是否正确;
  • 该 table 是否 active;
  • 是否被 parent table 重写;
  • 订单写入时 self_ordering_table_id 实际落成了谁。

为什么系统还要单独做删单 token 校验

remove_order() 里,除了入口 access_token 外,删单还要额外校验 order_access_token,而且只有 draft 单能删。

这很值得注意,因为它说明:

  • 配置入口 token 只证明“你能进入这个自助点餐环境”;
  • 订单 access token 才证明“你能操作这张具体订单”。

这是一种典型的双层边界:

  • 店级入口权限;
  • 单级对象权限。

如果你忽略这一层,就会误以为拿到自助点餐入口就能删除别人订单。Odoo 显然不想这么干。

为什么 sync_from_ui() 之后还要回头验价

process_order() 先把订单丢给 pos.order.sync_from_ui(),随后又执行 _verify_line_price()_compute_combo_price()

这正说明一件事:

自助点餐前端可以描述点了什么,但不能完全定义最终价格。

尤其是 combo、属性加价、fiscal position、pricelist 等场景,后端会重新校验和计算。否则一个公开页面就等于把定价真相交给浏览器,这在商业上太危险。

最容易误解的四件事

误区一:Self Order 就是匿名商城接口

不对。 它虽然以 public route 暴露,但后端真正执行依赖默认用户、公司上下文和 token 校验。

误区二:知道 config_id 就能下单

不对。 还要看配置模式、token、active session,以及具体模式是否允许。

误区三:mobile 和 kiosk 只是页面长得不一样

不对。 两者在桌台绑定、tracking number、订单来源语义上都不同。

误区四:二维码里的桌号只是前端展示信息

不对。 table_identifier 直接决定订单会不会绑定到正确桌台。

实战排错顺序

如果你遇到“扫码打不开 / 能看菜单不能下单 / 下单跑错桌 / 删单失败 / kiosk 与 mobile 行为不一致”,建议按这个顺序查:

  1. pos.configself_ordering_mode 是什么;
  2. 入口 URL 的 config_idaccess_token 是否正确;
  3. 当前配置是否有 active session;
  4. self_ordering_default_user_id 是否存在且权限足够;
  5. mobile 模式下 table_identifier 是否命中 active 桌台;
  6. 是否被 parent table 回退改写;
  7. 订单写入后 sourcetracking_numberself_ordering_table_id 是否正确;
  8. 删单时 order_access_token 是否匹配且订单是否仍为 draft

最后的结论

Odoo POS Self Order 看起来像一个“谁都能打开的点餐页”,但它真正的设计重点不是开放,而是可控开放:

  • 入口有配置与 token 校验;
  • 执行有默认用户与公司上下文;
  • mobile 与 kiosk 有不同语义;
  • 订单与删单都有对象级边界。

所以更准确的理解应该是:

自助点餐不是把 POS 暴露到公网,而是在公网入口外面再套了一层受控业务壳。

DISCUSSION

评论区

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