会计条款

Odoo 多行付款条款为什么最容易把人绕晕:installment、多行 rounding 与 residual computation 讲透

一张发票被拆成多期后,很多人以为每期只是按百分比切金额;但 Odoo 还要处理固定额、最后余额行、现金舍入和后续残余金额。本文把多行 payment term 的真实计算顺序讲透。

会计
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

Odoo 的多行付款条款并不是“按几行比例拆几笔到期日”这么简单。

它真实的计算顺序是:

先基于整张单的总额算每一行 term amount,再处理中间行的 currency rounding / cash rounding,最后把所有剩余差额压到最后一条 balance line。

所以只要你有:

  • 多个百分比行
  • 固定金额行
  • 外币
  • 现金舍入

最后每一期金额看起来都可能和“手工心算”不完全一样。


为什么最后一条 line 特别重要

account.payment.term._compute_terms() 里有个非常关键的规则:

  • 最后一条 line 永远按余额处理

源码注释已经写得很直白:

The last line is always the balance, no matter the type.

这意味着你在界面上看到最后一行即便写的是某种 line type,真正入计算时,它的任务也是:

  • 吃掉前面各行计算与舍入后剩下的 residual amount

所以 Odoo 不是“每一行都独立严格按配置算完”,而是:

  • 前几行先算
  • 最后一行兜底

这正是为了避免总和对不上。


百分比行、固定金额行、余额行,顺序为什么影响结果

在多行 payment term 里:

  • fixed line 直接用固定金额
  • percent line 按总额比例算
  • 最后一行 balance 吃剩余

因此如果你改的是:

  • 行顺序
  • 哪一行放最后
  • 固定额和百分比的排列

最终 residual 落点就会变。

这也是为什么同样一套“30% + 30% + 40%”语义,若你把最后一行换成别的形式,显示结果仍可能不一样:

因为最后一行承担的是平差职责,不是普通分期职责。


为什么外币下的分期金额更容易和直觉不一致

源码会同时算:

  • company_amount
  • foreign_amount

并先根据总额推一个 rate。

这说明 Odoo 不是只在发票币里拆一遍,然后简单折算。

它要同时维持:

  • 公司币总和守恒
  • 外币总和守恒
  • 各中间行按币种精度舍入
  • 最后一行余额吸收误差

所以外币情况下你最常见到的现象就是:

  • 前几期每一笔看着都被 round 过
  • 最后一笔“怪一点”

这恰恰是为了让整张单在两种货币口径下都闭合。


cash rounding 为什么会改变中间 installment,而不只改最后一期

_compute_terms() 里如果有 cash_rounding,对非最后一行也会先做 compute_difference()

这一点非常反直觉。

很多人以为现金舍入只会在整单总额或最后一行上体现;但 Odoo 的实现是:

  • 非余额行先按当前 installment 自身金额做 cash rounding
  • 这些被 round 过的金额再从 residual 中扣除
  • 最后 balance line 才接住剩余

所以现金舍入不是“最后再统一补差”,而是中间分期也可能先被调整


residual computation 为什么是这套算法的核心

residual_amountresidual_amount_currency 会在每循环一行后逐步减少。

这意味着 Odoo 的思路不是“预先精确算出所有行”,而更像:

  1. 先按规则求当前行
  2. 当前行一旦 round 完,立即从剩余总额中扣掉
  3. 把后续所有不可避免的小差异都留给最后 balance line

这种设计比“一次性算完再整体修补”更稳定,尤其适合:

  • 多币种
  • 多期
  • 含固定额 + 百分比混合
  • 含现金舍入

为什么这会直接影响 invoice residual 和后续核销体验

付款条款不是显示层逻辑,它会真的生成多个 payment term line。

一旦 term 被拆成多期:

  • 每一期有自己的到期日
  • 每一期有自己的应收/应付金额
  • 部分付款时会逐步影响整张单 residual

所以你在后续看到的:

  • 发票没结清但剩余金额不像简单“总额减付款”
  • 某一期金额有 0.01 差异
  • 最后一期看起来被系统“兜底”

往往都不是 bug,而是 payment term 计算时就埋下的结果。


最常见的 4 个误区

1. 以为每行都严格按 value type 独立计算

错。最后一行永远是 balance line。

2. 以为现金舍入只调最后一期

错。中间非余额行也可能先被 cash rounding 调整。

3. 以为 residual 只是付款后才有

错。payment term 自己在生成 installment 时就一直在维护 residual amount。

4. 以为多行 term 只影响 due date,不影响会计体验

错。它会直接塑造后续的 payment term journal items、催款节奏和结清观感。


一句话记忆

Odoo 多行付款条款的本质,不是“把总额切几刀”,而是“按顺序计算各期、逐期舍入、把残差留给最后 balance line 的结算算法”。

DISCUSSION

评论区

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