很多门店上线 POS 时,最容易轻视的步骤不是收银,也不是关店,而是开台。
表面上看,收银员只是录一个起始现金,然后进入前台;但在 Odoo 里,开台其实是在把“今天这班、这台机、这套现金控制规则”正式变成一个可入账的会话容器。
先说结论:opening_control 的核心作用不是拦你一下再放行,而是把“未开始营业”和“已经开始承担交易责任”明确切开。 一旦跨过这条线,会话就会拿到正式时间戳、正式 sequence 名称、现金控制留言,很多后续动作也随之变得不可逆。
先把状态机看清:不是一打开就叫 opened
pos.session 在源码里有四个关键状态:
opening_controlopenedclosing_controlclosed
很多团队以为创建 session 就等于“营业中”,其实不是。新会话默认先落在 opening_control。
这代表它只是一个“待确认开台”的草稿容器,还没正式开始承接当天交易。源码这样设计,是为了让门店在真正开门前,先把这些问题确认掉:
- 今天的备用金到底是多少;
- 是否存在现金支付方式;
- 是否需要留开台备注;
- 会话能否被安全地取消重来。
set_opening_control() 真正做了什么
在 pos_session.py 里,set_opening_control() 不是简单写个金额,它至少做了三件很关键的事:
- 调用
_set_opening_control_data(),把状态从opening_control切到opened; - 记录
start_at,也就是这班正式开始的时间; - 根据
ir.sequence生成正式 session 名称。
这意味着:开台不是补充元数据,而是会话从“待开始”切到“已营业”的分水岭。
很多实施项目里,大家只盯着收银前端能不能进入,却忽略了后台账务和审计语义其实就是从这里起步的。
现金控制为什么要在开台时留痕
如果当前 POS 配置里存在 is_cash_count 的支付方式,Odoo 在 _set_opening_control_data() 中会:
- 接收前端传来的
cashbox_value; - 把
opening_notes存到 session; - 计算
difference = cashbox_value - cash_register_balance_start; - 调用
_post_cash_details_message()把差异、理论值、实盘值和备注记进 chatter。
这一步非常重要,因为它说明 Odoo 对开台金额的理解不是“门店今天有多少钱”,而是:
今天这班开始时,现金抽屉被谁、以什么数额、带着什么说明接手了。
所以如果你在后台看到 Opening cash 的消息,不要把它当普通备注。那其实是 cash control 的审计锚点。
为什么没现金支付时,开台逻辑会变轻
源码里还藏着一个很容易忽略的边界:如果当前配置没有现金支付方式,set_opening_control() 依然会把 session 打开,但不会走现金差异那套逻辑。
这时如果有 notes,系统只是简单 message_post 留下一条 opening control message。
这说明 Odoo 的设计是分层的:
- 有现金控制:开台要确认现金抽屉责任;
- 无现金控制:开台更像“开始营业”的确认动作。
所以“为什么有的门店开台要录现金,有的不用”,答案往往不在前端界面,而在支付方式配置是否包含现金 counting。
为什么有的开台中会话能删,有的不能删
delete_opening_control_session() 的约束很硬:只有同时满足这两个条件,才允许删除:
- 状态仍然是
opening_control; - 该 session 还没有任何订单。
换句话说,只要会话已经正式 opened,或者哪怕还在 opening_control 但已经挂上订单,它就不能被当成“作废草稿”直接删掉。
这条规则背后的逻辑很朴素:
- 还没开始、也没发生交易,可以重来;
- 一旦开始营业,或者已经有业务数据进入,就必须保留痕迹。
这也是很多人遇到“为什么后台删不掉这个 session”时最容易误判的点。系统不是卡你,而是在防止把已承担责任的会话当普通草稿擦掉。
session 名称为什么要等开台后才正式分配
set_opening_control() 结尾会去找 ir.sequence(code='pos.session'),然后为 session 生成正式名称。
这件事表面不起眼,实际上意义很大:
- 开台前,会话只是临时壳;
- 开台后,它才获得正式业务编号;
- 这个编号随后会出现在后台查询、审计与运营沟通里。
所以如果你发现某些异常会话名字还是 / 或表现得很奇怪,优先不要怀疑前端显示问题,而要先看会话是否真的完成了 opening control。
最容易误解的三件事
误区一:开台只是 UI 上的一个确认弹窗
不对。它实际在切状态、记时间、写现金日志、分配正式编号。
误区二:开台金额只是为了后面好看一点
也不对。这个金额会影响现金理论余额计算,是 closing control 的起点之一。
误区三:还没卖东西的 session 都可以删
不完全对。只有仍处于 opening_control 且无订单的 session 才能删;一旦 opened,就不再是“随手可扔”的草稿。
实战排错顺序
遇到“开不了台 / 开台后编号异常 / 不能删除会话 / 现金起始额不对”,建议按这个顺序查:
- 先看 session 当前状态,是
opening_control还是已经opened; - 再看 POS 配置里是否存在现金支付方式;
- 看前端传入的
cashbox_value与后台cash_register_balance_start是否一致; - 看 chatter 里有没有 Opening cash / opening notes 留痕;
- 看
ir.sequence的pos.session配置是否正常; - 如果删不掉,确认该 session 是否已有订单,或是否已跨过 opening_control。
最后的结论
POS 开台最容易被低估,是因为它看起来不像“交易动作”。但在 Odoo 里,它其实决定了:
- 从什么时候开始正式营业;
- 现金责任从哪一刻开始计算;
- 这个会话是否能被当草稿取消;
- 后续 closing control 的基线从哪里起算。
所以不要把 opening control 当成“进系统前多点一次确认”。
它真正做的,是把“准备营业”变成“开始对今天这班交易负责”。
DISCUSSION
评论区