[FIX] account: correct management of groups of taxes on misc operations

Using group of taxes with type_tax_use='none' children messed up the sign of the tax_base_amount and line tags.

closes odoo/odoo#63479

X-original-commit: 3642c429899c4d5def27c9ba10ae76c763a8a566
Signed-off-by: oco-odoo <oco-odoo@users.noreply.github.com>
This commit is contained in:
Jigar Vaghela
2020-12-16 17:46:30 +00:00
committed by oco-odoo
parent 79fc263373
commit 29c82528d0
3 changed files with 147 additions and 7 deletions
+6 -4
View File
@@ -593,7 +593,7 @@ class AccountMove(models.Model):
if move.move_type == 'entry':
repartition_field = is_refund and 'refund_repartition_line_ids' or 'invoice_repartition_line_ids'
repartition_tags = base_line.tax_ids.mapped(repartition_field).filtered(lambda x: x.repartition_type == 'base').tag_ids
repartition_tags = base_line.tax_ids.flatten_taxes_hierarchy().mapped(repartition_field).filtered(lambda x: x.repartition_type == 'base').tag_ids
return balance_taxes_res
@@ -647,7 +647,7 @@ class AccountMove(models.Model):
'grouping_dict': False,
})
taxes_map_entry['amount'] += tax_vals['amount']
taxes_map_entry['tax_base_amount'] += self._get_base_amount_to_display(tax_vals['base'], tax_repartition_line)
taxes_map_entry['tax_base_amount'] += self._get_base_amount_to_display(tax_vals['base'], tax_repartition_line, tax_vals['group'])
taxes_map_entry['grouping_dict'] = grouping_dict
line.tax_exigible = tax_exigible
@@ -713,12 +713,14 @@ class AccountMove(models.Model):
taxes_map_entry['tax_line'].update(taxes_map_entry['tax_line']._get_fields_onchange_balance(force_computation=True))
@api.model
def _get_base_amount_to_display(self, base_amount, tax_rep_ln):
def _get_base_amount_to_display(self, base_amount, tax_rep_ln, parent_tax_group=None):
""" The base amount returned for taxes by compute_all has is the balance
of the base line. For inbound operations, positive sign is on credit, so
we need to invert the sign of this amount before displaying it.
"""
if tax_rep_ln.invoice_tax_id.type_tax_use == 'sale' or tax_rep_ln.refund_tax_id.type_tax_use == 'purchase':
source_tax = parent_tax_group or tax_rep_ln.invoice_tax_id or tax_rep_ln.refund_tax_id
if (tax_rep_ln.invoice_tax_id and source_tax.type_tax_use == 'sale') \
or (tax_rep_ln.refund_tax_id and source_tax.type_tax_use == 'purchase'):
return -base_amount
return base_amount
+14 -3
View File
@@ -299,17 +299,27 @@ class AccountTax(models.Model):
return rslt
def flatten_taxes_hierarchy(self):
def flatten_taxes_hierarchy(self, create_map=False):
# Flattens the taxes contained in this recordset, returning all the
# children at the bottom of the hierarchy, in a recordset, ordered by sequence.
# Eg. considering letters as taxes and alphabetic order as sequence :
# [G, B([A, D, F]), E, C] will be computed as [A, D, F, C, E, G]
# If create_map is True, an additional value is returned, a dictionary
# mapping each child tax to its parent group
all_taxes = self.env['account.tax']
groups_map = {}
for tax in self.sorted(key=lambda r: r.sequence):
if tax.amount_type == 'group':
all_taxes += tax.children_tax_ids.flatten_taxes_hierarchy()
flattened_children = tax.children_tax_ids.flatten_taxes_hierarchy()
all_taxes += flattened_children
for flat_child in flattened_children:
groups_map[flat_child] = tax
else:
all_taxes += tax
if create_map:
return all_taxes, groups_map
return all_taxes
def get_tax_tags(self, is_refund, repartition_type):
@@ -345,7 +355,7 @@ class AccountTax(models.Model):
company = self[0].company_id
# 1) Flatten the taxes.
taxes = self.flatten_taxes_hierarchy()
taxes, groups_map = self.flatten_taxes_hierarchy(create_map=True)
# 2) Avoid mixing taxes having price_include=False && include_base_amount=True
# with taxes having price_include=True. This use case is not supported as the
@@ -560,6 +570,7 @@ class AccountTax(models.Model):
'price_include': price_include,
'tax_exigibility': tax.tax_exigibility,
'tax_repartition_line_id': repartition_line.id,
'group': groups_map.get(tax),
'tag_ids': (repartition_line.tag_ids + subsequent_tags).ids,
'tax_ids': subsequent_taxes.ids,
})
+127
View File
@@ -491,6 +491,133 @@ class TestInvoiceTaxes(AccountTestInvoicingCommon):
{'balance': 1100.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 0, 'tax_repartition_line_id': False, 'tax_tag_invert': False},
])
def test_misc_entry_tax_group_signs(self):
""" Tests sign inversion of the tags on misc operations made with tax
groups.
"""
def _create_group_of_taxes(tax_type):
# We use asymmetric tags between the child taxes to avoid shadowing errors
child1_sale_tax = self.env['account.tax'].create({
'sequence': 1,
'name': 'child1_%s' % tax_type,
'type_tax_use': 'none',
'amount_type': 'percent',
'amount': 5,
'invoice_repartition_line_ids': [
(0, 0, {
'repartition_type': 'base',
'factor_percent': 100.0,
'tag_ids': [(6, 0, self.base_tag_pos.ids)],
}),
(0, 0, {
'repartition_type': 'tax',
'factor_percent': 100.0,
'tag_ids': [(6, 0, self.tax_tag_pos.ids)],
}),
],
'refund_repartition_line_ids': [
(0, 0, {
'repartition_type': 'base',
'factor_percent': 100.0,
}),
(0, 0, {
'repartition_type': 'tax',
'factor_percent': 100.0,
}),
],
})
child2_sale_tax = self.env['account.tax'].create({
'sequence': 2,
'name': 'child2_%s' % tax_type,
'type_tax_use': 'none',
'amount_type': 'percent',
'amount': 10,
'invoice_repartition_line_ids': [
(0, 0, {
'repartition_type': 'base',
'factor_percent': 100.0,
}),
(0, 0, {
'repartition_type': 'tax',
'factor_percent': 100.0,
}),
],
'refund_repartition_line_ids': [
(0, 0, {
'repartition_type': 'base',
'factor_percent': 100.0,
'tag_ids': [(6, 0, self.base_tag_neg.ids)],
}),
(0, 0, {
'repartition_type': 'tax',
'factor_percent': 100.0,
'tag_ids': [(6, 0, self.tax_tag_neg.ids)],
}),
],
})
return self.env['account.tax'].create({
'name': 'group_%s' % tax_type,
'type_tax_use': tax_type,
'amount_type': 'group',
'amount': 10,
'children_tax_ids':[(6,0,[child1_sale_tax.id, child2_sale_tax.id])]
})
def _create_misc_operation(tax, tax_field):
with Form(self.env['account.move'], view='account.view_move_form') as move_form:
for line_field in ('debit', 'credit'):
line_amount = tax_field == line_field and 1000 or 1150
with move_form.line_ids.new() as line_form:
line_form.name = '%s_line' % line_field
line_form.account_id = self.company_data['default_account_revenue']
line_form.debit = line_field == 'debit' and line_amount or 0
line_form.credit = line_field == 'credit' and line_amount or 0
if tax_field == line_field:
line_form.tax_ids.clear()
line_form.tax_ids.add(tax)
return move_form.save()
sale_group = _create_group_of_taxes('sale')
purchase_group = _create_group_of_taxes('purchase')
# Sale tax on debit: use refund repartition
debit_sale_move = _create_misc_operation(sale_group, 'debit')
self.assertRecordValues(debit_sale_move.line_ids.sorted('balance'), [
{'balance': -1150.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 0},
{'balance': 50.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 1000},
{'balance': 100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag_neg.ids, 'tax_base_amount': 1000},
{'balance': 1000.0, 'tax_ids': sale_group.ids, 'tax_tag_ids': self.base_tag_neg.ids, 'tax_base_amount': 0},
])
# Sale tax on credit: use invoice repartition
credit_sale_move = _create_misc_operation(sale_group, 'credit')
self.assertRecordValues(credit_sale_move.line_ids.sorted('balance'), [
{'balance': -1000.0, 'tax_ids': sale_group.ids, 'tax_tag_ids': self.base_tag_pos.ids, 'tax_base_amount': 0},
{'balance': -100.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 1000},
{'balance': -50.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag_pos.ids, 'tax_base_amount': 1000},
{'balance': 1150.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 0},
])
# Purchase tax on debit: use invoice repartition
debit_purchase_move = _create_misc_operation(purchase_group, 'debit')
self.assertRecordValues(debit_purchase_move.line_ids.sorted('balance'), [
{'balance': -1150.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 0},
{'balance': 50.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag_pos.ids, 'tax_base_amount': 1000},
{'balance': 100.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 1000},
{'balance': 1000.0, 'tax_ids': purchase_group.ids, 'tax_tag_ids': self.base_tag_pos.ids, 'tax_base_amount': 0},
])
# Purchase tax on credit: use refund repartition
credit_purchase_move = _create_misc_operation(purchase_group, 'credit')
self.assertRecordValues(credit_purchase_move.line_ids.sorted('balance'), [
{'balance': -1000.0, 'tax_ids': purchase_group.ids, 'tax_tag_ids': self.base_tag_neg.ids, 'tax_base_amount': 0},
{'balance': -100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag_neg.ids, 'tax_base_amount': 1000},
{'balance': -50.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 1000},
{'balance': 1150.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 0},
])
def test_tax_calculation_foreign_currency_large_quantity(self):
''' Test:
Foreign currency with rate of 1.1726 and tax of 21%