POS 关店与差异

Odoo POS 关店为什么不是“点一下就结束”:closing_control、现金差异与 force close 的真实边界

POS 收银员最容易低估的,不是开单,而是关店。Odoo 在 session closing 上同时处理草稿单、现金盘点、银行支付差异、会计分录平衡与库存落账,所以“关不掉”往往不是系统挑刺,而是在保护账务边界。本文从 action_pos_session_closing_control 到 _validate_session 讲透 POS 关店的真实机制与排错顺序。

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

POS 最常见的抱怨之一是:

白天都能卖,为什么一到关店就各种报错?

因为 Odoo 的 POS session 不是“营业状态开关”,而是一个要对收银、库存、会计同时交账的边界容器。 白天很多问题可以暂时悬着;到了关店,系统必须把账说圆。

所以结论先说:POS 关店失败,往往不是 closing 功能本身坏了,而是 closing 阶段第一次逼所有未闭合的问题一起结算。

closing_control 真正做的不是“锁屏”,而是进入清算阶段

pos.session 里,session 状态不是只有 opened 和 closed 两个点,中间还有 closing_control

action_pos_session_closing_control() 做的第一件事,不是生成会计分录,而是先检查:

  • 今天是否还有 draft 订单;
  • 当前 session 是否已经 closed;
  • 是否启用了 cash control;
  • 是否处于 rescue session。

如果还有 draft 订单,源码直接拒绝关店。这很关键,因为 draft 单代表业务动作还没完成,贸然关店会让“待确认交易”混进“已清算日结”。

所以很多人觉得 Odoo 太严格,其实它在守的是最基础的一条线:

草稿单不属于已结账日。

cash control 不是装饰项,它决定了关店路径完全不同

如果 cash_control 没开,closing_control 可能直接进入 action_pos_session_close()。 但只要开了现金控制,系统就会要求你面对现实:

  • 期初现金是多少;
  • 订单里按现金方式收了多少;
  • 实际数出来的现金是多少;
  • 差额到底进 profit account 还是 loss account。

_post_statement_difference() 里,Odoo 会把差额生成 statement line,并要求现金 journal 预先配置好利润/损失科目。

这也是为什么一些现场明明“就差几块钱”,却卡在关店上:

  • 不是不能差;
  • 是你必须明确差到哪一个科目里。

为什么 force close 会出现

很多人把 force close 当成“系统突然认输”。其实不是。

_validate_session() 在创建会计分录后,会检查 move 是否平衡。若不平衡,它会 rollback 当前事务,并弹出 _close_session_action() 对应的 wizard,让用户选择 balancing account 来强制平账。

也就是说 force close 的真实语义是:

系统发现你这次关店能算出结果,但算不平,所以要求你显式承认差额去向。

它不是推荐流程,而是一个人工兜底

关店时其实还会顺带处理库存与毛利

如果公司设置 update_stock_at_closing,关店时 _validate_session() 还会调用 _create_picking_at_end_of_session(),并在需要时为 FIFO/AVCO 行补算成本。

这点非常容易被忽略。 很多人以为 POS closing 只影响收银和会计,其实它还可能是库存正式落账的时点。

所以某些案例里“为什么白天前台都能卖,晚上关店才报库存或成本相关错误”,答案就在这里:

  • 白天交易可以先积压在 session 里;
  • 到关店才统一生成库存动作和成本归集。

rescue session 的 closing 又为什么特殊

源码里对 rescue session 还有特殊处理:

  • 如果 rescue session 且启用 cash control,系统只计算现金箱里的支付;
  • 并明确说明 rescue session 不能完全按普通前端流程关闭。

这说明 rescue session 的设计目标是“补救晚到订单”,不是“完整替代一次正常营业班次”。

所以看到 rescue session 无法像普通会话那样顺滑关店,不要奇怪,这就是产品边界。

最容易误解的四件事

误区一:关店时报错,说明关店功能有问题

很多时候不是。 更常见的是前面某个问题被延迟到关店才集中暴露。

误区二:现金差异只影响报表,不影响关店

不对。 差异要入账,没配 profit/loss account 就可能直接卡住。

误区三:force close 等于“忽略错误”

不对。 它不是忽略,而是指定差额怎么入账

误区四:POS closing 和库存无关

不对。 在 session closing 更新库存的公司设置下,关店就是库存与成本真正落地的时点。

实战排错顺序

遇到 POS 关不掉,建议别一上来盯着最后一个报错,先按顺序查:

  1. session 下是否还有 draft orders;
  2. cash control 是否开启;
  3. 收银员是否已经录入 closing cash;
  4. cash journal 是否配置了 profit/loss account;
  5. 是否存在银行支付差异或 split payment 差异;
  6. move 创建后是否平衡;
  7. 若启用 closing 时更新库存,picking / valuation / cost 是否能正常生成;
  8. 当前 session 是否 rescue session,是否触发了特殊路径。

最后的结论

Odoo POS 的关店,本质上不是“营业结束按钮”,而是一次结算闸门。 它同时在检查:

  • 业务单据是否都走完;
  • 现金是否盘清;
  • 差额是否有会计去向;
  • 会计分录是否平衡;
  • 库存与成本是否该在此刻落账。

所以 POS closing 难的地方,从来不是按钮本身,而是它逼你回答一句白天可以暂时回避、晚上必须说清的话:

今天这班收银,到底有没有真正对上。

DISCUSSION

评论区

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