很多门店会把 Odoo POS 的切换收银员理解成一个很轻的动作:
- A 下班,
- B 接手,
- 前台点一下头像,
- 当前订单继续卖。
这个理解只看到了表面。
源码里真正关键的问题其实是:切过去以后,这个人在前台到底还能做什么,不能做什么,哪些动作必须让经理兜底。
所以更准确的说法是:
Odoo POS 的 cashier 切换不是“换显示名”,而是把一组前台操作边界切到另一个人身上。
先说结论
结合 point_of_sale/static/src/app/services/pos_store.js、LoginScreen、ProductScreen 和 closing_popup.js,可以先抓住四个结论:
- 当前 cashier 会被浏览器记住。 Odoo 用
sessionStorage按 POS 配置保存“谁是当前收银员”。 - 切换后影响的是前台能力,不只是报表归属。 最直接的是改价、打折、正负号输入、现金操作这些按钮会立刻变化。
minimal角色不是“看起来权限小一点”,而是真的会被前端硬禁用一批入口。- 经理兜底不仅发生在登录阶段,更发生在关店差异、价格控制这类高风险动作上。
这四点连起来看,才是 cashier 切换的真实边界。
为什么说切换 cashier 会留下“浏览器记忆”
在 pos_store.js 里,Odoo 会把当前 cashier 存到:
sessionStorage.setItem(`connected_cashier_${this.config.id}`, user.id)
并在后续通过 _getConnectedCashier()、checkPreviousLoggedCashier() 重新取回。
这意味着什么?
这意味着 Odoo 不是把“当前收银员”当成一次性临时状态,而是把它视为:
- 这个浏览器标签页当前代表谁在值班,
- 这个 POS 配置恢复时默认切回谁,
- 哪个人的前台权限应当立即生效。
所以现实里常见的现象——“刷新页面后还是上一个收银员”“同一台机子总是默认某个人”——很多时候不是 bug,而是这个设计本身就在起作用。
收银员切换后,最先变化的不是名字,而是按钮
很多团队只在乎顶部显示的 cashier 名称,但源码里变化更直接的地方是 ProductScreen 的 numpad 权限。
比如:
discount按钮,在manual_discount关闭或 cashier 是minimal时会被禁用;price按钮,除了受restrict_price_control控制,还要求当前 cashier 不是minimal;- 正负号等高风险输入,在
minimal角色下也会被限制。
也就是说,切换 cashier 后,前台不是“继续同一套界面,只是换个人名”,而是:
- 有的人能直接改价;
- 有的人只能按标准价卖;
- 有的人能打折;
- 有的人连折扣键都点不了。
这正是门店常说的“同一台 POS,不同人上机,手感不一样”的根源。
restrict_price_control 真正限制的是谁
pos_store.js 里有个很关键的判断:
cashierHasPriceControlRights() {
return !this.config.restrict_price_control || this.getCashier()._role == "manager";
}
这段逻辑很值得细品。
它不是说“只要能登录就能改价”,而是说:
- 如果门店没开启价格控制限制,那么大家都能动;
- 一旦开启限制,就只有
manager能改价。
这就是非常典型的 POS 权限边界:
价格修改不是普通销售动作,而是高风险动作。
门店最容易出问题的,往往不是漏点一个商品,而是:
- 收银员临时改价没留痕;
- 为了成交随意给低价;
- 把应该走折扣审批的事,直接做成改价。
所以 cashier 切换真正重要的,不是“订单归谁”,而是“谁拿到了价格控制权”。
为什么 minimal 角色不是装饰性角色
很多人第一次看到 minimal,会以为只是一个“比 cashier 更简化”的展示层概念。
不是。
从菜单和按钮可见性来看,minimal 是很硬的前台操作边界。
在 navbar.xml 里,像这些入口都会因为 minimal 而被挡住:
- Install App
- Cash In/Out
- 某些需要更强操作能力的菜单项
再结合 numpad 上对:
- price
- discount
- 正负号
这些输入的限制,你会发现 minimal 的真实业务含义更像是:
- 可以执行标准收银;
- 但不能碰会改变金额结构、现金结构或终端状态的动作。
所以如果门店说“为什么新人能开单但不能改价、不能现金存取”,不要先怀疑配置坏了。先看他切进去后拿到的是不是 minimal。
现金存取按钮为什么也跟 cashier 有关系
在 pos_store.js 里:
get showCashMoveButton() {
return Boolean(this.config.cash_control && this.config._has_cash_move_perm);
}
而 navbar.xml 又额外要求当前 cashier 不是 minimal,才显示 Cash In/Out。
这说明 Odoo 对现金动作的态度非常明确:
- 现金控制本来就是敏感操作;
- 有没有现金权限是一层;
- 当前切进来的 cashier 是不是足够高级,又是一层。
这背后保护的不是 UI 美观,而是审计边界。
因为现金存取一旦发生,后面就会牵扯:
- 钱箱金额变化,
- 交接责任,
- closing control 时的差异解释,
- 最终会计或报表上的责任归属。
所以 cashier 切换不是“谁方便谁来点一下”,而是在决定:谁可以碰门店现金。
为什么经理兜底会在关店时突然变得重要
closing_popup.js 里有个门店最容易忽略的设计:
当盘点金额和系统预期金额不一致时,如果当前用户有足够 authority,就可以看到:
- 是否继续,
- 是否把差异记到账上。
如果没有足够 authority,系统会明确提示:
请联系经理接受 closing difference。
这就说明 cashier 切换的边界,到了关店时会进一步收紧。
因为关店差异不是普通交互错误,而是:
- 门店少钱或多钱;
- 是否允许带着差异过账;
- 这笔差异到底按损失还是盈利处理。
所以“经理权限”在 POS 里不是一个泛泛的称呼,它在关键时刻是一个风险签字位。
登录、切换、关店,其实是同一条权限链
如果把这些源码放在一起看,会发现一条很清晰的业务逻辑:
- 登录或切换 cashier,决定当前是谁;
- 当前 cashier 的
_role,决定前台哪些按钮可点; - 高风险动作,例如改价、现金存取、关店差异,进一步看是否需要 manager 级别兜底;
- 浏览器把当前 cashier 记下来,保证这套边界不是“点一下就忘”。
这就解释了为什么很多门店觉得:
- “明明都进 POS 了,为什么功能不一样?”
- “为什么同一订单 A 能改,B 不能改?”
- “为什么盘点差异出现后,普通收银员过不去?”
因为 Odoo 不是只在登录时做权限判断,而是在整个前台操作链上不断重新使用 cashier 身份。
常见误解
误解 1:切换 cashier 只是为了统计销售员
不对。
统计归属当然是结果之一,但更直接的影响其实是当前 UI 权限。
误解 2:能看到 POS 就代表有完整收银权限
不对。
minimal 角色就说明:能卖单,不等于能改价、能打折、能碰现金。
误解 3:经理权限只在后台才重要
也不对。
POS 前台本身就把经理当作高风险动作的最后授权位。
误解 4:刷新后还是上一个 cashier,一定是系统脏了
很多时候不是。
有可能只是 sessionStorage 把上次连接的 cashier 恢复出来了。
实战排查顺序
当你遇到“切换收银员后权限不对”的问题时,建议按这个顺序看:
- 当前浏览器恢复出来的 cashier 是谁;
- 该 cashier 的
_role是manager、普通 cashier 还是minimal; - POS 配置是否开启了
restrict_price_control; manual_discount是否开启;- 门店是否开启现金控制,以及当前人有没有 cash move 权限;
- 关店差异时,当前人是否具备 authority;
- 是权限问题,还是只是前一个 cashier 状态没被正确切走。
最后的理解方式
最值得记住的一句话是:
Odoo POS 的 cashier 切换,本质上是在切换“谁对这台收银机承担当前操作责任”。
名字只是最表面的变化。
真正被切换的是:
- 改价权,
- 折扣权,
- 现金动作权,
- 关店差异的处理权。
所以在门店制度设计里,cashier 切换绝不只是交接班按钮,而是权限、责任和审计边界一起切换。
DISCUSSION
评论区