[FIX] account: global tax rounding error management

Set Rounding Method to "Global rounding"
Create a sale order having these line values:
- Price Unit   54.45, tax 10% not included
- Price Unit  600.00, tax 10% not included
- Price Unit -500.00, tax 10% not included
Tax total will be:
Untaxed Amount 154.45
Taxes           15.44
Total          169.89
From the Sales Order create the Invoice

Issue:
Invoice tax totals will be
Untaxed Amount 154.45
Taxes           15.45
Total          169.90

This occurs because when managing the rounding errors for global
rounding the system 'force' a 1 cent difference on the negative line
that do not need rounding
Example:
Line 1
- current diff: 0.0
- computed tax amount: 5.455
- tax amount + diff: 5.455, after rounding: 5.45
- stored diff for next line: -0.005
Line 2
- current diff: -0.005
- computed tax amount: 60.0
- tax amount + diff: 59.995, after rounding: 60.0
- stored diff for next line: -0.005
Line 3
- current diff: -0.005
- computed tax amount: -50.0
- tax amount + diff: -50.005, after rounding: -50.01

A solution is to not add the diff when the tax line is already rounded

opw-3715229

closes odoo/odoo#156016

X-original-commit: bbca871841577203e44c98290ab0087ec94cf521
Signed-off-by: Laurent Smet (las) <las@odoo.com>
Signed-off-by: Andrea Grazioso (agr) <agr@odoo.com>
Co-authored-by: smetl <las@odoo.com>
This commit is contained in:
Andrea Grazioso (agr-odoo)
2024-03-04 17:02:43 +00:00
co-authored by smetl
parent cf1ead245c
commit a73f06af8a
2 changed files with 57 additions and 15 deletions
+43 -15
View File
@@ -1210,27 +1210,55 @@ class AccountTax(models.Model):
tax_details['group_tax_details'].append(tax_values)
if self.env.company.tax_calculation_rounding_method == 'round_globally':
# Aggregate all amounts according the tax lines grouping key.
comp_currency = self.env.company.currency_id
amount_per_tax_repartition_line_id = defaultdict(lambda: {
'delta_tax_amount': 0.0,
'delta_tax_amount_currency': 0.0,
'tax_amount': 0.0,
'tax_amount_currency': 0.0,
'tax_values_list': [],
})
for base_line, to_update_vals, tax_values_list in to_process:
currency = base_line['currency'] or self.env.company.currency_id
comp_currency = self.env.company.currency_id
currency = base_line['currency'] or comp_currency
for tax_values in tax_values_list:
grouping_key = frozendict(self._get_generation_dict_from_base_line(base_line, tax_values))
total_amounts = amount_per_tax_repartition_line_id[grouping_key]
tax_amount_currency_with_delta = tax_values['tax_amount_currency'] \
+ total_amounts['delta_tax_amount_currency']
tax_amount_currency = currency.round(tax_amount_currency_with_delta)
tax_amount_with_delta = tax_values['tax_amount'] \
+ total_amounts['delta_tax_amount']
tax_amount = comp_currency.round(tax_amount_with_delta)
tax_values['tax_amount_currency'] = tax_amount_currency
tax_values['tax_amount'] = tax_amount
total_amounts['delta_tax_amount_currency'] = tax_amount_currency_with_delta - tax_amount_currency
total_amounts['delta_tax_amount'] = tax_amount_with_delta - tax_amount
total_amounts['tax_amount_currency'] += tax_values['tax_amount_currency']
total_amounts['tax_amount'] += tax_values['tax_amount']
total_amounts['tax_values_list'].append(tax_values)
# Round them like what the creation of tax lines would do.
for key, values in amount_per_tax_repartition_line_id.items():
currency = self.env['res.currency'].browse(key['currency_id']) or comp_currency
values['tax_amount_rounded'] = comp_currency.round(values['tax_amount'])
values['tax_amount_currency_rounded'] = currency.round(values['tax_amount_currency'])
# Dispatch the amount accross the tax values.
for key, values in amount_per_tax_repartition_line_id.items():
foreign_currency = self.env['res.currency'].browse(key['currency_id']) or comp_currency
for currency, amount_field in ((comp_currency, 'tax_amount'), (foreign_currency, 'tax_amount_currency')):
raw_value = values[amount_field]
rounded_value = values[f'{amount_field}_rounded']
diff = rounded_value - raw_value
abs_diff = abs(diff)
diff_sign = -1 if diff < 0 else 1
tax_values_list = values['tax_values_list']
nb_error = math.ceil(abs_diff / currency.rounding)
nb_cents_per_tax_values = math.floor(nb_error / len(tax_values_list))
nb_extra_cent = nb_error % len(tax_values_list)
for tax_values in tax_values_list:
if not abs_diff:
break
nb_amount_curr_cent = nb_cents_per_tax_values
if nb_extra_cent:
nb_amount_curr_cent += 1
nb_extra_cent -= 1
# We can have more than one cent to distribute on a single tax_values.
abs_delta_to_add = min(abs_diff, currency.rounding * nb_amount_curr_cent)
tax_values[amount_field] += diff_sign * abs_delta_to_add
abs_diff -= abs_delta_to_add
grouping_key_generator = grouping_key_generator or default_grouping_key_generator
@@ -35,6 +35,12 @@ class TestTaxTotals(AccountTestInvoicingCommon):
'sequence': 5
})
cls.tax_10 = cls.env['account.tax'].create({
'name': "tax_10a",
'amount_type': 'percent',
'amount': 10.0,
})
cls.tax_16 = cls.env['account.tax'].create({
'name': "tax_16",
'amount_type': 'percent',
@@ -733,6 +739,14 @@ class TestTaxTotals(AccountTestInvoicingCommon):
run_case('round_per_line', lines, [16.60])
run_case('round_globally', lines, [16.59])
lines = [
(54.45, self.tax_10),
(600, self.tax_10),
(-500, self.tax_10),
]
run_case('round_per_line', lines, [15.45])
run_case('round_globally', lines, [15.45])
def test_invoice_foreign_currency_tax_totals(self):
self.env['res.currency.rate'].create({
'name': '2018-01-01',