POS 抹零与差异

Odoo POS 抹零为什么不等于“账上差几分”:rounded total、支付校验与 closing difference 讲透

很多人看到 POS 有 cash rounding,就会把所有金额差异都理解成“抹零造成的几分钱误差”;但 Odoo 真正区分的是前台 rounded total、支付行是否按精度录入、订单过账时的 rounding line,以及关店盘点时真正的 cash difference。本文结合 point_of_sale 源码,讲清 rounded total 和 closing difference 为什么根本不是一回事。

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

很多团队一看到 Odoo POS 里的 cash rounding,就会下意识把所有“几分钱差异”归到一个桶里:

  • 小票金额和原价差了几分;
  • 付款行被系统拦住;
  • 发票多了一条 rounding line;
  • 关店盘点出现差异;
  • 报表里又冒出 profit / loss。

于是大家很容易得出一个错误结论:

这些都是同一件事,只不过表现位置不同。

其实完全不是。

源码里最值得注意的恰恰是:Odoo 把这些“看起来都像金额差”的东西,拆成了不同层级的问题来处理。

先说结论

结合 order_payment_validation.jspos_order.pypos_session.pyclosing_popup.js,可以先抓住五个结论:

  1. rounded total 是订单层的金额呈现和支付精度问题。
  2. payment line 是否合规,会在前台验证阶段先被拦住。
  3. rounding line 是订单/发票/会计层为了让金额闭合而补出的结构。
  4. session closing 的 cash difference 是盘点层的现实现金差异。
  5. 这两种“差额”可能同时存在,但业务含义根本不同。

rounded total 先解决的是“这笔单怎么付”

order_payment_validation.js 里,Odoo 会检查现金支付行的金额是否已经按 rounding precision 录入。

如果没有,它会直接弹出:

  • 你的 payment line 必须按某个精度取整;
  • 你应该录入的是多少,而不是当前金额。

这一步说明 Odoo 的第一个保护点发生在:

订单还没正式确认之前,先确保前台支付行本身符合抹零规则。

也就是说,rounded total 首先是个交易录入规范问题。

不是等到后面记账了才补救,而是尽量在收银员录付款时就拦住错误。

为什么支付行校验不是多此一举

很多人会想:

  • 后台反正还能 round;
  • 为什么前台还要提醒一次?

原因非常现实。

如果付款行本身没按精度录入,后面会出现多层混乱:

  • 收银员以为顾客付了 9.97;
  • 系统其实只接受 10.00 这种粒度;
  • 找零、已付判断、票据金额都会开始出现边界误会。

所以前台这层校验保护的不是美观,而是:

  • 收银员输入的金额要和门店的 rounding 规则一致;
  • 本单是不是已经“付清”,要在一个明确的精度规则里判断。

rounding line 解决的是“账务怎么闭合”

到了 pos_order.py,问题就从前台交互转成了会计结构。

无论是在订单会计分录准备阶段,还是在创建发票时,Odoo 都可能根据 cash rounding 逻辑生成 display_type = 'rounding' 的行。

这类 rounding line 的作用不是告诉顾客“少收了几分”,而是让系统在账务上表达:

  • 这笔差额来自抹零;
  • 它应该落到 rounding 的 profit account 或 loss account;
  • 应收或 payment term 需要相应调节,确保整张凭证平衡。

也就是说:

rounding line 是会计解释层,不是盘点差异层。

这两层非常容易被混淆。

为什么同样叫 difference,closing difference 却是另一回事

pos_session.py 在关店时处理的 cash_register_difference,指的是:

  • 系统理论现金余额,
  • 和实际数出来的钱,
  • 之间的差。

这个差额最后会通过 _post_statement_difference() 走向:

  • Profit Account,
  • 或 Loss Account,
  • 并在日志或报表里记成 closing difference。

请注意,这里的问题不是“订单要不要四舍五入”。

而是:

  • 钱箱里现实世界的钱,
  • 和系统按订单、付款、现金存取推算出来的钱,
  • 是否一致。

这显然已经是另一个层级的问题了。

它关心的是门店现金控制,而不是单笔交易金额显示。

rounded total 和 cash difference 为什么常被混为一谈

因为从门店视角看,它们都可能表现为“差了几分钱/几毛钱”。

但业务语义完全不同:

rounded total / rounding line

这是系统按规则主动处理出来的金额调整。

它是在保护:

  • 订单结账的合法精度;
  • 发票或分录的平衡;
  • 抹零该进哪条会计线。

closing difference

这是盘点时发现的现实偏差

它是在表达:

  • 少钱了;
  • 多钱了;
  • 是否允许带着这个偏差关店;
  • 这笔偏差由谁接受并如何记账。

所以最值得记住的一点是:

抹零是规则内差额,closing difference 是现实偏差。

为什么 closing popup 会要求经理介入

closing_popup.js 很明确地展示了这一层边界。

如果钱数不匹配:

  • 有 authority 的人,可以选择是否把差异记到账上并继续;
  • 没有 authority 的人,会被要求联系经理接受差异。

这再次说明:

  • rounding 规则是配置与交易层面的系统行为;
  • closing difference 则是需要人来承担责任的门店异常。

系统能自动算抹零, 但不会替门店自动接受“钱箱确实不平”。

rounding difference 和 closing difference 甚至可能同时存在

这是很多实施现场最容易误判的地方。

完全可能出现:

  • 单笔订单因为现金抹零,生成了 rounding line;
  • 但关店时,收银员实点现金仍然和理论余额不一致。

这两者并不互相抵消,也不互相证明。

你不能因为系统允许 rounding,就默认关店差异合理; 也不能因为关店有差异,就断定是 rounding 配错了。

二者需要分开诊断。

为什么报表里 profit / loss 也不能一概而论

源码里,rounding 相关利润/损失账户,和 closing difference 相关利润/损失账户,都可能进入账务。

这会导致门店或财务人员看到:

  • 都像是 small difference;
  • 都进了某种 profit / loss;
  • 于是误以为是同一来源。

但其实:

  • 一个来自订单抹零规则;
  • 一个来自盘点现实偏差。

如果不先区分来源,后续分析会越来越乱。

常见误解

误解 1:rounded total 就是关店差异

不对。

一个是交易规则内的金额调整,一个是现金盘点的现实差异。

误解 2:只要后台能补 rounding line,前台付款就随便输

不对。

前台 payment line 本身就必须符合 rounding precision。

误解 3:所有几分钱差额都应该查 cash rounding 配置

不对。

有些是 rounding 配置问题,有些是实际现金控制问题。

误解 4:有 closing difference 就说明 rounding 算错了

也不对。

closing difference 可能来自点钞、现金存取、漏录等现实操作差异。

实战排查顺序

当你遇到“金额差异”时,建议先把问题分层:

  1. 是 payment line 在前台就被 rounding 校验拦住了吗;
  2. 是订单/发票里出现了 rounding line 吗;
  3. 这笔抹零是否按正确 account 落账;
  4. 还是 session closing 时 counted cash 与 expected cash 不一致;
  5. 当前 closing difference 是否超出门店允许阈值,需要经理接受;
  6. 报表里的差额到底来自 rounding 规则还是 closing difference。

最后的理解方式

如果只记一句话,我建议记:

Odoo POS 里的 rounded total 是“按规则收多少钱”,closing difference 是“最后钱箱里到底剩多少钱”。

前者是交易与会计结构问题, 后者是门店现金控制与责任问题。

它们都可能表现成“几分钱差额”, 但绝对不是同一件事。

DISCUSSION

评论区

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