很多门店第一次启用 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_rounding 和 only_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_total和amount_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 / 混合支付差几分”时,建议按这个顺序查:
- 先看 POS 配置是否启用了
cash_rounding; - 再看
only_round_cash_method是否开启; - 看订单 payment lines 里是否真的存在现金支付方式;
- 看 rounding method 的 rounding 精度和 rounding_method;
- 看
amount_total、amount_paid与 diff 是否落在允许区间; - 若已开票,再看发票上的 rounding line 与 payment term line 是否同步调整。
最后的结论
POS 现金取整最容易被低估,是因为大家只看到“前台少几分钱、找零更顺手”。
但在 Odoo 源码里,这件事真正牵扯的是:
- 哪些订单允许按现金逻辑判断已付;
- 电子支付是否被错误卷入取整;
- 混合支付时差额到底归谁;
- 发票和会计分录如何保持可审计。
所以别把 cash rounding 理解成“9.97 自动变 10.00”的小功能。
它本质上是在协调收银习惯、支付边界和会计落账三套语义。
DISCUSSION
评论区