Files
odoo_source/addons/account/tests/test_invoice_taxes.py
T
oco-odoo 753636a412 [FIX] account: primarily compute taxes in currency
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
2022-10-31 10:41:51 +01:00

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,
}])