[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:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user