[FIX] account: Fix computation of taxes with round_globally.

-github issue: 14673

closes odoo/odoo#44312

-task: 2007300
X-original-commit: 6d3f3c749dbccc875f67c17ee95086df494044f6
Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
This commit is contained in:
Laurent Smet
2020-01-30 13:41:55 +00:00
parent a707bba744
commit eec0b80756
2 changed files with 176 additions and 7 deletions
+31 -7
View File
@@ -1819,10 +1819,8 @@ class AccountTax(models.Model):
# precision of the currency.
# The context key 'round' allows to force the standard behavior.
round_tax = False if company.tax_calculation_rounding_method == 'round_globally' else True
round_total = True
if 'round' in self.env.context:
round_tax = bool(self.env.context['round'])
round_total = bool(self.env.context['round'])
if not round_tax:
prec += 5
@@ -1850,7 +1848,33 @@ class AccountTax(models.Model):
# (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
base = round(price_unit * quantity, prec)
# The first/last base must absolutely be rounded to work in round globally.
# Indeed, the sum of all taxes ('taxes' key in the result dictionary) must be strictly equals to
# 'price_included' - 'price_excluded' whatever the rounding method.
#
# Example using the global rounding without any decimals:
# Suppose two invoice lines: 27000 and 10920, both having a 19% price included tax.
#
# Line 1 Line 2
# -----------------------------------------------------------------------
# total_included: 27000 10920
# tax: 27000 / 1.19 = 4310.924 10920 / 1.19 = 1743.529
# total_excluded: 22689.076 9176.471
#
# If the rounding of the total_excluded isn't made at the end, it could lead to some rounding issues
# when summing the tax amounts, e.g. on invoices.
# In that case:
# - amount_untaxed will be 22689 + 9176 = 31865
# - amount_tax will be 4310.924 + 1743.529 = 6054.453 ~ 6054
# - amount_total will be 31865 + 6054 = 37919 != 37920 = 27000 + 10920
#
# By performing a rounding at the end to compute the price_excluded amount, the amount_tax will be strictly
# equals to 'price_included' - 'price_excluded' after rounding and then:
# Line 1: sum(taxes) = 27000 - 22689 = 4311
# Line 2: sum(taxes) = 10920 - 2176 = 8744
# amount_tax = 4311 + 8744 = 13055
# amount_total = 31865 + 13055 = 37920
base = currency.round(price_unit * quantity)
# For the computation of move lines, we could have a negative base value.
# In this case, compute all with positive values and negate them at the end.
@@ -1898,7 +1922,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 = currency.round(recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount))
# 5) Iterate the taxes in the sequence order to compute missing tax amounts.
# Start the computation of accumulated amounts at the total_excluded value.
@@ -1983,9 +2007,9 @@ class AccountTax(models.Model):
return {
'base_tags': taxes.mapped(is_refund and 'refund_repartition_line_ids' or 'invoice_repartition_line_ids').filtered(lambda x: x.repartition_type == 'base').mapped('tag_ids').ids,
'taxes': taxes_vals,
'total_excluded': sign * (currency.round(total_excluded) if round_total else total_excluded),
'total_included': sign * (currency.round(total_included) if round_total else total_included),
'total_void': sign * (currency.round(total_void) if round_total else total_void),
'total_excluded': sign * total_excluded,
'total_included': sign * currency.round(total_included),
'total_void': sign * currency.round(total_void),
}
@api.model
+145
View File
@@ -107,6 +107,13 @@ class TestTaxCommon(AccountTestUsersCommon):
}),
],
})
cls.tax_19_percent = cls.tax_model.create({
'name': "test_rounding_methods_1",
'amount_type': 'percent',
'amount': 19,
})
cls.bank_journal = cls.env['account.journal'].search([('type', '=', 'bank'), ('company_id', '=', cls.account_manager.company_id.id)])[0]
cls.bank_account = cls.bank_journal.default_debit_account_id
cls.expense_account = cls.env['account.account'].search([('user_type_id.type', '=', 'payable')], limit=1) #Should be done by onchange later
@@ -649,3 +656,141 @@ class TestTax(TestTaxCommon):
],
tax.compute_all(-1.0)
)
def test_rounding_tax_excluded_round_per_line(self):
''' Test the rounding of a 19% price excluded tax in an invoice having 22689 and 9176 as lines.
The decimal precision is set to zero.
The computation must be similar to round(22689 * 0.19) + round(9176 * 0.19).
'''
self.tax_19_percent.company_id.currency_id.rounding = 1.0
self.tax_19_percent.company_id.tax_calculation_rounding_method = 'round_per_line'
res1 = self.tax_19_percent.compute_all(22689)
self._check_compute_all_results(
27000, # 'total_included'
22689, # 'total_excluded'
[
# base, amount
# ---------------
(22689, 4311),
# ---------------
],
res1
)
res2 = self.tax_19_percent.compute_all(9176)
self._check_compute_all_results(
10919, # 'total_included'
9176, # 'total_excluded'
[
# base , amount
# ---------------
(9176, 1743),
# ---------------
],
res2
)
def test_rounding_tax_excluded_round_globally(self):
''' Test the rounding of a 19% price excluded tax in an invoice having 22689 and 9176 as lines.
The decimal precision is set to zero.
The computation must be similar to round((22689 + 9176) * 0.19).
'''
self.tax_19_percent.company_id.currency_id.rounding = 1.0
self.tax_19_percent.company_id.tax_calculation_rounding_method = 'round_globally'
res1 = self.tax_19_percent.compute_all(22689)
self._check_compute_all_results(
27000, # 'total_included'
22689, # 'total_excluded'
[
# base, amount
# ---------------
(22689, 4310.91),
# ---------------
],
res1
)
res2 = self.tax_19_percent.compute_all(9176)
self._check_compute_all_results(
10919, # 'total_included'
9176, # 'total_excluded'
[
# base , amount
# ---------------
(9176, 1743.44),
# ---------------
],
res2
)
def test_rounding_tax_included_round_per_line(self):
''' Test the rounding of a 19% price included tax in an invoice having 27000 and 10920 as lines.
The decimal precision is set to zero.
The computation must be similar to round(27000 / 1.19) + round(10920 / 1.19).
'''
self.tax_19_percent.price_include = True
self.tax_19_percent.company_id.currency_id.rounding = 1.0
self.tax_19_percent.company_id.tax_calculation_rounding_method = 'round_per_line'
res1 = self.tax_19_percent.compute_all(27000)
self._check_compute_all_results(
27000, # 'total_included'
22689, # 'total_excluded'
[
# base , amount
# ---------------
(22689, 4311),
# ---------------
],
res1
)
res2 = self.tax_19_percent.compute_all(10920)
self._check_compute_all_results(
10920, # 'total_included'
9176, # 'total_excluded'
[
# base , amount
# ---------------
(9176, 1744),
# ---------------
],
res2
)
def test_rounding_tax_included_round_globally(self):
''' Test the rounding of a 19% price included tax in an invoice having 27000 and 10920 as lines.
The decimal precision is set to zero.
The computation must be similar to round((27000 + 10920) / 1.19).
'''
self.tax_19_percent.price_include = True
self.tax_19_percent.company_id.currency_id.rounding = 1.0
self.tax_19_percent.company_id.tax_calculation_rounding_method = 'round_globally'
res1 = self.tax_19_percent.compute_all(27000)
self._check_compute_all_results(
27000, # 'total_included'
22689, # 'total_excluded'
[
# base , amount
# ---------------
(22689, 4311),
# ---------------
],
res1
)
res2 = self.tax_19_percent.compute_all(10920)
self._check_compute_all_results(
10920, # 'total_included'
9176, # 'total_excluded'
[
# base , amount
# ---------------
(9176, 1744),
# ---------------
],
res2
)