Consider the following situation: 1) Create a company in USD 2) Have an exchange rate of 1 EUR = 2 USD 3) Create a 1/3 tax 4) Make an invoice of one line of 100 EUR on which the tax is applied => In 15.0, this created: - a base line of 200 USD = 100 EUR - a tax line of 66.66 USD = 33.33 EUR => In 16.0, this creates: - a base line of 200 USD = 100 EUR - a tax line of 66.67 USD = 33.33 EUR This is due to the fact 16.0 computes the tax amounts twice: once in domestic currency, and once in foreign currency (while 15.0 computed everything in domestic, rounded, then converted). So, it does 100/3 = 33.33333333, rounding it to 33.33 and 200/3 = 66.6666666, rounded to 66.67. The problem with the way v16 does it is that the ratio amount_currency/balance becomes inaccurate to recompute the exchange rate, due to the increase of the rounding difference (indeed 33.33/66.67=0,499925004 , wich is pretty far from the original 0.5). Because of that, when using a cash basis taxe of 1/3 on the exact same setup as in our example, instead of getting a base of 200, a base of 200.01 is computed on the cash basis entry (and hence used by the reports), even though the rate hasn't changed a bit. To fix that, we now compute the tax amount directly in foreign currency, round it accordingly, and only then, convert it to domestic currency. Doing that, our test scenario gives the following: 200/3 = 66.66666 => rounded to 66.67 => 66.67/2 = 33.335 => rounded to 33.34 The lines become: - a base line of 200 USD = 100 EUR - a tax line of 66.67 USD = 33.34 EUR With this, the tax amount on the invoice is now consistent with the currency rate: 66.67/2 = 33.335, which gets rounded to 33.34. The currency rate computed by the cash basis thus becomes 33.34/66.67 = 0.5001; way closer to the original rate, and not causing a significative enough rounding error to impact the base amount anymore. X-original-commit: 68fe20f612b8da003415a061b18bb0464329dba9 Part-of: odoo/odoo#104564
702 lines
32 KiB
Python
702 lines
32 KiB
Python
# -*- coding: utf-8 -*-
|
|
# pylint: disable=C0326
|
|
from odoo import Command
|
|
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
|
from odoo.tests import tagged, Form
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestInvoiceTaxes(AccountTestInvoicingCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls, chart_template_ref=None):
|
|
super().setUpClass(chart_template_ref=chart_template_ref)
|
|
|
|
cls.company_data['company'].country_id = cls.env.ref('base.us')
|
|
|
|
cls.percent_tax_1 = cls.env['account.tax'].create({
|
|
'name': '21%',
|
|
'amount_type': 'percent',
|
|
'amount': 21,
|
|
'sequence': 10,
|
|
})
|
|
cls.percent_tax_1_incl = cls.env['account.tax'].create({
|
|
'name': '21% incl',
|
|
'amount_type': 'percent',
|
|
'amount': 21,
|
|
'price_include': True,
|
|
'include_base_amount': True,
|
|
'sequence': 20,
|
|
})
|
|
cls.percent_tax_2 = cls.env['account.tax'].create({
|
|
'name': '12%',
|
|
'amount_type': 'percent',
|
|
'amount': 12,
|
|
'sequence': 30,
|
|
})
|
|
cls.percent_tax_3_incl = cls.env['account.tax'].create({
|
|
'name': '5% incl',
|
|
'amount_type': 'percent',
|
|
'amount': 5,
|
|
'price_include': True,
|
|
'include_base_amount': True,
|
|
'sequence': 40,
|
|
})
|
|
cls.group_tax = cls.env['account.tax'].create({
|
|
'name': 'group 12% + 21%',
|
|
'amount_type': 'group',
|
|
'amount': 21,
|
|
'children_tax_ids': [
|
|
(4, cls.percent_tax_1_incl.id),
|
|
(4, cls.percent_tax_2.id)
|
|
],
|
|
'sequence': 40,
|
|
})
|
|
|
|
tax_report = cls.env['account.report'].create({
|
|
'name': "Tax report",
|
|
'country_id': cls.company_data['company'].country_id.id,
|
|
'column_ids': [
|
|
Command.create({
|
|
'name': "Balance",
|
|
'expression_label': 'balance',
|
|
}),
|
|
],
|
|
})
|
|
|
|
tax_report_line = cls.env['account.report.line'].create({
|
|
'name': 'test_tax_report_line',
|
|
'report_id': tax_report.id,
|
|
'sequence': 10,
|
|
'expression_ids': [
|
|
Command.create({
|
|
'label': 'balance',
|
|
'engine': 'tax_tags',
|
|
'formula': 'test_tax_report_line',
|
|
}),
|
|
],
|
|
})
|
|
tax_tags = tax_report_line.expression_ids._get_matching_tags()
|
|
cls.tax_tag_pos, cls.tax_tag_neg = tax_tags.sorted('tax_negate')
|
|
|
|
base_report_line = cls.env['account.report.line'].create({
|
|
'name': 'base_test_tax_report_line',
|
|
'report_id': tax_report.id,
|
|
'sequence': 10,
|
|
'expression_ids': [
|
|
Command.create({
|
|
'label': 'balance',
|
|
'engine': 'tax_tags',
|
|
'formula': 'base_test_tax_report_line',
|
|
}),
|
|
],
|
|
})
|
|
base_tags = base_report_line.expression_ids._get_matching_tags()
|
|
cls.base_tag_pos, cls.base_tag_neg = base_tags.sorted('tax_negate')
|
|
|
|
def _create_invoice(self, taxes_per_line, inv_type='out_invoice', currency_id=False, invoice_payment_term_id=False):
|
|
''' Create an invoice on the fly.
|
|
|
|
:param taxes_per_line: A list of tuple (price_unit, account.tax recordset)
|
|
'''
|
|
vals = {
|
|
'move_type': inv_type,
|
|
'partner_id': self.partner_a.id,
|
|
'invoice_line_ids': [(0, 0, {
|
|
'name': 'xxxx',
|
|
'quantity': 1,
|
|
'price_unit': amount,
|
|
'tax_ids': [(6, 0, taxes.ids)],
|
|
}) for amount, taxes in taxes_per_line],
|
|
}
|
|
if currency_id:
|
|
vals['currency_id'] = currency_id.id
|
|
if invoice_payment_term_id:
|
|
vals['invoice_payment_term_id'] = invoice_payment_term_id.id
|
|
return self.env['account.move'].create(vals)
|
|
|
|
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_post()
|
|
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id').sorted(lambda x: x.name), [
|
|
{'name': self.percent_tax_2.name, 'tax_base_amount': 100, 'balance': -12, 'tax_ids': []},
|
|
{'name': self.percent_tax_1.name, 'tax_base_amount': 100, 'balance': -21, 'tax_ids': []},
|
|
{'name': self.percent_tax_1_incl.name, 'tax_base_amount': 100, 'balance': -21, '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 | 12% | 100 | 21
|
|
12% | / | 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_post()
|
|
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id').sorted(lambda x: -x.balance), [
|
|
{'name': self.percent_tax_1_incl.name, 'tax_base_amount': 100, 'balance': -21, 'tax_ids': [self.percent_tax_2.id]},
|
|
{'name': self.percent_tax_2.name, 'tax_base_amount': 221, 'balance': -26.52, 'tax_ids': []},
|
|
])
|
|
|
|
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_post()
|
|
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id').sorted('balance'), [
|
|
{'name': self.percent_tax_1_incl.name, 'tax_base_amount': 100.0, 'balance': -21.0, 'tax_ids': [self.percent_tax_2.id]},
|
|
{'name': self.percent_tax_2.name, 'tax_base_amount': 121.0, 'balance': -14.52, 'tax_ids': []},
|
|
{'name': self.percent_tax_2.name, 'tax_base_amount': 100.0, 'balance': -12.0, 'tax_ids': []},
|
|
])
|
|
|
|
def _create_tax_tag(self, tag_name):
|
|
return self.env['account.account.tag'].create({
|
|
'name': tag_name,
|
|
'applicability': 'taxes',
|
|
'country_id': self.env.company.country_id.id,
|
|
})
|
|
|
|
def test_tax_repartition(self):
|
|
inv_base_tag = self._create_tax_tag('invoice_base')
|
|
inv_tax_tag_10 = self._create_tax_tag('invoice_tax_10')
|
|
inv_tax_tag_90 = self._create_tax_tag('invoice_tax_90')
|
|
ref_base_tag = self._create_tax_tag('refund_base')
|
|
ref_tax_tag = self._create_tax_tag('refund_tax')
|
|
|
|
account_1 = self.env['account.account'].create({'name': 'test1', 'code': 'test1', 'account_type': 'asset_current'})
|
|
account_2 = self.env['account.account'].create({'name': 'test2', 'code': 'test2', 'account_type': 'asset_current'})
|
|
|
|
tax = self.env['account.tax'].create({
|
|
'name': "Tax with account",
|
|
'amount_type': 'fixed',
|
|
'type_tax_use': 'sale',
|
|
'amount': 42,
|
|
'invoice_repartition_line_ids': [
|
|
(0,0, {
|
|
'repartition_type': 'base',
|
|
'tag_ids': [(4, inv_base_tag.id, 0)],
|
|
}),
|
|
|
|
(0,0, {
|
|
'factor_percent': 10,
|
|
'repartition_type': 'tax',
|
|
'account_id': account_1.id,
|
|
'tag_ids': [(4, inv_tax_tag_10.id, 0)],
|
|
}),
|
|
|
|
(0,0, {
|
|
'factor_percent': 90,
|
|
'repartition_type': 'tax',
|
|
'account_id': account_2.id,
|
|
'tag_ids': [(4, inv_tax_tag_90.id, 0)],
|
|
}),
|
|
],
|
|
'refund_repartition_line_ids': [
|
|
(0,0, {
|
|
'repartition_type': 'base',
|
|
'tag_ids': [(4, ref_base_tag.id, 0)],
|
|
}),
|
|
|
|
(0,0, {
|
|
'factor_percent': 10,
|
|
'repartition_type': 'tax',
|
|
'tag_ids': [(4, ref_tax_tag.id, 0)],
|
|
}),
|
|
|
|
(0,0, {
|
|
'factor_percent': 90,
|
|
'repartition_type': 'tax',
|
|
'account_id': account_1.id,
|
|
'tag_ids': [(4, ref_tax_tag.id, 0)],
|
|
}),
|
|
],
|
|
})
|
|
|
|
# Test invoice repartition
|
|
invoice = self._create_invoice([(100, tax)], inv_type='out_invoice')
|
|
invoice.action_post()
|
|
|
|
self.assertEqual(len(invoice.line_ids), 4, "There should be 4 account move lines created for the invoice: payable, base and 2 tax lines")
|
|
inv_base_line = invoice.line_ids.filtered(lambda x: not x.tax_repartition_line_id and x.account_id.account_type != 'asset_receivable')
|
|
self.assertEqual(len(inv_base_line), 1, "There should be only one base line generated")
|
|
self.assertEqual(abs(inv_base_line.balance), 100, "Base amount should be 100")
|
|
self.assertEqual(inv_base_line.tax_tag_ids, inv_base_tag, "Base line should have received base tag")
|
|
inv_tax_lines = invoice.line_ids.filtered(lambda x: x.tax_repartition_line_id.repartition_type == 'tax')
|
|
self.assertEqual(len(inv_tax_lines), 2, "There should be two tax lines, one for each repartition line.")
|
|
self.assertEqual(abs(inv_tax_lines.filtered(lambda x: x.account_id == account_1).balance), 4.2, "Tax line on account 1 should amount to 4.2 (10% of 42)")
|
|
self.assertEqual(inv_tax_lines.filtered(lambda x: x.account_id == account_1).tax_tag_ids, inv_tax_tag_10, "Tax line on account 1 should have 10% tag")
|
|
self.assertAlmostEqual(abs(inv_tax_lines.filtered(lambda x: x.account_id == account_2).balance), 37.8, 2, "Tax line on account 2 should amount to 37.8 (90% of 42)")
|
|
self.assertEqual(inv_tax_lines.filtered(lambda x: x.account_id == account_2).tax_tag_ids, inv_tax_tag_90, "Tax line on account 2 should have 90% tag")
|
|
|
|
# Test refund repartition
|
|
refund = self._create_invoice([(100, tax)], inv_type='out_refund')
|
|
refund.action_post()
|
|
|
|
self.assertEqual(len(refund.line_ids), 4, "There should be 4 account move lines created for the refund: payable, base and 2 tax lines")
|
|
ref_base_line = refund.line_ids.filtered(lambda x: not x.tax_repartition_line_id and x.account_id.account_type != 'asset_receivable')
|
|
self.assertEqual(len(ref_base_line), 1, "There should be only one base line generated")
|
|
self.assertEqual(abs(ref_base_line.balance), 100, "Base amount should be 100")
|
|
self.assertEqual(ref_base_line.tax_tag_ids, ref_base_tag, "Base line should have received base tag")
|
|
ref_tax_lines = refund.line_ids.filtered(lambda x: x.tax_repartition_line_id.repartition_type == 'tax')
|
|
self.assertEqual(len(ref_tax_lines), 2, "There should be two refund tax lines")
|
|
self.assertEqual(abs(ref_tax_lines.filtered(lambda x: x.account_id == ref_base_line.account_id).balance), 4.2, "Refund tax line on base account should amount to 4.2 (10% of 42)")
|
|
self.assertAlmostEqual(abs(ref_tax_lines.filtered(lambda x: x.account_id == account_1).balance), 37.8, 2, "Refund tax line on account 1 should amount to 37.8 (90% of 42)")
|
|
self.assertEqual(ref_tax_lines.mapped('tax_tag_ids'), ref_tax_tag, "Refund tax lines should have the right tag")
|
|
|
|
def test_division_tax(self):
|
|
'''
|
|
Test that when using division tax, with percentage amount
|
|
100% any change on price unit is correctly reflected on
|
|
the whole move.
|
|
|
|
Complete scenario:
|
|
- Create a division tax, 100% amount, included in price.
|
|
- Create an invoice, with only the mentioned tax
|
|
- Change price_unit of the aml
|
|
- Total price of the move should change as well
|
|
'''
|
|
|
|
sale_tax = self.env['account.tax'].create({
|
|
'name': 'tax',
|
|
'type_tax_use': 'sale',
|
|
'amount_type': 'division',
|
|
'amount': 100,
|
|
'price_include': True,
|
|
'include_base_amount': True,
|
|
})
|
|
invoice = self._create_invoice([(100, sale_tax)])
|
|
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id'), [{
|
|
'name': sale_tax.name,
|
|
'tax_base_amount': 0.0,
|
|
'balance': -100,
|
|
}])
|
|
# change price unit, everything should change as well
|
|
with Form(invoice) as invoice_form:
|
|
with invoice_form.invoice_line_ids.edit(0) as line_edit:
|
|
line_edit.price_unit = 200
|
|
|
|
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id'), [{
|
|
'name': sale_tax.name,
|
|
'tax_base_amount': 0.0,
|
|
'balance': -200,
|
|
}])
|
|
|
|
def test_misc_journal_entry_tax_tags_sale(self):
|
|
sale_tax = self.env['account.tax'].create({
|
|
'name': 'tax',
|
|
'type_tax_use': 'sale',
|
|
'amount_type': 'percent',
|
|
'amount': 10,
|
|
'invoice_repartition_line_ids': [
|
|
(0, 0, {
|
|
'repartition_type': 'base',
|
|
'tag_ids': [(6, 0, self.base_tag_pos.ids)],
|
|
}),
|
|
(0, 0, {
|
|
'repartition_type': 'tax',
|
|
'tag_ids': [(6, 0, self.tax_tag_pos.ids)],
|
|
}),
|
|
],
|
|
'refund_repartition_line_ids': [
|
|
(0, 0, {
|
|
'repartition_type': 'base',
|
|
'tag_ids': [(6, 0, self.base_tag_neg.ids)],
|
|
}),
|
|
(0, 0, {
|
|
'repartition_type': 'tax',
|
|
'tag_ids': [(6, 0, self.tax_tag_neg.ids)],
|
|
}),
|
|
],
|
|
})
|
|
|
|
inv_tax_rep_ln = sale_tax.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax')
|
|
ref_tax_rep_ln = sale_tax.refund_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax')
|
|
|
|
# === Tax in debit ===
|
|
|
|
move_form = Form(self.env['account.move'], view='account.view_move_form')
|
|
move_form.ref = 'azerty'
|
|
|
|
# Debit base tax line.
|
|
with move_form.line_ids.new() as credit_line:
|
|
credit_line.name = 'debit_line_1'
|
|
credit_line.account_id = self.company_data['default_account_revenue']
|
|
credit_line.debit = 1000.0
|
|
credit_line.tax_ids.clear()
|
|
credit_line.tax_ids.add(sale_tax)
|
|
|
|
|
|
# Balance the journal entry.
|
|
with move_form.line_ids.new() as credit_line:
|
|
credit_line.name = 'balance'
|
|
credit_line.account_id = self.company_data['default_account_revenue']
|
|
credit_line.credit = 1100.0
|
|
|
|
move = move_form.save()
|
|
|
|
self.assertRecordValues(move.line_ids.sorted('balance'), [
|
|
{'balance': -1100.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 0, 'tax_repartition_line_id': False, 'tax_tag_invert': False},
|
|
{'balance': 100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag_neg.ids, 'tax_base_amount': 1000, 'tax_repartition_line_id': ref_tax_rep_ln.id, 'tax_tag_invert': False},
|
|
{'balance': 1000.0, 'tax_ids': sale_tax.ids, 'tax_tag_ids': self.base_tag_neg.ids, 'tax_base_amount': 0, 'tax_repartition_line_id': False, 'tax_tag_invert': False},
|
|
])
|
|
|
|
# === Tax in credit ===
|
|
|
|
move_form = Form(self.env['account.move'], view='account.view_move_form')
|
|
move_form.ref = 'azerty'
|
|
|
|
# Debit base tax line.
|
|
with move_form.line_ids.new() as credit_line:
|
|
credit_line.name = 'debit_line_1'
|
|
credit_line.account_id = self.company_data['default_account_revenue']
|
|
credit_line.credit = 1000.0
|
|
credit_line.tax_ids.clear()
|
|
credit_line.tax_ids.add(sale_tax)
|
|
|
|
# Balance the journal entry.
|
|
with move_form.line_ids.new() as debit_line:
|
|
debit_line.name = 'balance'
|
|
debit_line.account_id = self.company_data['default_account_revenue']
|
|
debit_line.debit = 1100.0
|
|
|
|
move = move_form.save()
|
|
|
|
self.assertRecordValues(move.line_ids.sorted('balance'), [
|
|
{'balance': -1000.0, 'tax_ids': sale_tax.ids, 'tax_tag_ids': self.base_tag_pos.ids, 'tax_base_amount': 0, 'tax_repartition_line_id': False, 'tax_tag_invert': True},
|
|
{'balance': -100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag_pos.ids, 'tax_base_amount': 1000, 'tax_repartition_line_id': inv_tax_rep_ln.id, 'tax_tag_invert': True},
|
|
{'balance': 1100.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 0, 'tax_repartition_line_id': False, 'tax_tag_invert': False},
|
|
])
|
|
|
|
def test_misc_journal_entry_tax_tags_purchase(self):
|
|
purch_tax = self.env['account.tax'].create({
|
|
'name': 'tax',
|
|
'type_tax_use': 'purchase',
|
|
'amount_type': 'percent',
|
|
'amount': 10,
|
|
'invoice_repartition_line_ids': [
|
|
(0, 0, {
|
|
'repartition_type': 'base',
|
|
'tag_ids': [(6, 0, self.base_tag_pos.ids)],
|
|
}),
|
|
(0, 0, {
|
|
'repartition_type': 'tax',
|
|
'tag_ids': [(6, 0, self.tax_tag_pos.ids)],
|
|
}),
|
|
],
|
|
'refund_repartition_line_ids': [
|
|
(0, 0, {
|
|
'repartition_type': 'base',
|
|
'tag_ids': [(6, 0, self.base_tag_neg.ids)],
|
|
}),
|
|
(0, 0, {
|
|
'repartition_type': 'tax',
|
|
'tag_ids': [(6, 0, self.tax_tag_neg.ids)],
|
|
}),
|
|
],
|
|
})
|
|
|
|
inv_tax_rep_ln = purch_tax.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax')
|
|
ref_tax_rep_ln = purch_tax.refund_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax')
|
|
|
|
# === Tax in debit ===
|
|
|
|
move_form = Form(self.env['account.move'])
|
|
move_form.ref = 'azerty'
|
|
|
|
# Debit base tax line.
|
|
with move_form.line_ids.new() as credit_line:
|
|
credit_line.name = 'debit_line_1'
|
|
credit_line.account_id = self.company_data['default_account_revenue']
|
|
credit_line.debit = 1000.0
|
|
credit_line.tax_ids.clear()
|
|
credit_line.tax_ids.add(purch_tax)
|
|
|
|
# Balance the journal entry.
|
|
with move_form.line_ids.new() as credit_line:
|
|
credit_line.name = 'balance'
|
|
credit_line.account_id = self.company_data['default_account_revenue']
|
|
credit_line.credit = 1100.0
|
|
|
|
move = move_form.save()
|
|
|
|
self.assertRecordValues(move.line_ids.sorted('balance'), [
|
|
{'balance': -1100.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 0, 'tax_repartition_line_id': False, 'tax_tag_invert': False},
|
|
{'balance': 100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag_pos.ids, 'tax_base_amount': 1000, 'tax_repartition_line_id': inv_tax_rep_ln.id, 'tax_tag_invert': False},
|
|
{'balance': 1000.0, 'tax_ids': purch_tax.ids, 'tax_tag_ids': self.base_tag_pos.ids, 'tax_base_amount': 0, 'tax_repartition_line_id': False, 'tax_tag_invert': False},
|
|
])
|
|
|
|
# === Tax in credit ===
|
|
|
|
move_form = Form(self.env['account.move'])
|
|
move_form.ref = 'azerty'
|
|
|
|
# Debit base tax line.
|
|
with move_form.line_ids.new() as credit_line:
|
|
credit_line.name = 'debit_line_1'
|
|
credit_line.account_id = self.company_data['default_account_revenue']
|
|
credit_line.credit = 1000.0
|
|
credit_line.tax_ids.clear()
|
|
credit_line.tax_ids.add(purch_tax)
|
|
|
|
# Balance the journal entry.
|
|
with move_form.line_ids.new() as debit_line:
|
|
debit_line.name = 'balance'
|
|
debit_line.account_id = self.company_data['default_account_revenue']
|
|
debit_line.debit = 1100.0
|
|
|
|
move = move_form.save()
|
|
|
|
self.assertRecordValues(move.line_ids.sorted('balance'), [
|
|
{'balance': -1000.0, 'tax_ids': purch_tax.ids, 'tax_tag_ids': self.base_tag_neg.ids, 'tax_base_amount': 0, 'tax_repartition_line_id': False, 'tax_tag_invert': True},
|
|
{'balance': -100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag_neg.ids, 'tax_base_amount': 1000, 'tax_repartition_line_id': ref_tax_rep_ln.id, 'tax_tag_invert': True},
|
|
{'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',
|
|
'tag_ids': [(6, 0, self.base_tag_pos.ids)],
|
|
}),
|
|
(0, 0, {
|
|
'repartition_type': 'tax',
|
|
'tag_ids': [(6, 0, self.tax_tag_pos.ids)],
|
|
}),
|
|
],
|
|
'refund_repartition_line_ids': [
|
|
(0, 0, {'repartition_type': 'base'}),
|
|
(0, 0, {'repartition_type': 'tax'}),
|
|
],
|
|
})
|
|
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'}),
|
|
(0, 0, {'repartition_type': 'tax'}),
|
|
],
|
|
'refund_repartition_line_ids': [
|
|
(0, 0, {
|
|
'repartition_type': 'base',
|
|
'tag_ids': [(6, 0, self.base_tag_neg.ids)],
|
|
}),
|
|
(0, 0, {
|
|
'repartition_type': 'tax',
|
|
'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%
|
|
price_unit | Quantity | Taxes
|
|
------------------
|
|
2.82 | 20000 | 21% not incl
|
|
'''
|
|
self.env['res.currency.rate'].create({
|
|
'name': '2018-01-01',
|
|
'rate': 1.1726,
|
|
'currency_id': self.currency_data['currency'].id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
self.currency_data['currency'].rounding = 0.05
|
|
|
|
invoice = self.env['account.move'].create({
|
|
'move_type': 'out_invoice',
|
|
'partner_id': self.partner_a.id,
|
|
'currency_id': self.currency_data['currency'].id,
|
|
'invoice_date': '2018-01-01',
|
|
'date': '2018-01-01',
|
|
'invoice_line_ids': [(0, 0, {
|
|
'name': 'xxxx',
|
|
'quantity': 20000,
|
|
'price_unit': 2.82,
|
|
'tax_ids': [(6, 0, self.percent_tax_1.ids)],
|
|
})]
|
|
})
|
|
|
|
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id'), [{
|
|
'tax_base_amount': 48098.24, # 20000 * 2.82 / 1.1726
|
|
'credit': 10100.63, # tax_base_amount * 0.21
|
|
}])
|
|
|
|
def test_ensure_no_unbalanced_entry(self):
|
|
''' Ensure to not create an unbalanced journal entry when saving. '''
|
|
self.env['res.currency.rate'].create({
|
|
'name': '2018-01-01',
|
|
'rate': 0.654065014,
|
|
'currency_id': self.currency_data['currency'].id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
self.currency_data['currency'].rounding = 0.05
|
|
|
|
invoice = self._create_invoice([
|
|
(5, self.percent_tax_3_incl),
|
|
(10, self.percent_tax_3_incl),
|
|
(50, self.percent_tax_3_incl),
|
|
], currency_id=self.currency_data['currency'], invoice_payment_term_id=self.pay_terms_a)
|
|
invoice.action_post()
|
|
|
|
def test_tax_calculation_multi_currency(self):
|
|
self.env['res.currency.rate'].create({
|
|
'name': '2018-01-01',
|
|
'rate': 0.273748,
|
|
'currency_id': self.currency_data['currency'].id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
self.currency_data['currency'].rounding = 0.01
|
|
|
|
invoice = self.env['account.move'].create({
|
|
'move_type': 'out_invoice',
|
|
'partner_id': self.partner_a.id,
|
|
'currency_id': self.currency_data['currency'].id,
|
|
'invoice_date': '2018-01-01',
|
|
'date': '2018-01-01',
|
|
'invoice_line_ids': [(0, 0, {
|
|
'name': 'xxxx',
|
|
'quantity': 1,
|
|
'price_unit': 155.32,
|
|
'tax_ids': [(6, 0, self.percent_tax_1.ids)],
|
|
})]
|
|
})
|
|
|
|
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id'), [{
|
|
'tax_base_amount': 567.38, # 155.32 * 1 / (1 / 0.273748)
|
|
'balance': -119.16, # tax_base_amount * 0.21
|
|
}])
|
|
|
|
self.assertRecordValues(invoice.line_ids.filtered(lambda l: not l.name), [{
|
|
'balance': 686.54,
|
|
}])
|
|
|
|
with Form(invoice) as invoice_form:
|
|
invoice_form.currency_id = self.currency_data['currency']
|
|
|
|
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id'), [{
|
|
'tax_base_amount': 567.38,
|
|
'balance': -119.16,
|
|
}])
|
|
|
|
self.assertRecordValues(invoice.line_ids.filtered(lambda l: l.account_id.account_type == 'asset_receivable'), [{
|
|
'balance': 686.54,
|
|
}])
|