summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurent Smet <las@odoo.com>2018-09-07 07:14:51 +0000
committerOlivier Colson <oco@odoo.com>2019-05-09 11:56:39 +0000
commit574295df57d8da78ca6109f277a5a2332bf38101 (patch)
tree7a0a55ab0c9af4c176901442241f6d650da01d8f
parent4523a11bed332d7cb1f1846d2ff6b54f83afe430 (diff)
[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
-rw-r--r--addons/account/models/account.py3
-rw-r--r--addons/account/models/account_invoice.py69
-rw-r--r--addons/account/tests/__init__.py1
-rw-r--r--addons/account/tests/test_invoice_taxes.py141
-rw-r--r--addons/account/views/account_invoice_view.xml4
5 files changed, 195 insertions, 23 deletions
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 8bc67fcf1e8..431cd15fbce 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -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
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 28e0a4429ac..e11c31ae6a0 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -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):
diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py
index e87ae79c27a..0eb1d84e613 100644
--- a/addons/account/tests/__init__.py
+++ b/addons/account/tests/__init__.py
@@ -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
diff --git a/addons/account/tests/test_invoice_taxes.py b/addons/account/tests/test_invoice_taxes.py
new file mode 100644
index 00000000000..bb8dd06b617
--- /dev/null
+++ b/addons/account/tests/test_invoice_taxes.py
@@ -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]},
+ ])
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml
index e81ca343db5..b8dd9c25e05 100644
--- a/addons/account/views/account_invoice_view.xml
+++ b/addons/account/views/account_invoice_view.xml
@@ -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"/>