[FIX] account: Fix indian taxes computation.

For the indian case, when facing two percent price-included taxes having the same percentage,
both need to produce the same tax amounts.

task_id: 3410529

closes odoo/odoo#150971

X-original-commit: 235347db954495b8c0a1bf3bfd0cd408aec7ef0f
Signed-off-by: de Wouters de Bouchout Jean-Benoît (jbw) <jbw@odoo.com>
Signed-off-by: Laurent Smet (las) <las@odoo.com>
This commit is contained in:
Laurent Smet
2024-01-25 09:46:49 +00:00
parent d7fc350ecd
commit 8a443222a1
3 changed files with 97 additions and 54 deletions
+35 -20
View File
@@ -721,19 +721,29 @@ class AccountTax(models.Model):
# || tax_3 | .. | |
# || ... | .. | .. |
# ----------------------------
def recompute_base(base_amount, fixed_amount, percent_amount, division_amount):
# Recompute the new base amount based on included fixed/percent amounts and the current base amount.
# Example:
# tax | amount | type | price_include |
# -----------------------------------------------
# tax_1 | 10% | percent | t
# tax_2 | 15 | fix | t
# tax_3 | 20% | percent | t
# tax_4 | 10% | division | t
# -----------------------------------------------
def recompute_base(base_amount, incl_tax_amounts):
""" Recompute the new base amount based on included fixed/percent amounts and the current base amount. """
fixed_amount = incl_tax_amounts['fixed_amount']
division_amount = sum(tax_factor for _i, tax_factor in incl_tax_amounts['division_taxes'])
percent_amount = sum(tax_factor for _i, tax_factor in incl_tax_amounts['percent_taxes'])
if company.country_code == 'IN':
# For the indian case, when facing two percent price-included taxes having the same percentage,
# both need to produce the same tax amounts. To do that, the tax amount of those taxes are computed
# directly during the first traveling in reversed order.
total_percentage = sum(tax_factor for _i, tax_factor in incl_tax_amounts['percent_taxes'])
for i, tax_factor in incl_tax_amounts['percent_taxes']:
tax_amount = float_round(base * tax_factor / (100 + total_percentage), precision_rounding=prec)
cached_tax_amounts[i] = tax_amount
fixed_amount += tax_amount
percent_amount = 0.0
incl_tax_amounts.update({
'percent_taxes': [],
'division_taxes': [],
'fixed_amount': 0.0,
})
# if base_amount = 145, the new base is computed as:
# (145 - 15) / (1.0 + 30%) * 90% = 130 / 1.3 * 90% = 90
return (base_amount - fixed_amount) / (1.0 + percent_amount / 100.0) * (100 - division_amount) / 100
# The first/last base must absolutely be rounded to work in round globally.
@@ -780,7 +790,11 @@ class AccountTax(models.Model):
i = len(taxes) - 1
store_included_tax_total = True
# Keep track of the accumulated included fixed/percent amount.
incl_fixed_amount = incl_percent_amount = incl_division_amount = 0
incl_tax_amounts = {
'percent_taxes': [],
'division_taxes': [],
'fixed_amount': 0.0,
}
# Store the tax amounts we compute while searching for the total_excluded
cached_tax_amounts = {}
if handle_price_include:
@@ -793,20 +807,19 @@ class AccountTax(models.Model):
sum_repartition_factor = sum(tax_repartition_lines.mapped("factor"))
if tax.include_base_amount:
base = recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount)
incl_fixed_amount = incl_percent_amount = incl_division_amount = 0
base = recompute_base(base, incl_tax_amounts)
store_included_tax_total = True
if tax.price_include or self._context.get('force_price_include'):
if tax.amount_type == 'percent':
incl_percent_amount += tax.amount * sum_repartition_factor
incl_tax_amounts['percent_taxes'].append((i, tax.amount * sum_repartition_factor))
elif tax.amount_type == 'division':
incl_division_amount += tax.amount * sum_repartition_factor
incl_tax_amounts['division_taxes'].append((i, tax.amount * sum_repartition_factor))
elif tax.amount_type == 'fixed':
incl_fixed_amount += abs(quantity) * tax.amount * sum_repartition_factor * abs(fixed_multiplicator)
incl_tax_amounts['fixed_amount'] = abs(quantity) * tax.amount * sum_repartition_factor * abs(fixed_multiplicator)
else:
# tax.amount_type == other (python)
tax_amount = tax._compute_amount(base, sign * price_unit, quantity, product, partner, fixed_multiplicator) * sum_repartition_factor
incl_fixed_amount += tax_amount
incl_tax_amounts['fixed_amount'] += tax_amount
# Avoid unecessary re-computation
cached_tax_amounts[i] = tax_amount
# In case of a zero tax, do not store the base amount since the tax amount will
@@ -819,7 +832,7 @@ class AccountTax(models.Model):
store_included_tax_total = False
i -= 1
total_excluded = recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount)
total_excluded = recompute_base(base, incl_tax_amounts)
if self._context.get('round_base', True):
total_excluded = currency.round(total_excluded)
@@ -855,6 +868,8 @@ class AccountTax(models.Model):
# We know the total to reach for that tax, so we make a substraction to avoid any rounding issues
tax_amount = total_included_checkpoints[i] - (base + cumulated_tax_included_amount)
cumulated_tax_included_amount = 0
elif price_include and i in cached_tax_amounts:
tax_amount = cached_tax_amounts[i]
else:
tax_amount = tax.with_context(force_price_include=False)._compute_amount(
tax_base_amount, sign * price_unit, quantity, product, partner, fixed_multiplicator)
+16 -1
View File
@@ -220,7 +220,7 @@ class TestTax(TestTaxCommon):
res = self.group_tax_percent.with_context({'force_price_include':True}).compute_all(100.0)
self._check_compute_all_results(
100, # 'total_included'
83.33, # 'total_excluded'
83.33, # 'total_excluded'
[
# base , amount | seq | amount | incl | incl_base
# ---------------------------------------------------
@@ -231,6 +231,21 @@ class TestTax(TestTaxCommon):
res
)
self.env.company.country_id = self.env.ref('base.in')
res = self.group_tax_percent.with_context({'force_price_include':True}).compute_all(100.0)
self._check_compute_all_results(
100, # 'total_included'
83.34, # 'total_excluded'
[
# base , amount | seq | amount | incl | incl_base
# ---------------------------------------------------
(83.34, 8.33), # | 1 | 10% | |
(83.34, 8.33), # | 2 | 10% | |
# ---------------------------------------------------
],
res
)
def test_tax_percent_division(self):
self.division_tax.price_include = True
self.division_tax.include_base_amount = True
@@ -1436,7 +1436,8 @@ export class PosStore extends Reactive {
// 2) Deal with the rounding methods
var round_tax = this.company.tax_calculation_rounding_method != "round_globally";
const company = this.company;
var round_tax = company.tax_calculation_rounding_method != 'round_globally';
var initial_currency_rounding = currency_rounding;
if (!round_tax) {
@@ -1444,13 +1445,34 @@ export class PosStore extends Reactive {
}
// 3) Iterate the taxes in the reversed sequence order to retrieve the initial base of the computation.
var recompute_base = function (base_amount, fixed_amount, percent_amount, division_amount) {
return (
(((base_amount - fixed_amount) / (1.0 + percent_amount / 100.0)) *
(100 - division_amount)) /
100
);
};
var recompute_base = function(base_amount, incl_tax_amounts){
let fixed_amount = incl_tax_amounts.fixed_amount;
let division_amount = 0.0;
for(const [, tax_factor] of incl_tax_amounts.division_taxes){
division_amount += tax_factor;
}
let percent_amount = 0.0;
for(const [, tax_factor] of incl_tax_amounts.percent_taxes){
percent_amount += tax_factor;
}
if(company.country && company.country.code === "IN"){
for(const [i, tax_factor] of incl_tax_amounts.percent_taxes){
const tax_amount = round_pr(base_amount * tax_factor / (100 + percent_amount), currency_rounding);
cached_tax_amounts[i] = tax_amount;
fixed_amount += tax_amount;
}
percent_amount = 0.0;
}
Object.assign(incl_tax_amounts, {
percent_taxes: [],
division_taxes: [],
fixed_amount: 0.0,
});
return (base_amount - fixed_amount) / (1.0 + percent_amount / 100.0) * (100 - division_amount) / 100;
}
var base = round_pr(price_unit * quantity, initial_currency_rounding);
@@ -1464,36 +1486,29 @@ export class PosStore extends Reactive {
var i = taxes.length - 1;
var store_included_tax_total = true;
var incl_fixed_amount = 0.0;
var incl_percent_amount = 0.0;
var incl_division_amount = 0.0;
const incl_tax_amounts = {
percent_taxes: [],
division_taxes: [],
fixed_amount: 0.0,
}
var cached_tax_amounts = {};
if (handle_price_include) {
taxes.reverse().forEach(function (tax) {
if (tax.include_base_amount) {
base = recompute_base(
base,
incl_fixed_amount,
incl_percent_amount,
incl_division_amount
);
incl_fixed_amount = 0.0;
incl_percent_amount = 0.0;
incl_division_amount = 0.0;
base = recompute_base(base, incl_tax_amounts);
store_included_tax_total = true;
}
if (tax.price_include) {
if (tax.amount_type === "percent") {
incl_percent_amount += tax.amount * tax.sum_repartition_factor;
incl_tax_amounts.percent_taxes.push([i, tax.amount * tax.sum_repartition_factor]);
} else if (tax.amount_type === "division") {
incl_division_amount += tax.amount * tax.sum_repartition_factor;
incl_tax_amounts.division_taxes.push([i, tax.amount * tax.sum_repartition_factor]);
} else if (tax.amount_type === "fixed") {
incl_fixed_amount +=
Math.abs(quantity) * tax.amount * tax.sum_repartition_factor;
incl_tax_amounts.fixed_amount += Math.abs(quantity) * tax.amount * tax.sum_repartition_factor;
} else {
var tax_amount = self._compute_all(tax, base, quantity);
incl_fixed_amount += tax_amount;
incl_tax_amounts.fixed_amount += tax_amount;
cached_tax_amounts[i] = tax_amount;
}
if (store_included_tax_total) {
@@ -1505,10 +1520,7 @@ export class PosStore extends Reactive {
});
}
var total_excluded = round_pr(
recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount),
initial_currency_rounding
);
var total_excluded = round_pr(recompute_base(base, incl_tax_amounts), initial_currency_rounding);
var total_included = total_excluded;
// 4) Iterate the taxes in the sequence order to fill missing base/amount values.
@@ -1533,11 +1545,12 @@ export class PosStore extends Reactive {
total_included_checkpoints[i] !== undefined &&
tax.sum_repartition_factor != 0
) {
var tax_amount =
total_included_checkpoints[i] - (base + cumulated_tax_included_amount);
var tax_amount = total_included_checkpoints[i] - (base + cumulated_tax_included_amount);
cumulated_tax_included_amount = 0;
} else {
tax_amount = self._compute_all(tax, tax_base_amount, quantity, true);
}else if(tax.price_include && cached_tax_amounts.hasOwnProperty(i)){
var tax_amount = cached_tax_amounts[i];
}else{
var tax_amount = self._compute_all(tax, tax_base_amount, quantity, true);
}
tax_amount = round_pr(tax_amount, currency_rounding);