Odoo 的提前付款折扣已经有不少人讲过“什么时候到期、什么时候还能享受折扣”。
但真正最容易配错、也最容易让报表口径跑偏的,往往不是日期,而是:
折扣到底是连税一起打,还是只动未税部分,还是在会计与税务上采用混合语义?
如果你去看 /home/ubuntu/odoo-temp/addons/account/models/account_payment_term.py 和 account_tax.py,会发现官方在这件事上其实非常明确,而且比很多项目想象得更严格。
一、早付折扣不是销售优惠,而是 payment term 自身的一部分
源码里早付折扣字段直接挂在 account.payment.term 上:
early_discountdiscount_percentagediscount_daysearly_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 有三个值:
includedexcludedmixed
很多人第一次看,只觉得像国家本地化参数。 其实它表达的是 税基边界。
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_datediscount_balancediscount_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 设默认值:
BE→mixedNL→excluded- 其他 →
included
这再次说明官方把这件事视为法规 / 本地化语义,不是任意的业务偏好。
所以当客户问“为什么隔壁国家版本默认不一样”,正确回答不是“官方随便定的”,而是“默认在适配各地常见税务处理边界”。
八、排查早付折扣金额不对时该怎么看
建议按这个顺序:
- payment term 是否只有单行 100%;
- 是否真的启用了
early_discount; discount_percentage/discount_days是否合理;- 当前 computation 是
included、excluded还是mixed; - 你在看的金额是
discount_balance还是普通 residual; - 是否还有 cash rounding;
- 是否误把 sale discount / global discount 当成同一类逻辑。
一句话记忆
Odoo 早付折扣最核心的不是“提前几天”,而是“提前付款时税基边界怎么算”;
included、excluded、mixed决定的正是这条最容易被忽略的会计与税务分界线。
DISCUSSION
评论区