[FIX] account: fix usage of include_base_amount taxes in invoices
Suppose 3 taxes, all with include_base_amount checked: - 21% - 21% TVAC - 12% Suppose an invoice with 3 lines, one per tax, all with quantity = 1: - price_unit = 100 with 21% - price_unit = 121 with 21% TVAC - price_unit = 100 with 12% - Validate the invoice and take a look to the taxes set on taxes journal entries: Originator Tax: 12% Taxes: / Originator Tax: 21% Taxes: 12% Originator Tax: 21% TVAC Taxes: 21% 12% ... instead of: Originator Tax: 12% Taxes: / Originator Tax: 21% Taxes: / Originator Tax: 21% TVAC Taxes: / -opw: 1861177
This commit is contained in:
committed by
Olivier Colson
parent
4523a11bed
commit
574295df57
@@ -1030,7 +1030,8 @@ class AccountTax(models.Model):
|
||||
return str(invoice_tax_val['tax_id']) + '-' + \
|
||||
str(invoice_tax_val['account_id']) + '-' + \
|
||||
str(invoice_tax_val['account_analytic_id']) + '-' + \
|
||||
str(invoice_tax_val.get('analytic_tag_ids', []))
|
||||
str(invoice_tax_val.get('analytic_tag_ids', [])) + '-' + \
|
||||
str(invoice_tax_val.get('tax_ids') or [])
|
||||
|
||||
def _compute_amount(self, base_amount, price_unit, quantity=1.0, product=None, partner=None):
|
||||
""" Returns the amount of a single tax. base_amount is the actual amount on which the tax is applied, which is
|
||||
|
||||
@@ -1077,12 +1077,13 @@ class AccountInvoice(models.Model):
|
||||
else:
|
||||
return self.env.ref('account.invoice_form').id
|
||||
|
||||
def _prepare_tax_line_vals(self, line, tax):
|
||||
""" Prepare values to create an account.invoice.tax line
|
||||
|
||||
The line parameter is an account.invoice.line, and the
|
||||
tax parameter is the output of account.tax.compute_all().
|
||||
"""
|
||||
def _prepare_tax_line_vals(self, line, tax, tax_ids):
|
||||
''' Prepare values to create an account.invoice.tax line.
|
||||
:param line: An account.invoice.line record.
|
||||
:param tax: Tax values outputted by compute_all() in account.tax.
|
||||
:param taxes: A list of account.tax ids affecting the tax base amount.
|
||||
:return: The account.invoice.tax values to create a new record.
|
||||
'''
|
||||
vals = {
|
||||
'invoice_id': self.id,
|
||||
'name': tax['name'],
|
||||
@@ -1094,6 +1095,7 @@ class AccountInvoice(models.Model):
|
||||
'account_analytic_id': tax['analytic'] and line.account_analytic_id.id or False,
|
||||
'account_id': self.type in ('out_invoice', 'in_invoice') and (tax['account_id'] or line.account_id.id) or (tax['refund_account_id'] or line.account_id.id),
|
||||
'analytic_tag_ids': tax['analytic'] and line.analytic_tag_ids.ids or False,
|
||||
'tax_ids': tax_ids and [(6, None, tax_ids)] or False,
|
||||
}
|
||||
|
||||
# If the taxes generate moves on the same financial account as the invoice line,
|
||||
@@ -1106,6 +1108,11 @@ class AccountInvoice(models.Model):
|
||||
|
||||
@api.multi
|
||||
def get_taxes_values(self, tax_group_fields=False):
|
||||
def is_tax_affecting_base_amount(tax):
|
||||
return (tax.amount_type not in ('group', 'division') and tax.include_base_amount and tax.price_include)\
|
||||
or (tax.amount_type == 'division' and tax.include_base_amount and not tax.price_include)
|
||||
# Avoid redundant browsing.
|
||||
tax_map = dict((t.id, t) for t in self.invoice_line_ids.mapped('invoice_line_tax_ids'))
|
||||
default_tax_group_fields = set(['amount', 'base'])
|
||||
if tax_group_fields:
|
||||
default_tax_group_fields |= set(tax_group_fields)
|
||||
@@ -1114,18 +1121,38 @@ class AccountInvoice(models.Model):
|
||||
for line in self.invoice_line_ids:
|
||||
if not line.account_id:
|
||||
continue
|
||||
|
||||
price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
|
||||
taxes = line.invoice_line_tax_ids.compute_all(price_unit, self.currency_id, line.quantity, line.product_id, self.partner_id)['taxes']
|
||||
for tax in taxes:
|
||||
val = self._prepare_tax_line_vals(line, tax)
|
||||
key = self.env['account.tax'].browse(tax['id']).get_grouping_key(val)
|
||||
|
||||
affecting_base_tax_ids = []
|
||||
for tax_vals in taxes:
|
||||
# Retrieve the tax record (not in tax_vals when dealing with group of taxes).
|
||||
if tax_map.get(tax_vals['id']):
|
||||
tax = tax_map[tax_vals['id']]
|
||||
else:
|
||||
tax = tax_map[tax_vals['id']] = self.env['account.tax'].browse(tax_vals['id'])
|
||||
|
||||
val = self._prepare_tax_line_vals(line, tax_vals, affecting_base_tax_ids)
|
||||
key = tax.get_grouping_key({
|
||||
'tax_id': val['tax_id'],
|
||||
'account_id': val['account_id'],
|
||||
'account_analytic_id': val['account_analytic_id'],
|
||||
'analytic_tag_ids': val['analytic_tag_ids'],
|
||||
'tax_ids': affecting_base_tax_ids,
|
||||
})
|
||||
|
||||
if key not in tax_grouped:
|
||||
tax_grouped[key] = val
|
||||
tax_grouped[key]['base'] = round_curr(val['base'])
|
||||
else:
|
||||
for field in default_tax_group_fields:
|
||||
tax_grouped[key][field] += round_curr(val.get(field) or 0)
|
||||
tax_grouped[key][field] += val.get(field) or 0
|
||||
|
||||
if tax.amount_type == "group":
|
||||
affecting_base_tax_ids += tax.children_tax_ids.filtered(lambda t: is_tax_affecting_base_amount(t)).ids
|
||||
elif is_tax_affecting_base_amount(tax):
|
||||
affecting_base_tax_ids.append(tax.id)
|
||||
return tax_grouped
|
||||
|
||||
@api.multi
|
||||
@@ -1232,19 +1259,14 @@ class AccountInvoice(models.Model):
|
||||
res.append(move_line_dict)
|
||||
return res
|
||||
|
||||
@api.model
|
||||
@api.multi
|
||||
def tax_line_move_line_get(self):
|
||||
self.ensure_one()
|
||||
|
||||
res = []
|
||||
# keep track of taxes already processed
|
||||
done_taxes = []
|
||||
# loop the invoice.tax.line in reversal sequence
|
||||
for tax_line in sorted(self.tax_line_ids, key=lambda x: -x.sequence):
|
||||
if tax_line.amount_total:
|
||||
tax = tax_line.tax_id
|
||||
if tax.amount_type == "group":
|
||||
for child_tax in tax.children_tax_ids:
|
||||
done_taxes.append(child_tax.id)
|
||||
|
||||
analytic_tag_ids = [(4, analytic_tag.id, None) for analytic_tag in tax_line.analytic_tag_ids]
|
||||
res.append({
|
||||
'invoice_tax_line_id': tax_line.id,
|
||||
@@ -1258,9 +1280,8 @@ class AccountInvoice(models.Model):
|
||||
'account_analytic_id': tax_line.account_analytic_id.id,
|
||||
'analytic_tag_ids': analytic_tag_ids,
|
||||
'invoice_id': self.id,
|
||||
'tax_ids': [(6, 0, list(done_taxes))] if tax_line.tax_id.include_base_amount else []
|
||||
'tax_ids': [(6, 0, tax_line.tax_ids.ids)],
|
||||
})
|
||||
done_taxes.append(tax.id)
|
||||
return res
|
||||
|
||||
def inv_line_characteristic_hashcode(self, invoice_line):
|
||||
@@ -2034,7 +2055,7 @@ class AccountInvoiceLine(models.Model):
|
||||
class AccountInvoiceTax(models.Model):
|
||||
_name = "account.invoice.tax"
|
||||
_description = "Invoice Tax"
|
||||
_order = 'sequence'
|
||||
_order = 'sequence, id desc'
|
||||
|
||||
def _prepare_invoice_tax_val(self):
|
||||
self.ensure_one()
|
||||
@@ -2042,7 +2063,9 @@ class AccountInvoiceTax(models.Model):
|
||||
'tax_id': self.tax_id.id,
|
||||
'account_id': self.account_id.id,
|
||||
'account_analytic_id': self.account_analytic_id.id,
|
||||
'analytic_tag_ids': self.analytic_tag_ids.ids or False}
|
||||
'analytic_tag_ids': self.analytic_tag_ids.ids or False,
|
||||
'tax_ids': self.tax_ids and tax.tax_ids.ids,
|
||||
}
|
||||
|
||||
@api.depends('invoice_id.invoice_line_ids')
|
||||
def _compute_base_amount(self):
|
||||
@@ -2072,6 +2095,8 @@ class AccountInvoiceTax(models.Model):
|
||||
company_id = fields.Many2one('res.company', string='Company', related='account_id.company_id', store=True, readonly=True)
|
||||
currency_id = fields.Many2one('res.currency', related='invoice_id.currency_id', store=True, readonly=True)
|
||||
base = fields.Monetary(string='Base', compute='_compute_base_amount', store=True)
|
||||
tax_ids = fields.Many2many('account.tax', string='Affecting Base Taxes',
|
||||
help='Taxes affecting the tax base amount applied before this one.')
|
||||
|
||||
@api.depends('amount', 'amount_rounding')
|
||||
def _compute_amount_total(self):
|
||||
|
||||
@@ -16,6 +16,7 @@ from . import test_product_id_change
|
||||
from . import test_reconciliation
|
||||
from . import test_search
|
||||
from . import test_tax
|
||||
from . import test_invoice_taxes
|
||||
from . import test_account_move_taxes_edition
|
||||
from . import test_templates_consistency
|
||||
from . import test_account_fiscal_year
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
|
||||
from odoo.tests.common import Form
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestInvoiceTaxes(AccountingTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestInvoiceTaxes, self).setUp()
|
||||
|
||||
self.percent_tax_1 = self.env['account.tax'].create({
|
||||
'name': '21%',
|
||||
'amount_type': 'percent',
|
||||
'amount': 21,
|
||||
'sequence': 10,
|
||||
})
|
||||
self.percent_tax_1_incl = self.env['account.tax'].create({
|
||||
'name': '21% incl',
|
||||
'amount_type': 'percent',
|
||||
'amount': 21,
|
||||
'price_include': True,
|
||||
'include_base_amount': True,
|
||||
'sequence': 20,
|
||||
})
|
||||
self.percent_tax_2 = self.env['account.tax'].create({
|
||||
'name': '12%',
|
||||
'amount_type': 'percent',
|
||||
'amount': 12,
|
||||
'sequence': 30,
|
||||
})
|
||||
self.group_tax = self.env['account.tax'].create({
|
||||
'name': 'group 12% + 21%',
|
||||
'amount_type': 'group',
|
||||
'amount': 21,
|
||||
'children_tax_ids': [
|
||||
(4, self.percent_tax_1_incl.id),
|
||||
(4, self.percent_tax_2.id)
|
||||
],
|
||||
'sequence': 40,
|
||||
})
|
||||
|
||||
def _create_invoice(self, taxes_per_line):
|
||||
''' Create an invoice on the fly.
|
||||
|
||||
:param taxes_per_line: A list of tuple (price_unit, account.tax recordset)
|
||||
'''
|
||||
self_ctx = self.env['account.invoice'].with_context(type='out_invoice')
|
||||
journal_id = self_ctx._default_journal().id
|
||||
self_ctx = self_ctx.with_context(journal_id=journal_id)
|
||||
|
||||
with Form(self_ctx, view='account.invoice_form') as invoice_form:
|
||||
invoice_form.partner_id = self.env.ref('base.partner_demo')
|
||||
|
||||
for amount, taxes in taxes_per_line:
|
||||
with invoice_form.invoice_line_ids.new() as invoice_line_form:
|
||||
invoice_line_form.name = 'xxxx'
|
||||
invoice_line_form.quantity = 1
|
||||
invoice_line_form.price_unit = amount
|
||||
invoice_line_form.invoice_line_tax_ids.clear()
|
||||
for tax in taxes:
|
||||
invoice_line_form.invoice_line_tax_ids.add(tax)
|
||||
return invoice_form.save()
|
||||
|
||||
def test_one_tax_per_line(self):
|
||||
''' Test:
|
||||
price_unit | Taxes
|
||||
------------------
|
||||
100 | 21%
|
||||
121 | 21% incl
|
||||
100 | 12%
|
||||
|
||||
Expected:
|
||||
Tax | Taxes | Base | Amount
|
||||
--------------------------------------------
|
||||
21% | / | 100 | 21
|
||||
21% incl | / | 100 | 21
|
||||
12% | / | 100 | 12
|
||||
'''
|
||||
invoice = self._create_invoice([
|
||||
(100, self.percent_tax_1),
|
||||
(121, self.percent_tax_1_incl),
|
||||
(100, self.percent_tax_2),
|
||||
])
|
||||
invoice.action_invoice_open()
|
||||
self.assertRecordValues(invoice.tax_line_ids, [
|
||||
{'name': self.percent_tax_1.name, 'base': 100, 'amount': 21, 'tax_ids': []},
|
||||
{'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': []},
|
||||
{'name': self.percent_tax_2.name, 'base': 100, 'amount': 12, 'tax_ids': []},
|
||||
])
|
||||
|
||||
def test_affecting_base_amount(self):
|
||||
''' Test:
|
||||
price_unit | Taxes
|
||||
------------------
|
||||
121 | 21% incl, 12%
|
||||
100 | 12%
|
||||
|
||||
Expected:
|
||||
Tax | Taxes | Base | Amount
|
||||
--------------------------------------------
|
||||
21% incl | / | 100 | 21
|
||||
12% | 21% incl | 121 | 14.52
|
||||
12% | / | 100 | 12
|
||||
'''
|
||||
invoice = self._create_invoice([
|
||||
(121, self.percent_tax_1_incl + self.percent_tax_2),
|
||||
(100, self.percent_tax_2),
|
||||
])
|
||||
invoice.action_invoice_open()
|
||||
self.assertRecordValues(invoice.tax_line_ids, [
|
||||
{'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': []},
|
||||
{'name': self.percent_tax_2.name, 'base': 100, 'amount': 12, 'tax_ids': []},
|
||||
{'name': self.percent_tax_2.name, 'base': 121, 'amount': 14.52, 'tax_ids': [self.percent_tax_1_incl.id]},
|
||||
])
|
||||
|
||||
def test_group_of_taxes(self):
|
||||
''' Test:
|
||||
price_unit | Taxes
|
||||
------------------
|
||||
121 | 21% incl + 12%
|
||||
100 | 12%
|
||||
|
||||
Expected:
|
||||
Tax | Taxes | Base | Amount
|
||||
--------------------------------------------
|
||||
21% incl | / | 100 | 21
|
||||
12% | 21% incl | 121 | 14.52
|
||||
12% | / | 100 | 12
|
||||
'''
|
||||
invoice = self._create_invoice([
|
||||
(121, self.group_tax),
|
||||
(100, self.percent_tax_2),
|
||||
])
|
||||
invoice.action_invoice_open()
|
||||
self.assertRecordValues(invoice.tax_line_ids, [
|
||||
{'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': []},
|
||||
{'name': self.percent_tax_2.name, 'base': 100, 'amount': 12, 'tax_ids': []},
|
||||
{'name': self.percent_tax_2.name, 'base': 121, 'amount': 14.52, 'tax_ids': [self.percent_tax_1_incl.id]},
|
||||
])
|
||||
@@ -124,6 +124,7 @@
|
||||
<field name="name"/>
|
||||
<field name="account_id" groups="account.group_account_user"/>
|
||||
<field name="base"/>
|
||||
<field name="tax_ids"/>
|
||||
<field name="amount_total" string="Amount"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</tree>
|
||||
@@ -143,6 +144,7 @@
|
||||
<field name="manual"/>
|
||||
<field name="amount_total" string="Amount"/>
|
||||
<field name="base"/>
|
||||
<field name="tax_ids"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group>
|
||||
</form>
|
||||
@@ -353,6 +355,7 @@
|
||||
<field name="name"/>
|
||||
<!--Need invisible fields for on_change to create all necessary info -->
|
||||
<field name="tax_id" invisible="1"/>
|
||||
<field name="tax_ids" invisible="1"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="manual" invisible="1"/>
|
||||
<field name="account_id" groups="account.group_account_user"/>
|
||||
@@ -618,6 +621,7 @@
|
||||
<field name="name"/>
|
||||
<!--Need invisible fields for on_change to create all necessary info -->
|
||||
<field name="tax_id" invisible="1"/>
|
||||
<field name="tax_ids" invisible="1"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="manual" invisible="1"/>
|
||||
<field name="account_id" groups="account.group_account_user"/>
|
||||
|
||||
Reference in New Issue
Block a user