很多团队对 Odoo POS 员工登录的理解,停留在“店员扫条码、输 PIN、进收银界面”。
这当然是表层现象,但真正决定系统是否安全、是否好排错的,不是那一下扫码动作,而是后面三层边界:
- 这台 POS 到底能加载哪些员工;
- 加载到前端的员工,到底被赋予什么角色;
- 条码和 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 做了一个很微妙的处理:
- 字段读取只取最小集合:
name、user_id、work_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。这里最容易被忽略的是:它并不只是“管理员 / 非管理员”二分,而是至少区分三层:
managerminimalcashier
另外,如果员工对应的用户属于 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 明明对却进不去”,建议按这个顺序查:
- 该 POS 配置是否启用了
pos_hr; - 当前配置的员工范围是全员还是指定员工;
- 该员工是否命中
config._employee_domain(); - 当前操作用户是否具备
point_of_sale.group_pos_user; - 员工对应用户是否落在 POS manager 组;
- 该员工在配置里属于
advanced、minimal还是普通 cashier; - 前端收到的是不是该员工的哈希 barcode/PIN;
- 是否存在活跃 session 阻止员工删除或角色调整生效。
最后的结论
Odoo POS 的员工登录机制,本质上是在同时解决三件事:
- 让前台知道“谁可以上机”;
- 让门店知道“这个人能做到什么程度”;
- 让系统在不明文暴露凭证的前提下完成身份校验。
所以这件事的正确理解不是“扫一下码就登录”,而是:
POS 员工登录,其实是门店操作权限、收银责任归属和敏感凭证最小暴露三件事的交叉点。
DISCUSSION
评论区