POS 现金取整

Odoo POS 现金 rounding 为什么不是“把 9.97 算成 10 元”:only_round_cash_method、混合支付与 rounding line 边界讲透

很多人把 POS 现金取整理解成“最终金额四舍五入一下”,但 Odoo 真正处理的是配置层策略约束、订单已付判断、现金/非现金混合支付边界,以及发票上的 rounding line 怎么补。本文结合 point_of_sale 源码讲清:为什么 rounding 不是随便开个开关、为什么混合支付是高风险区,以及金额差几分时系统到底在保护什么。

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

很多门店第一次启用 POS cash rounding 时,会把它理解成一句很简单的话:

9.97 收 10.00,不就完了吗?

但 Odoo 的实现远比这个复杂。POS 现金取整不是“美化付款数字”,而是在尽量同时满足前台找零习惯、后台金额平衡和发票可审计性。

先说结论:Odoo POS 的 rounding 真正要解决的,不是总价显示得顺不顺眼,而是“哪些付款场景允许取整、取整后的已付判断怎么做、差额最终落到哪里”。 这也是为什么源码对 mixed payments 明确写了 TODO,说明这块边界本来就敏感。

第一步先看配置边界:不是所有 rounding 都允许进 POS

pos.config 上有个硬约束:如果启用了 cash_rounding,其 rounding_method.strategy 必须是 add_invoice_line

也就是说,Odoo 不允许 POS 随便拿别的 rounding 策略来混用。

这背后的原因很现实。POS 不是纯会计后台,它要面对现场收银、退款、发票、支付方式拆分等多条链路。对 POS 来说,最稳定、最容易追溯的方式,就是把 rounding 差额显式落成一条 line,而不是让它悄悄吞进某个税或商品金额里。

_get_rounded_amount() 在决定什么叫“视为付清”

源码里 _get_rounded_amount() 很关键:

  • 如果没开 cash_rounding,金额按正常币种精度处理;
  • 如果开了 rounding,且 only_round_cash_method = False,订单总额会参与取整;
  • 如果 only_round_cash_method = True,只有订单里存在现金付款时才参与取整。

这说明 Odoo 在问的不是“金额能不能四舍五入”,而是:

当前这笔订单,有没有资格按现金世界的规则去判断是否已付。

这和很多现场想象的“订单总额统一改成整数”完全不是一回事。

only_round_cash_method 为什么这么重要

这个字段其实在保护一个核心边界:电子支付并不天然接受现金世界的取整逻辑。

如果顾客全程刷卡、线上支付或者其他非现金方式,而系统却仍然强行把 9.97 视作 10.00,那后面很容易出现:

  • 支付渠道金额对不上;
  • 对账差几分;
  • 发票与支付记录不一致;
  • 财务误以为系统吞差额。

所以 only_round_cash_method=True 的意义不是“更保守一点”,而是在明确告诉系统:

  • 有现金参与时,允许按找零语义做 rounding;
  • 没有现金参与时,不要把收银习惯硬套到电子支付上。

为什么 mixed payment 是源码里的敏感区

源码里有显式 TODO:当 cash_roundingonly_round_cash_method 同时启用,且订单混合了现金与非现金支付时,仍有边界需要特别小心。

这很好理解。

假设一单总额 9.97:

  • 顾客付 5 元现金;
  • 再刷卡 4.97。

此时你到底应该:

  • 把整单当成现金世界,允许收 10.00?
  • 还是只让现金部分受 rounding 影响?
  • 如果差额留在卡上或留在现金上,会不会改变渠道对账?

这不是“写段 if” 就能优雅解决的问题,因为它已经涉及支付责任到底挂在哪个 payment line 上。

所以源码对 mixed payment 的谨慎不是缺陷,而是在承认:现金 rounding 一旦碰到多支付方式分摊,语义会立刻复杂化。

action_pos_order_paid() 怎样判断差几分钱还能算 paid

在订单正式标记 paid 之前,Odoo 会先判断:

  • 如果没开 rounding,amount_totalamount_paid 必须严格对齐;
  • 如果开了 rounding,则允许存在一个不超过上限的 diff;
  • 这个上限和 rounding method 有关,例如 HALF-UP 会取半个 rounding 单位作为最大容差。

所以“订单少付几分钱为什么还能过”不是系统松,而是它在按 configured rounding 规则判断这几分钱是不是合法取整差。

发票上的 rounding line 在补什么

当订单涉及开票时,_create_invoice() 里会进一步比较:

  • 实际支付总额;
  • 发票金额;
  • 由 rounding 带来的 difference_currency / difference_balance。

如果有差额,系统会:

  • 找已有 rounding line 叠加;
  • 或新建 display_type='rounding' 的行;
  • 同时调整 payment term line,保证整体平衡。

这一步非常关键,因为它说明 Odoo 不是简单把差额“忽略不计”,而是:

把那几分钱明确落到一条可审计的会计语义里。

所以看到 rounding line,不要先怀疑脏数据。很多时候它恰恰说明系统在认真补齐取整差额的落点。

最容易误解的三件事

误区一:cash rounding 就是订单总价统一改整数

不对。它还取决于 payment method、配置策略以及已付判断逻辑。

误区二:只要启用了 rounding,所有支付方式都可以一起跟着圆整

不对。only_round_cash_method 就是在限制这个边界。

误区三:差几分钱能过,说明系统不严谨

也不对。前提是这些差额落在 rounding 容差内,并且后续能被明确记账。

实战排错顺序

遇到“为什么这单能过 / 这单又报未付清 / 发票多出 rounding line / 混合支付差几分”时,建议按这个顺序查:

  1. 先看 POS 配置是否启用了 cash_rounding
  2. 再看 only_round_cash_method 是否开启;
  3. 看订单 payment lines 里是否真的存在现金支付方式;
  4. 看 rounding method 的 rounding 精度和 rounding_method;
  5. amount_totalamount_paid 与 diff 是否落在允许区间;
  6. 若已开票,再看发票上的 rounding line 与 payment term line 是否同步调整。

最后的结论

POS 现金取整最容易被低估,是因为大家只看到“前台少几分钱、找零更顺手”。

但在 Odoo 源码里,这件事真正牵扯的是:

  • 哪些订单允许按现金逻辑判断已付;
  • 电子支付是否被错误卷入取整;
  • 混合支付时差额到底归谁;
  • 发票和会计分录如何保持可审计。

所以别把 cash rounding 理解成“9.97 自动变 10.00”的小功能。

它本质上是在协调收银习惯、支付边界和会计落账三套语义。

DISCUSSION

评论区

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