[FIX] l10n_eu_oss:apply tag from localization
This commit aims to allow assigning tags to the taxes created by the OSS feature by providing the xml_id of their report.line in the eu_tag_map.py file. Before this commit: In l10n_be, the taxes created by OSS (l10n_eu_services) didn't set the tag +47 on invoice_repartition_lines nor +49 on refund_repartition_lines. This make the VAT report for Belgium wrong. After this commit: Taxes created by OSS for a company using the belgian CoA will get their tags set properly and thus will the taxes impact the belgian tax report correctly. task: 2770182 ticket: 2768622 Community-PR: https://github.com/odoo/odoo/pull/85607 Design choices: This fix is currently solving the issue for l10n_be but we have no doubt that it will be raised for other EU countries too. In order to provide the tags, we decided to be consistent with what as been done regarding the tax mapping. Thus we decided to create and maintain a simple mapping file and to test it. several other methods were explored: - create a global variable and update it from all localization modules. This method would work but is ugly and error prone. - create a templating method and override it from localization modules. The problem is where to set the root of the template method? The naïve solution would be to create a bridge module between l10n_eu_services and l10n_be but that would lead to an explosion in the number of bridge modules which we don't want. In order to keep things simple and generic, we could put the template method directly into the account module. But it is kind of ugly because account shouldn't know anything about the oss feature and it would encourage such a leaky design to happen again in the future. closes odoo/odoo#86314 X-original-commit: 7dcc2ae4665d2d7b577ed10160691775b86a848f Signed-off-by: William André (wan) <wan@odoo.com> Signed-off-by: Brice Bartoletti <bib@odoo.com>
This commit is contained in:
@@ -3,5 +3,6 @@
|
||||
|
||||
from . import chart_template
|
||||
from . import eu_tax_map
|
||||
from . import eu_tag_map
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
"""
|
||||
The EU_TAG_MAP answers the question: "which tag should I apply on the OSS tax repartition line?"
|
||||
|
||||
{
|
||||
'fiscal_country_code': {
|
||||
'invoice_base_tag': xml_id_of_the_tag or None,
|
||||
'invoice_tax_tag': xml_id_of_the_tag or None,
|
||||
'refund_base_tag': xml_id_of_the_tag or None,
|
||||
'refund_tax_tag': xml_id_of_the_tag or None,
|
||||
},
|
||||
}
|
||||
"""
|
||||
|
||||
EU_TAG_MAP = {
|
||||
# Austria
|
||||
'l10n_at.l10n_at_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Belgium
|
||||
'l10n_be.l10nbe_chart_template': {
|
||||
'invoice_base_tag': 'l10n_be.tax_report_line_47',
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': 'l10n_be.tax_report_line_49',
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Bulgaria
|
||||
'BG': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Croatia
|
||||
'l10n_hr.l10n_hr_chart_template_rrif': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Cyprus
|
||||
'CY': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Czech - Done in 13.0 - CoA not available yet
|
||||
'l10n_cz.cz_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Denmark
|
||||
'l10n_dk.dk_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Estonia - Done in 13.0 - CoA not available yet
|
||||
'EE': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Finland
|
||||
'l10n_fi.fi_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# France
|
||||
'l10n_fr.l10n_fr_pcg_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Germany SKR03
|
||||
'l10n_de_skr03.l10n_de_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Germany SKR04
|
||||
'l10n_de_skr04.l10n_chart_de_skr04': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Greece
|
||||
'l10n_gr.l10n_gr_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Hungary
|
||||
'l10n_hu.hungarian_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Ireland
|
||||
'l10n_ie.l10n_ie': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Italy
|
||||
'l10n_it.l10n_it_chart_template_generic': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Latvia
|
||||
'LV': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Lithuania
|
||||
'l10n_lt.account_chart_template_lithuania': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Luxembourg
|
||||
'l10n_lu.lu_2011_chart_1': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Malta - Done in 13.0 - CoA not available yet
|
||||
'MT': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Netherlands
|
||||
'l10n_nl.l10nnl_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Poland
|
||||
'l10n_pl.pl_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Portugal
|
||||
'l10n_pt.pt_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Romania
|
||||
'l10n_ro.ro_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Slovakia - Done in 13.0 - CoA not available yet
|
||||
'l10n_sk.sk_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Slovenia
|
||||
'l10n_si.gd_chart': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Spain
|
||||
'l10n_es.account_chart_template_common': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
# Sweden
|
||||
'l10n_se.l10nse_chart_template': {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
},
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
|
||||
from odoo import api, models, Command
|
||||
from .eu_tax_map import EU_TAX_MAP
|
||||
|
||||
import itertools
|
||||
from .eu_tag_map import EU_TAG_MAP
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
@@ -91,21 +90,19 @@ class Company(models.Model):
|
||||
def _get_repartition_lines_oss(self):
|
||||
self.ensure_one()
|
||||
defaults = self.env['account.tax'].with_company(self).default_get(['invoice_repartition_line_ids', 'refund_repartition_line_ids'])
|
||||
oss_account = self._get_oss_account()
|
||||
if oss_account:
|
||||
defaults['invoice_repartition_line_ids'][1][2]['account_id'] = oss_account.id
|
||||
defaults['refund_repartition_line_ids'][1][2]['account_id'] = oss_account.id
|
||||
|
||||
oss_tag = self.env.ref('l10n_eu_oss.tag_oss')
|
||||
for orm_command in itertools.chain(defaults['invoice_repartition_line_ids'], defaults['refund_repartition_line_ids']):
|
||||
rep_line_vals = orm_command[2]
|
||||
rep_line_vals['tag_ids'] = rep_line_vals.get('tag_ids', []) + [Command.link(oss_tag.id)]
|
||||
|
||||
oss_account, oss_tags = self._get_oss_account(), self._get_oss_tags()
|
||||
base_line, tax_line, vals = 0, 1, 2
|
||||
for doc_type in 'invoice', 'refund':
|
||||
if oss_account:
|
||||
defaults[f'{doc_type}_repartition_line_ids'][tax_line][vals]['account_id'] = oss_account.id
|
||||
if oss_tags:
|
||||
defaults[f'{doc_type}_repartition_line_ids'][base_line][vals]['tag_ids'] += [Command.link(tag.id) for tag in oss_tags[f'{doc_type}_base_tag']]
|
||||
defaults[f'{doc_type}_repartition_line_ids'][tax_line][vals]['tag_ids'] += [Command.link(tag.id) for tag in oss_tags[f'{doc_type}_tax_tag']]
|
||||
return defaults['invoice_repartition_line_ids'], defaults['refund_repartition_line_ids']
|
||||
|
||||
def _get_oss_account(self):
|
||||
self.ensure_one()
|
||||
if not self.env.ref('l10n_eu_oss.oss_tax_account_company_%s' % self.id, raise_if_not_found=False):
|
||||
if not self.env.ref(f'l10n_eu_oss.oss_tax_account_company_{self.id}', raise_if_not_found=False):
|
||||
sales_tax_accounts = self.env['account.tax'].search([
|
||||
('type_tax_use', '=', 'sale'),
|
||||
('company_id', '=', self.id)
|
||||
@@ -127,3 +124,22 @@ class Company(models.Model):
|
||||
'noupdate': True,
|
||||
})
|
||||
return self.env.ref(f'l10n_eu_oss.oss_tax_account_company_{self.id}')
|
||||
|
||||
def _get_oss_tags(self):
|
||||
oss_tag = self.env.ref('l10n_eu_oss.tag_oss')
|
||||
[chart_template_xml_id] = self.chart_template_id.get_external_id().values()
|
||||
tag_for_country = EU_TAG_MAP.get(chart_template_xml_id, {
|
||||
'invoice_base_tag': None,
|
||||
'invoice_tax_tag': None,
|
||||
'refund_base_tag': None,
|
||||
'refund_tax_tag': None,
|
||||
})
|
||||
|
||||
return {
|
||||
repartition_line_key: (
|
||||
self.env.ref(tag_xml_id).tag_ids.filtered(lambda t: not t.tax_negate)
|
||||
if tag_xml_id
|
||||
else self.env['account.account.tag']
|
||||
) + oss_tag
|
||||
for repartition_line_key, tag_xml_id in tag_for_country.items()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_oss
|
||||
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo.addons.l10n_eu_oss.models.eu_tag_map import EU_TAG_MAP
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', 'post_install_l10n', '-at_install')
|
||||
class TestOSSBelgium(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref='l10n_be.l10nbe_chart_template'):
|
||||
try:
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
except ValueError as e:
|
||||
if e.args[0] == "External ID not found in the system: l10n_be.l10nbe_chart_template":
|
||||
cls.skipTest(cls, reason="Belgian CoA is required for this testSuite but l10n_be isn't installed")
|
||||
else:
|
||||
raise e
|
||||
cls.company_data['company'].country_id = cls.env.ref('base.be')
|
||||
cls.company_data['company']._map_eu_taxes()
|
||||
|
||||
def test_country_tag_from_belgium(self):
|
||||
# get an eu country which isn't the current one:
|
||||
another_eu_country_code = (self.env.ref('base.europe').country_ids - self.company_data['company'].country_id)[0].code
|
||||
tax_oss = self.env['account.tax'].search([('name', 'ilike', f'%{another_eu_country_code}%')], limit=1)
|
||||
|
||||
for doc_type, report_line_xml_id in (
|
||||
("invoice", "l10n_be.tax_report_line_47"),
|
||||
("refund", "l10n_be.tax_report_line_49"),
|
||||
):
|
||||
with self.subTest(doc_type=doc_type, report_line_xml_id=report_line_xml_id):
|
||||
oss_tag_id = tax_oss[f"{doc_type}_repartition_line_ids"]\
|
||||
.filtered(lambda x: x.repartition_type == 'base')\
|
||||
.tag_ids
|
||||
|
||||
expected_tag_id = self.env.ref(report_line_xml_id)\
|
||||
.tag_ids\
|
||||
.filtered(lambda t: not t.tax_negate)
|
||||
|
||||
self.assertIn(expected_tag_id, oss_tag_id, f"{doc_type} tag from Belgian CoA not correctly linked")
|
||||
|
||||
|
||||
@tagged('post_install', 'post_install_l10n', '-at_install')
|
||||
class TestOSSUSA(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.company_data['company']._map_eu_taxes()
|
||||
|
||||
def test_no_oss_tax(self):
|
||||
# get an eu country which isn't the current one:
|
||||
another_eu_country_code = (self.env.ref('base.europe').country_ids - self.company_data['company'].country_id)[0].code
|
||||
tax_oss = self.env['account.tax'].search([('name', 'ilike', f'%{another_eu_country_code}%')], limit=1)
|
||||
|
||||
self.assertFalse(len(tax_oss), "OSS tax shouldn't be instanced on a US company")
|
||||
|
||||
|
||||
|
||||
@tagged('post_install', 'post_install_l10n', '-at_install')
|
||||
class TestOSSMap(AccountTestInvoicingCommon):
|
||||
|
||||
def test_oss_eu_tag_map(self):
|
||||
""" Checks that the xml_id referenced in the map are correct.
|
||||
In case of failure display the couple (chart_template_xml_id, tax_report_line_xml_id).
|
||||
The test doesn't fail for unreferenced char_template or unreferenced tax_report_line.
|
||||
"""
|
||||
chart_templates = self.env['account.chart.template'].search([])
|
||||
for chart_template in chart_templates:
|
||||
[chart_template_xml_id] = chart_template.get_external_id().values()
|
||||
oss_tags = EU_TAG_MAP.get(chart_template_xml_id, {})
|
||||
for tax_report_line_xml_id in filter(lambda d: d, oss_tags.values()):
|
||||
with self.subTest(chart_template_xml_id=chart_template_xml_id, tax_report_line_xml_id=tax_report_line_xml_id):
|
||||
tag = self.env.ref(tax_report_line_xml_id, raise_if_not_found=False)
|
||||
self.assertIsNotNone(tag, f"The following xml_id is incorrect in EU_TAG_MAP.py:{tax_report_line_xml_id}")
|
||||
Reference in New Issue
Block a user