提前折扣

Odoo 提前付款折扣为什么最容易在“税基到底要不要跟着折”上出错:payment term、included/excluded/mixed 边界讲透

提前付款折扣最容易让人误判的不是折扣日期,而是税基边界。Odoo 在 payment term 上提供 included、excluded、mixed 三种计算口径,并且只允许单行 100% 条款启用早付折扣。本文专讲这条税基边界,避免和普通折扣、到期日逻辑混为一谈。

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

Odoo 的提前付款折扣已经有不少人讲过“什么时候到期、什么时候还能享受折扣”。

但真正最容易配错、也最容易让报表口径跑偏的,往往不是日期,而是:

折扣到底是连税一起打,还是只动未税部分,还是在会计与税务上采用混合语义?

如果你去看 /home/ubuntu/odoo-temp/addons/account/models/account_payment_term.pyaccount_tax.py,会发现官方在这件事上其实非常明确,而且比很多项目想象得更严格。

一、早付折扣不是销售优惠,而是 payment term 自身的一部分

源码里早付折扣字段直接挂在 account.payment.term 上:

  • early_discount
  • discount_percentage
  • discount_days
  • early_pay_discount_computation

这说明 Odoo 认为早付折扣不是“付款时临时给个优惠”,而是:

  • 从开票那一刻起,付款条件里就已经写好了;
  • 这条应收 / 应付天然带着另一套可结清金额;
  • 后续所有 payment date、follow-up、reconcile 逻辑都可能受它影响。

所以它和销售行上的 discount 不是一回事。

二、为什么系统强制“只能单行 100% 条款启用 early discount”

_check_lines() 里有个很硬的约束:

  • 如果 payment term 有多行;
  • 又启用了 early_discount
  • 直接报错。

错误文案也很明确:

The Early Payment Discount functionality can only be used with payment terms using a single 100% line.

这说明官方有一个非常清晰的边界判断:

当一张发票已经被拆成多期到期时,系统不想再把“整单提前付款折扣”叠上去。

原因不只是交互复杂,而是会计语义会变得很混乱:

  • 折扣到底作用于哪一期?
  • 是只折第一期,还是全部期次按比例折?
  • 税基要不要拆开算?
  • follow-up 到底看哪个节点?

官方干脆把这条路堵死,避免你在一个已经分期的债权结构上再叠一个整体折扣窗口。

三、included / excluded / mixed 真正表达的是什么

early_pay_discount_computation 有三个值:

  • included
  • excluded
  • mixed

很多人第一次看,只觉得像国家本地化参数。 其实它表达的是 税基边界

1. included

_get_amount_due_after_discount()_compute_terms() 里,如果不是 excluded / mixed,系统会按总额计算折扣。

也就是:

  • 折扣作用在 total_amount 上;
  • 税也在折扣口径里一起动。

这相当于:早付时,连含税总额一起打折。

2. excluded

如果是 excluded,系统会用:

  • total_amount - untaxed_amount * percentage

这说明折扣只作用在未税部分,税额不跟着折。

也就是:客户早付能少付一部分货款 / 服务款,但税款口径不跟着降。

3. mixed

mixed 最容易让人误解。

源码默认把比利时公司设为 mixed,从名字 Always (upon invoice) 也能看出, 它不是简单的“税全跟”或“税全不跟”,而是在会计 / 税务确认层面采用特殊口径。

对实施来说,最重要的不是死记本地法规,而是知道:

这里不是 UI 选项,而是在决定 early discount 对 tax base 的法律与报表边界。

四、为什么 discount_balance / discount_amount_currency 比折扣百分比更重要

很多人只记得折扣百分比,却忽略 _compute_terms() 实际会产出:

  • discount_date
  • discount_balance
  • discount_amount_currency

这三个字段一起才构成真正可执行的早付规则:

  • discount_date:窗口什么时候截止;
  • discount_balance:公司币口径下,窗口内多少金额算结清;
  • discount_amount_currency:单据币口径下,窗口内多少金额算结清。

所以 Odoo 不是把 early discount 当注释,而是把它变成一组真正参与应收 / 应付管理的金额语义。

五、为什么 cash rounding 也会被卷进来

_get_amount_due_after_discount()_compute_terms() 里还专门处理了 cash_rounding

这很能说明官方思路:

  • 早付折扣不是抽象比例;
  • 它最终要落成“今天如果付款,到底付多少”;
  • 而真实付款金额必须能和票据允许的 rounding 口径对上。

这也是为什么一些项目觉得“系统算出来怎么差 0.01 / 0.02”,其实不是算错,而是 rounding 规则在发挥作用。

六、为什么它和普通 line discount / global discount 不能混着理解

销售折扣更多是在:

  • 交易定价层;
  • 发票形成前;
  • 单价 / 行级金额的表达。

而 early payment discount 在 Odoo 里是:

  • 发票已经成立后;
  • 应收应付如何被结清;
  • 税基边界是否随提前付款改变。

所以两者都叫 discount,但完全不是同一个层面。

误区 1:把 early payment discount 当成“另一种 sale discount”

错。

它更像 settlement condition,而不是 pricing rule。

误区 2:以为只要设置了百分比,税务口径自然会跟着处理

不一定。

关键在 early_pay_discount_computation,不是百分比本身。

七、为什么不同国家默认值不同

_compute_discount_computation() 会按公司 country_code 设默认值:

  • BEmixed
  • NLexcluded
  • 其他 → included

这再次说明官方把这件事视为法规 / 本地化语义,不是任意的业务偏好。

所以当客户问“为什么隔壁国家版本默认不一样”,正确回答不是“官方随便定的”,而是“默认在适配各地常见税务处理边界”。

八、排查早付折扣金额不对时该怎么看

建议按这个顺序:

  1. payment term 是否只有单行 100%;
  2. 是否真的启用了 early_discount
  3. discount_percentage / discount_days 是否合理;
  4. 当前 computation 是 includedexcluded 还是 mixed
  5. 你在看的金额是 discount_balance 还是普通 residual;
  6. 是否还有 cash rounding;
  7. 是否误把 sale discount / global discount 当成同一类逻辑。

一句话记忆

Odoo 早付折扣最核心的不是“提前几天”,而是“提前付款时税基边界怎么算”;includedexcludedmixed 决定的正是这条最容易被忽略的会计与税务分界线。

DISCUSSION

评论区

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