diff --git a/modules/account/tax.py b/modules/account/tax.py index 8df933bc68..400db59537 100644 --- a/modules/account/tax.py +++ b/modules/account/tax.py @@ -1,7 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime -from collections import namedtuple +from collections import defaultdict, namedtuple from decimal import Decimal from itertools import cycle, groupby @@ -1192,8 +1192,13 @@ class _TaxKey(dict): return hash(self._key()) -_TaxableLine = namedtuple( - '_TaxableLine', ('taxes', 'unit_price', 'quantity', 'tax_date')) +class _TaxableLine(namedtuple( + '_TaxableLine', + ('taxes', 'unit_price', 'quantity', 'tax_date'))): + @property + def _key(self): + base = self.unit_price * Decimal(str(self.quantity or 0)) + return (tuple(self.taxes), base >= 0) class TaxableMixin(object): @@ -1266,34 +1271,43 @@ class TaxableMixin(object): Tax = pool.get('account.tax') Configuration = pool.get('account.configuration') - taxes = {} + all_taxes = {} with Transaction().set_context(self._get_tax_context()): config = Configuration(1) tax_rounding = config.get_multivalue('tax_rounding') - taxable_lines = [_TaxableLine(*params) - for params in self.taxable_lines] - for line in taxable_lines: - 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 - # tax line per taxable_lines - if self.currency: - taxline['base'] = self.currency.round(taxline['base']) - if taxline not in taxes: - taxes[taxline] = taxline - else: - taxes[taxline]['base'] += taxline['base'] - taxes[taxline]['amount'] += taxline['amount'] - current_taxes[taxline] = taxes[taxline] - if tax_rounding == 'line': - self._round_taxes(current_taxes) - if tax_rounding == 'document': - self._round_taxes(taxes) - return taxes + taxable_lines = defaultdict(list) + for params in self.taxable_lines: + taxable_line = _TaxableLine(*params) + taxable_lines[taxable_line._key].append(taxable_line) + for grouped_taxable_lines in taxable_lines.values(): + taxes = {} + for line in grouped_taxable_lines: + 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 tax line per taxable_lines + if self.currency: + taxline['base'] = self.currency.round( + taxline['base']) + if taxline not in taxes: + taxes[taxline] = taxline + else: + taxes[taxline]['base'] += taxline['base'] + taxes[taxline]['amount'] += taxline['amount'] + current_taxes[taxline] = taxes[taxline] + if tax_rounding == 'line': + self._round_taxes(current_taxes) + if tax_rounding == 'document': + self._round_taxes(taxes) + if all_taxes.keys() & taxes.keys(): + raise ValueError("Duplicate tax line keys") + all_taxes.update(taxes) + return all_taxes class TaxLine(ModelSQL, ModelView): diff --git a/modules/account/tests/test_module.py b/modules/account/tests/test_module.py index 7998a1a778..aa188f9120 100644 --- a/modules/account/tests/test_module.py +++ b/modules/account/tests/test_module.py @@ -1457,6 +1457,50 @@ class AccountTestCase( self.assertEqual(taxline_3['base'], Decimal('-2.95')) self.assertEqual(taxline_3['amount'], Decimal('-0.30')) + @with_transaction() + def test_taxable_mixin_tax_residual_rounding_per_same_taxes(self): + """Test TaxableMixin for rounding with residual amount only for same + taxes""" + 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 = 'document' + config.save() + + tax_account, = Account.search([ + ('code', '=', '6.3.6'), + ]) + + tax = Tax() + tax.name = tax.description = "Tax" + tax.type = 'percentage' + tax.rate = Decimal('.06') + tax.invoice_account = tax_account + tax.credit_note_account = tax_account + tax.save() + + taxable = self.Taxable( + currency=currency, + taxable_lines=[ + ([tax], Decimal('11.2736'), 1.04, None), + ([tax], Decimal('0.4529'), -1, None), + ], + company=company) + + taxline_1, taxline_2 = taxable._get_taxes() + self.assertEqual(taxline_1['base'], Decimal('11.72')) + self.assertEqual(taxline_1['amount'], Decimal('0.7')) + self.assertEqual(taxline_2['base'], Decimal('-0.45')) + self.assertEqual(taxline_2['amount'], Decimal('-0.03')) + @with_transaction() def test_tax_compute_with_children_update_unit_price(self): "Test tax compute with children taxes modifying unit_price"