POS 员工登录

Odoo POS 收银员登录为什么不只是“扫工号进系统”:employee 可见域、角色分层与哈希 PIN 边界讲透

很多人以为 POS 员工登录只是前端做个扫码或输 PIN,但 Odoo 真正在处理的是“谁能看见哪些员工、谁在前台拥有什么权限、敏感凭证能不能明文下发”。本文结合 pos_hr 源码,讲清 employee 域、manager/minimal/cashier 三层角色、barcode/PIN 哈希下发,以及为什么删员工会被活跃会话拦住。

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

很多团队对 Odoo POS 员工登录的理解,停留在“店员扫条码、输 PIN、进收银界面”。

这当然是表层现象,但真正决定系统是否安全、是否好排错的,不是那一下扫码动作,而是后面三层边界:

  1. 这台 POS 到底能加载哪些员工;
  2. 加载到前端的员工,到底被赋予什么角色;
  3. 条码和 PIN 能不能以明文形式下发到浏览器。

结论先说:Odoo POS 的员工登录不是一个前端小功能,而是一套“员工可见域 + 前台角色分层 + 凭证最小暴露”的组合设计。 如果你把它理解成“员工表全部下发给前端,前端自己判断能不能进”,后面权限和审计一定会越做越乱。

先看主问题:前端为什么看不到全部员工

pos_hr/models/hr_employee.py 里,_load_pos_data_domain() 并不是直接返回全体员工,而是调用:

config._employee_domain(config.current_user_id.id)

这意味着 POS 员工列表不是“系统里有谁就给谁”,而是先经过当前 POS 配置和当前用户语境计算可见域

换句话说,Odoo 的默认态度是:

员工能不能出现在这台收银机上,先看配置与域,不先看扫码动作。

这很重要,因为很多实施项目会误把“员工登录失败”理解成 PIN 错了,实际上更常见的原因是:

  • 员工不在这台 POS 的员工范围里;
  • 当前配置只允许特定员工集合;
  • 当前用户并不能把某些员工下发到前端。

为什么 hr.employee 能在 POS 里读到,但又不像后台那样完全暴露

源码里还有一段容易被忽略的注释:hr.employee 有 public fallback 机制,用户即使没有完整读取权限,也可能通过 hr.employee.public 收到公开字段。

于是 pos_hr 做了一个很微妙的处理:

  • 字段读取只取最小集合:nameuser_idwork_contact_id
  • 条码和 PIN 不是直接跟 read() 一起裸读;
  • 真正读取条码/PIN 时,会先检查当前用户是否有 point_of_sale.group_pos_user
  • 再经过可见记录过滤;
  • 最后把条码和 PIN 做 SHA1 哈希后才下发。

这背后的设计思想非常明确:

POS 前端需要“验证凭证是否匹配”,但不需要知道原始凭证内容。

因此前端拿到的不是原始 barcode/PIN,而是哈希值,前台输入值同样做哈希后比对。

角色为什么不是只有 cashier / manager 两层

_load_pos_data_read() 里,Odoo 会给每个员工附加 _role_user_role。这里最容易被忽略的是:它并不只是“管理员 / 非管理员”二分,而是至少区分三层:

  • manager
  • minimal
  • cashier

另外,如果员工对应的用户属于 group_pos_manager_id,还会额外给 _user_role = 'admin'

这说明 POS 的员工权限不是简单映射后台 ACL,而是前台再建了一层更贴近门店操作的角色模型

你可以把它理解成两套边界叠在一起:

  • 后台权限:这个用户在 Odoo 后台能做什么;
  • POS 前台角色:这个员工在收银界面能看到什么、能执行什么敏感动作。

所以有些团队说“这个员工明明是系统用户,为什么在 POS 里还是很多按钮点不了”,根本原因通常不是 bug,而是前台 _role 没进到 manager 级别。

为什么删除员工会被活跃会话拦住

hr_employee._unlink_except_active_pos_session() 这段逻辑很像“系统多管闲事”,但其实特别合理。

当某个员工可能仍被活跃 POS session 使用时,源码会阻止删除,并提示先关闭相关会话。原因不是因为数据库做不了级联,而是因为:

  • 活跃会话里可能正在用这个员工做登录切换;
  • closing control 需要按员工汇总收款与现金出入;
  • 审计日志与 cash move 可能还要追溯到该员工。

也就是说,Odoo 在保护的不是一条员工主数据,而是:

门店操作中的在场身份与审计归属。

这也是为什么很多“离职员工删不掉”的案例,真正正确的处理顺序不是强删,而是先关会话、核对 closing、再清理主数据。

员工身份为什么会一路传到关店报表和现金流水

pos_hr/models/pos_session.py 里,closing control 会把付款按员工聚合:

  • _aggregate_payments_amounts_by_employee()
  • _aggregate_moves_by_employee()

同时,现金存取的 statement line 也会写入 employee_id

这解释了一个很现实的问题:

为什么 POS 员工登录不是一个“UI 头像切换”而已?

因为员工身份一旦进入订单、付款和现金移动,后面会影响:

  • 收银员维度的销售统计;
  • closing control 的金额归属;
  • cash in / cash out 的责任人;
  • chatter / message 中的开店、关店作者信息。

所以如果你在门店里让所有人共用一个总账号,再在流程上口头区分“谁值班”,从 Odoo 的设计角度看,其实等于主动放弃了可审计性。

最容易误解的四件事

误区一:PIN 不安全,因为前端总能拿到

不对。 前端确实要拿到可校验信息,但 pos_hr 下发的是哈希,不是原始 PIN。它解决不了“浏览器永远可信”这个大问题,但至少没有把敏感值明文摊给前端。

误区二:员工登录权限等于后台用户权限

不对。 后台用户组和 POS 前台 _role 是两层结构。你后台是用户,不等于在 POS 前台就是 manager。

误区三:员工删不掉说明有脏数据

不一定。 更常见的是活跃 session 仍引用该员工,属于业务保护而不是数据损坏。

误区四:看不到员工就是同步坏了

也不一定。 优先怀疑 employee domain、配置选人范围、当前用户组,而不是先怀疑同步。

实战排错顺序

如果你遇到“员工不能登录 / 某些员工不出现 / 角色不对 / PIN 明明对却进不去”,建议按这个顺序查:

  1. 该 POS 配置是否启用了 pos_hr
  2. 当前配置的员工范围是全员还是指定员工;
  3. 该员工是否命中 config._employee_domain()
  4. 当前操作用户是否具备 point_of_sale.group_pos_user
  5. 员工对应用户是否落在 POS manager 组;
  6. 该员工在配置里属于 advancedminimal 还是普通 cashier;
  7. 前端收到的是不是该员工的哈希 barcode/PIN;
  8. 是否存在活跃 session 阻止员工删除或角色调整生效。

最后的结论

Odoo POS 的员工登录机制,本质上是在同时解决三件事:

  • 让前台知道“谁可以上机”;
  • 让门店知道“这个人能做到什么程度”;
  • 让系统在不明文暴露凭证的前提下完成身份校验。

所以这件事的正确理解不是“扫一下码就登录”,而是:

POS 员工登录,其实是门店操作权限、收银责任归属和敏感凭证最小暴露三件事的交叉点。

DISCUSSION

评论区

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