oca-ocb-accounting/odoo-bringout-oca-ocb-account/account/tests/test_invoice_taxes.py
Ernad Husremovic 768b70e05e 19.0 vanilla
2026-03-09 09:30:07 +01:00

952 lines
43 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):
super().setUpClass()
cls.other_currency = cls.setup_other_currency('HRK')
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_override': 'tax_included',
'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_override': 'tax_included',
'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',
}),
],
})
cls.tax_tag = tax_report_line.expression_ids._get_matching_tags()
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',
}),
],
})
cls.base_tag = base_report_line.expression_ids._get_matching_tags()
def _create_invoice_taxes_per_line(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_setting_tax_separately(self):
''' Test:
price_unit | Taxes
------------------
100 | 21%
Expected:
Tax | Taxes | Base | Amount
--------------------------------------------
21% | / | 100 | 21
'''
invoice = self._create_invoice_taxes_per_line([(100, self.env['account.tax'])])
invoice.invoice_line_ids[0].tax_ids = self.percent_tax_1
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id'), [
{'name': self.percent_tax_1.name, 'tax_base_amount': -100, 'balance': -21, 'tax_ids': []},
])
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_taxes_per_line([
(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_taxes_per_line([
(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_taxes_per_line([
(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_taxes_per_line([(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_taxes_per_line([(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_override': 'tax_included',
'include_base_amount': True,
})
invoice = self._create_invoice_taxes_per_line([(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.ids)],
}),
(0, 0, {
'repartition_type': 'tax',
'tag_ids': [(6, 0, self.tax_tag.ids)],
}),
],
'refund_repartition_line_ids': [
(0, 0, {
'repartition_type': 'base',
'tag_ids': [(6, 0, self.base_tag.ids)],
}),
(0, 0, {
'repartition_type': 'tax',
'tag_ids': [(6, 0, self.tax_tag.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},
{'balance': 100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag.ids, 'tax_base_amount': 1000, 'tax_repartition_line_id': ref_tax_rep_ln.id},
{'balance': 1000.0, 'tax_ids': sale_tax.ids, 'tax_tag_ids': self.base_tag.ids, 'tax_base_amount': 0, 'tax_repartition_line_id': 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.ids, 'tax_base_amount': 0, 'tax_repartition_line_id': False},
{'balance': -100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag.ids, 'tax_base_amount': -1000, 'tax_repartition_line_id': inv_tax_rep_ln.id},
{'balance': 1100.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 0, 'tax_repartition_line_id': 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.ids)],
}),
(0, 0, {
'repartition_type': 'tax',
'tag_ids': [(6, 0, self.tax_tag.ids)],
}),
],
'refund_repartition_line_ids': [
(0, 0, {
'repartition_type': 'base',
'tag_ids': [(6, 0, self.base_tag.ids)],
}),
(0, 0, {
'repartition_type': 'tax',
'tag_ids': [(6, 0, self.tax_tag.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},
{'balance': 100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag.ids, 'tax_base_amount': 1000, 'tax_repartition_line_id': inv_tax_rep_ln.id},
{'balance': 1000.0, 'tax_ids': purch_tax.ids, 'tax_tag_ids': self.base_tag.ids, 'tax_base_amount': 0, 'tax_repartition_line_id': 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.ids, 'tax_base_amount': 0, 'tax_repartition_line_id': False},
{'balance': -100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag.ids, 'tax_base_amount': -1000, 'tax_repartition_line_id': ref_tax_rep_ln.id},
{'balance': 1100.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_base_amount': 0, 'tax_repartition_line_id': 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.ids)],
}),
(0, 0, {
'repartition_type': 'tax',
'tag_ids': [(6, 0, self.tax_tag.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.ids)],
}),
(0, 0, {
'repartition_type': 'tax',
'tag_ids': [(6, 0, self.tax_tag.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.ids, 'tax_base_amount': 1000},
{'balance': 1000.0, 'tax_ids': sale_group.ids, 'tax_tag_ids': self.base_tag.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.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.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.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.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.ids, 'tax_base_amount': 0},
{'balance': -100.0, 'tax_ids': [], 'tax_tag_ids': self.tax_tag.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.other_currency.id,
'company_id': self.env.company.id,
})
self.other_currency.rounding = 0.05
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner_a.id,
'currency_id': self.other_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.other_currency.id,
'company_id': self.env.company.id,
})
self.other_currency.rounding = 0.05
invoice = self._create_invoice_taxes_per_line([
(5, self.percent_tax_3_incl),
(10, self.percent_tax_3_incl),
(50, self.percent_tax_3_incl),
], currency_id=self.other_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.other_currency.id,
'company_id': self.env.company.id,
})
self.other_currency.rounding = 0.01
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner_a.id,
'currency_id': self.other_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.15, # tax_base_amount * 0.21
}])
self.assertRecordValues(invoice.line_ids.filtered(lambda l: not l.name), [{
'balance': 686.53,
}])
with Form(invoice) as invoice_form:
invoice_form.currency_id = self.other_currency
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id'), [{
'tax_base_amount': -567.38,
'balance': -119.15,
}])
self.assertRecordValues(invoice.line_ids.filtered(lambda l: l.account_id.account_type == 'asset_receivable'), [{
'balance': 686.53,
}])
def test_fixed_tax_with_zero_price(self):
fixed_tax = self.env['account.tax'].create({
'name': 'Test 5 fixed',
'amount_type': 'fixed',
'amount': 5,
})
invoice = self._create_invoice_taxes_per_line([
(0, fixed_tax),
])
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id'), [{
'credit': 5.0,
'debit': 0,
}])
invoice.invoice_line_ids.quantity = 2
self.assertRecordValues(invoice.line_ids.filtered('tax_line_id'), [{
'credit': 10.0,
'debit': 0,
}])
def test_tax_repartition_lines_dispatch_amount_1(self):
""" Ensure the tax amount is well dispatched to the repartition lines and the rounding errors are well managed.
The 5% tax is applied on 1 so the total tax amount should be 0.05.
However, there are 10 tax repartition lines of 0.05 * 0.1 = 0.005 that will be rounded to 0.01.
The test checks the tax amount doesn't become 10 * 0.01 = 0.1 instead of 0.05.
"""
base_tax_rep = Command.create({'repartition_type': 'base', 'factor_percent': 100.0})
tax_reps = [Command.create({'repartition_type': 'tax', 'factor_percent': 10.0})] * 10
tax = self.env['account.tax'].create({
'name': "test_tax_repartition_lines_dispatch_amount_1",
'amount_type': 'percent',
'amount': 5.0,
'invoice_repartition_line_ids': [base_tax_rep] + tax_reps,
'refund_repartition_line_ids': [base_tax_rep] + tax_reps,
})
invoice = self._create_invoice_taxes_per_line([(1, tax)])
self.assertRecordValues(invoice, [{
'amount_untaxed': 1.0,
'amount_tax': 0.05,
'amount_total': 1.05,
}])
self.assertRecordValues(
invoice.line_ids.filtered('tax_line_id').sorted('balance'),
[{'balance': -0.01}] * 5,
)
def test_tax_repartition_lines_dispatch_amount_2(self):
""" Ensure the tax amount is well dispatched to the repartition lines and the rounding errors are well managed.
The 5% tax is applied on 1 so the total tax amount should be 0.05 but the distribution is 100 - 100 = 0%.
So at the end, the tax amount is 0.
However, there are 10 positive tax repartition lines of 0.05 * 0.1 = 0.005 that will be rounded to 0.01
and one negative repartition line of 50% and 2 of 25% that will give respectively 0.025 ~= 0.03 and 0.0125 ~= 0.01.
The test checks the tax amount is still zero at the end.
"""
base_tax_rep = Command.create({'repartition_type': 'base', 'factor_percent': 100.0})
plus_tax_reps = [Command.create({'repartition_type': 'tax', 'factor_percent': 10.0})] * 10
neg_tax_reps = [
Command.create({'repartition_type': 'tax', 'factor_percent': percent})
for percent in (-50, -25, -25)
]
tax = self.env['account.tax'].create({
'name': "test_tax_repartition_lines_dispatch_amount_2",
'amount_type': 'percent',
'amount': 5.0,
'invoice_repartition_line_ids': [base_tax_rep] + plus_tax_reps + neg_tax_reps,
'refund_repartition_line_ids': [base_tax_rep] + plus_tax_reps + neg_tax_reps,
})
invoice = self._create_invoice_taxes_per_line([(1, tax)], inv_type='out_refund')
self.assertRecordValues(invoice, [{
'amount_untaxed': 1.0,
'amount_tax': 0.0,
'amount_total': 1.0,
}])
self.assertRecordValues(
invoice.line_ids.filtered('tax_line_id').sorted('balance'),
[{'balance': -0.03}] + [{'balance': -0.01}] * 2 + [{'balance': 0.01}] * 5,
)
def test_tax_line_amount_currency_modification_auto_balancing(self):
date = '2017-01-01'
move = self.env['account.move'].create({
'move_type': 'out_invoice',
'date': date,
'partner_id': self.partner_a.id,
'invoice_date': date,
'currency_id': self.other_currency.id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
Command.create({
'name': self.product_a.name,
'product_id': self.product_a.id,
'product_uom_id': self.product_a.uom_id.id,
'quantity': 1.0,
'price_unit': 1000,
'tax_ids': self.product_a.taxes_id.ids,
}),
Command.create({
'name': self.product_b.name,
'product_id': self.product_b.id,
'product_uom_id': self.product_b.uom_id.id,
'quantity': 1.0,
'price_unit': 200,
'tax_ids': self.product_b.taxes_id.ids,
}),
]
})
receivable_line = move.line_ids.filtered(lambda line: line.display_type == 'payment_term')
self.assertRecordValues(receivable_line, [
{'amount_currency': 1410.00, 'balance': 705.00},
])
# Modify the tax lines
tax_lines = move.line_ids.filtered(lambda line: line.display_type == 'tax').sorted('amount_currency')
self.assertRecordValues(tax_lines, [
{'amount_currency': -180.00, 'balance': -90.00},
{'amount_currency': -30.00, 'balance': -15.00},
])
tax_lines[0].amount_currency = -180.03
# The following line should not cause the move to become unbalanced; i.e. there should be no error
tax_lines[1].amount_currency = -29.99
self.assertRecordValues(receivable_line, [
{'amount_currency': 1410.02, 'balance': 705.02},
])
def test_product_account_tags(self):
product_tag = self.env['account.account.tag'].create({
'name': "Pikachu",
'applicability': 'products',
})
self.product_a.account_tag_ids = [Command.set(product_tag.ids)]
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'date': '2025-01-01',
'partner_id': self.partner_a.id,
'invoice_line_ids': [
Command.create({
'name': 'test with product tag',
'product_id': self.product_a.id,
'price_unit': 1000,
'tax_ids': self.percent_tax_1.ids,
}),
Command.create({
'name': 'test with product tag',
'product_id': self.product_b.id,
'price_unit': 100,
'tax_ids': self.percent_tax_1.ids,
}),
Command.create({
'name': 'test without product tag',
'product_id': self.product_b.id,
'price_unit': 200,
'tax_ids': self.percent_tax_2.ids,
}),
]
})
invoice.action_post()
self.assertRecordValues(
invoice.line_ids,
[
{'tax_ids': self.percent_tax_1.ids, 'tax_line_id': False, 'tax_tag_ids': product_tag.ids, 'credit': 1000, 'debit': 0},
{'tax_ids': self.percent_tax_1.ids, 'tax_line_id': False, 'tax_tag_ids': [], 'credit': 100, 'debit': 0},
{'tax_ids': self.percent_tax_2.ids, 'tax_line_id': False, 'tax_tag_ids': [], 'credit': 200, 'debit': 0},
{'tax_ids': [], 'tax_line_id': self.percent_tax_1.id, 'tax_tag_ids': product_tag.ids, 'credit': 210, 'debit': 0},
{'tax_ids': [], 'tax_line_id': self.percent_tax_1.id, 'tax_tag_ids': [], 'credit': 21, 'debit': 0},
{'tax_ids': [], 'tax_line_id': self.percent_tax_2.id, 'tax_tag_ids': [], 'credit': 24, 'debit': 0},
{'tax_ids': [], 'tax_line_id': False, 'tax_tag_ids': [], 'credit': 0, 'debit': 1555},
],
)
def test_fiscal_position_tax_mapping_with_inactive_tax(self):
""" Test that inactive taxes are not mapped by fiscal positions """
fp = self.env['account.fiscal.position'].create({'name': 'FP'})
src_tax = self.company_data['default_tax_sale']
active_tax = self.percent_tax_1.copy({
'fiscal_position_ids': [Command.set(fp.ids)],
'original_tax_ids': [Command.set(src_tax.ids)],
})
self.percent_tax_2.copy({
'active': False,
'fiscal_position_ids': [Command.set(fp.ids)],
'original_tax_ids': [Command.set(src_tax.ids)],
})
self.partner_a.property_account_position_id = fp
move = self.init_invoice('out_invoice', self.partner_a, post=True, products=[self.product])
line = move.invoice_line_ids
self.assertEqual(line.tax_ids, active_tax, f"The tax should be {active_tax.name}, but is is currently {line.tax_ids.name}")
def test_multiple_onchange_product_and_price(self):
"""
This test checks that the totals are computed correctly when an onchange is executed
with "price_unit" before "product_id" in the values.
This test covers a UI issue where the totals were not updated when the price was changed,
then the product and finally the price again.
The issue was only occuring between the change of value and the next save.
That's why the test is using the onchange method directly instead of using a Form.
"""
invoice = self.init_invoice('out_invoice', products=self.product_a)
self.assertEqual(invoice.tax_totals['base_amount'], 1000.0)
self.assertEqual(invoice.tax_totals['total_amount'], 1150.0)
# The onchange is executed directly to simulate the following flow:
# 1) unit price is changed to any value
# 2) product is changed to "Product B"
# 3) unit price is changed to 2000.0
results = invoice.onchange(
{'invoice_line_ids': [Command.update(invoice.invoice_line_ids[0].id, {'price_unit': 2000.0, 'product_id': self.product_b.id})]},
['invoice_line_ids'],
{"invoice_line_ids": {}, 'tax_totals': {}}
)
self.assertEqual(results['value']['tax_totals']['base_amount'], 2000.0)
self.assertEqual(results['value']['tax_totals']['total_amount'], 2600.0)
def test_tax_mapping_with_tax_fiscal_position_set_to_all(self):
"""
A tax without any fiscal positions assigned should be applicable to
any fiscal position.
This test ensures that when a single fiscal position exists and a tax
has no fiscal position restriction, the tax is correctly applied to
invoice lines.
"""
self.env['account.fiscal.position'].search([]).action_archive()
default_tax = self.company_data['default_tax_sale']
self.env['account.fiscal.position'].create({
'name': 'FP',
'auto_apply': True,
'country_id': self.env.company.country_id.id,
'tax_ids': default_tax.ids,
})
self.assertEqual(self.product.taxes_id, default_tax)
default_tax.fiscal_position_ids = False
self.partner_a.country_id = self.env.company.country_id.id
move = self._create_invoice_one_line(product_id=self.product, partner_id=self.partner_a, post=True)
line = move.invoice_line_ids
self.assertEqual(line.tax_ids, default_tax)