Accrue and allocate rounding errors when there are multiple taxes

Closes #11317
This commit is contained in:
Nicolas Évrard
2022-12-25 18:55:17 +01:00
parent 458f6b0327
commit 2da82e2b94
3 changed files with 123 additions and 3 deletions
+1
View File
@@ -1,3 +1,4 @@
* Accrue and allocate rounding errors when there are multiple taxes
* Use match pattern to find journal
* Display general ledger information on account form
+20 -3
View File
@@ -3,7 +3,7 @@
import datetime
from collections import namedtuple
from decimal import Decimal
from itertools import groupby
from itertools import cycle, groupby
from sql import Literal
from sql.aggregate import Sum
@@ -1248,8 +1248,23 @@ class TaxableMixin(object):
def _round_taxes(self, taxes):
if not self.currency:
return
remainder = 0
for taxline in taxes.values():
taxline['amount'] = self.currency.round(taxline['amount'])
rounded_amount = self.currency.round(taxline['amount'])
remainder += rounded_amount - taxline['amount']
taxline['amount'] = rounded_amount
# We need to compensate the rounding we did
remainder = self.currency.round(remainder, opposite=True)
if abs(remainder) >= self.currency.rounding:
offset_amount = self.currency.rounding.copy_sign(remainder)
for tax in cycle(taxes.values()):
tax['amount'] -= offset_amount
remainder -= offset_amount
if abs(remainder) < self.currency.rounding:
break
@fields.depends('company', methods=['_get_tax_context', '_round_taxes'])
def _get_taxes(self):
@@ -1267,6 +1282,7 @@ class TaxableMixin(object):
assert all(t.company == self.company for t in line.taxes)
l_taxes = Tax.compute(Tax.browse(line.taxes), line.unit_price,
line.quantity, line.tax_date or self.tax_date)
current_taxes = {}
for tax in l_taxes:
taxline = self._compute_tax_line(**tax)
# Base must always be rounded per line as there will be one
@@ -1278,8 +1294,9 @@ class TaxableMixin(object):
else:
taxes[taxline]['base'] += taxline['base']
taxes[taxline]['amount'] += taxline['amount']
current_taxes[taxline] = taxes[taxline]
if tax_rounding == 'line':
self._round_taxes(taxes)
self._round_taxes(current_taxes)
if tax_rounding == 'document':
self._round_taxes(taxes)
return taxes
+102
View File
@@ -1153,6 +1153,108 @@ class AccountTestCase(
self.assertEqual(tax['base'], Decimal('100.00'))
self.assertEqual(tax['amount'], Decimal('20.02'))
@with_transaction()
def test_taxable_mixin_tax_residual_rounding(self):
"Test TaxableMixin for rounding with residual amount"
pool = Pool()
Account = pool.get('account.account')
Tax = pool.get('account.tax')
Configuration = pool.get('account.configuration')
currency = create_currency('cur')
company = create_company()
with set_company(company):
create_chart(company)
config = Configuration(1)
config.tax_rounding = 'line'
config.save()
tax_account, = Account.search([
('name', '=', 'Main Tax'),
])
tax1 = Tax()
tax1.name = tax1.description = "Tax 1"
tax1.type = 'percentage'
tax1.rate = Decimal('.1')
tax1.invoice_account = tax_account
tax1.credit_note_account = tax_account
tax1.save()
tax2 = Tax()
tax2.name = tax2.description = "Tax 2"
tax2.type = 'percentage'
tax2.rate = Decimal('.1')
tax2.invoice_account = tax_account
tax2.credit_note_account = tax_account
tax2.save()
taxable = self.Taxable(
currency=currency,
taxable_lines=[
([tax1, tax2], Decimal('1.0417'), 1, None),
],
company=company)
taxline_1, taxline_2 = taxable._get_taxes()
self.assertEqual(taxline_1['base'], Decimal('1.04'))
self.assertEqual(taxline_1['amount'], Decimal('.11'))
self.assertEqual(taxline_2['base'], Decimal('1.04'))
self.assertEqual(taxline_2['amount'], Decimal('.10'))
@with_transaction()
def test_taxable_mixin_tax_residual_rounding_negative_residual(self):
"Test TaxableMixin for rounding with negative residual amount"
pool = Pool()
Account = pool.get('account.account')
Tax = pool.get('account.tax')
Configuration = pool.get('account.configuration')
currency = create_currency('cur')
company = create_company()
with set_company(company):
create_chart(company)
config = Configuration(1)
config.tax_rounding = 'line'
config.save()
tax_account, = Account.search([
('name', '=', 'Main Tax'),
])
tax1 = Tax()
tax1.name = tax1.description = "Tax 1"
tax1.type = 'percentage'
tax1.rate = Decimal('.1')
tax1.invoice_account = tax_account
tax1.credit_note_account = tax_account
tax1.save()
tax2 = Tax()
tax2.name = tax2.description = "Tax 2"
tax2.type = 'percentage'
tax2.rate = Decimal('.1')
tax2.invoice_account = tax_account
tax2.credit_note_account = tax_account
tax2.save()
# -2.95 is the unit price of -3.54 with 20% tax included
taxable = self.Taxable(
currency=currency,
taxable_lines=[
([tax1], Decimal('30.00'), 1, None),
([tax1, tax2], Decimal('-2.95'), 1, None),
],
company=company)
taxline_1, taxline_2, taxline_3 = taxable._get_taxes()
self.assertEqual(taxline_1['base'], Decimal('30.00'))
self.assertEqual(taxline_1['amount'], Decimal('3.00'))
self.assertEqual(taxline_2['base'], Decimal('-2.95'))
self.assertEqual(taxline_2['amount'], Decimal('-0.29'))
self.assertEqual(taxline_3['base'], Decimal('-2.95'))
self.assertEqual(taxline_3['amount'], Decimal('-0.30'))
@with_transaction()
def test_tax_compute_with_children_update_unit_price(self):
"Test tax compute with children taxes modifying unit_price"