diff --git a/addons/account_edi_ubl_cii/i18n/account_edi_ubl_cii.pot b/addons/account_edi_ubl_cii/i18n/account_edi_ubl_cii.pot index 89604145bf6..b3a5c6f8357 100644 --- a/addons/account_edi_ubl_cii/i18n/account_edi_ubl_cii.pot +++ b/addons/account_edi_ubl_cii/i18n/account_edi_ubl_cii.pot @@ -780,6 +780,21 @@ msgstr "" msgid "Tax '%s' is invalid: %s" msgstr "" +#. module: account_edi_ubl_cii +#. odoo-python +#: code:addons/account_edi_ubl_cii/models/res_partner.py:0 +#, python-format +msgid "The Peppol endpoint is not valid. The expected format is: 0239843188" +msgstr "" + +#. module: account_edi_ubl_cii +#. odoo-python +#: code:addons/account_edi_ubl_cii/models/res_partner.py:0 +#, python-format +msgid "" +"The Peppol endpoint is not valid. The expected format is: 73282932000074" +msgstr "" + #. module: account_edi_ubl_cii #. odoo-python #: code:addons/account_edi_ubl_cii/models/account_edi_xml_ubl_bis3.py:0 diff --git a/addons/account_edi_ubl_cii/models/account_edi_common.py b/addons/account_edi_ubl_cii/models/account_edi_common.py index 790e51bceb6..4b9e78d914f 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_common.py +++ b/addons/account_edi_ubl_cii/models/account_edi_common.py @@ -44,7 +44,7 @@ EAS_MAPPING = { 'AT': {'9915': 'vat'}, 'AU': {'0151': 'vat'}, 'BA': {'9924': 'vat'}, - 'BE': {'9925': 'vat', '0208': 'company_registry'}, + 'BE': {'0208': 'company_registry'}, 'BG': {'9926': 'vat'}, 'CH': {'9927': 'vat'}, 'CY': {'9928': 'vat'}, @@ -54,7 +54,7 @@ EAS_MAPPING = { 'EE': {'9931': 'vat'}, 'ES': {'9920': 'vat'}, 'FI': {'0213': 'vat'}, - 'FR': {'9957': 'vat', '0009': 'siret'}, + 'FR': {'0009': 'siret', '9957': 'vat'}, 'SG': {'0195': 'l10n_sg_unique_entity_number'}, 'GB': {'9932': 'vat'}, 'GR': {'9933': 'vat'}, diff --git a/addons/account_edi_ubl_cii/models/res_partner.py b/addons/account_edi_ubl_cii/models/res_partner.py index b5c2c9159fb..92ad2cabe78 100644 --- a/addons/account_edi_ubl_cii/models/res_partner.py +++ b/addons/account_edi_ubl_cii/models/res_partner.py @@ -1,7 +1,11 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import models, fields, api +import re +from stdnum.fr import siret + +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError from odoo.addons.account_edi_ubl_cii.models.account_edi_common import EAS_MAPPING @@ -121,6 +125,14 @@ class ResPartner(models.Model): ] ) + @api.constrains('peppol_endpoint') + def _check_peppol_fields(self): + for partner in self: + if partner.peppol_endpoint and partner.peppol_eas: + error = self._build_error_peppol_endpoint(partner.peppol_eas, partner.peppol_endpoint) + if error: + raise ValidationError(error) + @api.depends('country_code') def _compute_ubl_cii_format(self): for partner in self: @@ -139,35 +151,45 @@ class ResPartner(models.Model): else: partner.ubl_cii_format = partner.ubl_cii_format - @api.depends('country_code') + @api.depends('peppol_eas') def _compute_peppol_endpoint(self): + """ If the EAS changes and a valid endpoint is available, set it. Otherwise, keep the existing value.""" for partner in self: - if partner.ubl_cii_format != 'facturx' and partner.country_code in EAS_MAPPING: - eas_to_field = EAS_MAPPING[partner.country_code] - # Try to set both the peppol_eas and the peppol_endpoint - for field_name in eas_to_field.values(): - if field_name and field_name in partner._fields and partner[field_name]: - partner.peppol_endpoint = partner[field_name] - # If it's not possible to set the peppol_endpoint, just set the peppol_eas - if not partner.peppol_endpoint: - partner.peppol_endpoint = partner.peppol_endpoint - else: - partner.peppol_endpoint = partner.peppol_endpoint + partner.peppol_endpoint = partner.peppol_endpoint + if partner.country_code in EAS_MAPPING: + field = EAS_MAPPING[partner.country_code].get(partner.peppol_eas) + if field \ + and field in partner._fields \ + and partner[field] \ + and not partner._build_error_peppol_endpoint(partner.peppol_eas, partner[field]): + partner.peppol_endpoint = partner[field] @api.depends('country_code') def _compute_peppol_eas(self): + """ + If the country_code changes, recompute the EAS only if there is a country_code, it exists in the + EAS_MAPPING, and the current EAS is not consistent with the new country_code. + """ for partner in self: - if partner.ubl_cii_format != 'facturx' and partner.country_code in EAS_MAPPING: + partner.peppol_eas = partner.peppol_eas + if partner.country_code and partner.country_code in EAS_MAPPING: eas_to_field = EAS_MAPPING[partner.country_code] - # Try to set both the peppol_eas and the peppol_endpoint - for eas, field_name in eas_to_field.items(): - if field_name in partner._fields and partner[field_name]: - partner.peppol_eas = eas - # If it's not possible to set the peppol_endpoint, just set the peppol_eas - if not partner.peppol_eas: - partner.peppol_eas = list(eas_to_field.keys())[0] - else: - partner.peppol_eas = partner.peppol_eas + if partner.peppol_eas not in eas_to_field.keys(): + new_eas = list(EAS_MAPPING[partner.country_code].keys())[0] + # Iterate on the possible EAS until a valid one is found + for eas, field in eas_to_field.items(): + if field and field in partner._fields and partner[field]: + if not partner._build_error_peppol_endpoint(eas, partner[field]): + new_eas = eas + break + partner.peppol_eas = new_eas + + def _build_error_peppol_endpoint(self, eas, endpoint): + """ This function contains all the rules regarding the peppol_endpoint.""" + if eas == '0208' and not re.match(r"^\d{10}$", endpoint): + return _("The Peppol endpoint is not valid. The expected format is: 0239843188") + if eas == '0009' and not siret.is_valid(endpoint): + return _("The Peppol endpoint is not valid. The expected format is: 73282932000074") def _get_edi_builder(self): self.ensure_one() diff --git a/addons/account_edi_ubl_cii/tests/__init__.py b/addons/account_edi_ubl_cii/tests/__init__.py new file mode 100644 index 00000000000..5b6a56d0275 --- /dev/null +++ b/addons/account_edi_ubl_cii/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_partner_peppol_fields diff --git a/addons/account_edi_ubl_cii/tests/test_partner_peppol_fields.py b/addons/account_edi_ubl_cii/tests/test_partner_peppol_fields.py new file mode 100644 index 00000000000..4febe29ff46 --- /dev/null +++ b/addons/account_edi_ubl_cii/tests/test_partner_peppol_fields.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +from contextlib import contextmanager +from unittest.mock import patch + +from odoo.tests import tagged +from odoo.addons.account.tests.common import AccountTestInvoicingCommon + + +@tagged('post_install', '-at_install') +class TestAccountUblCii(AccountTestInvoicingCommon): + + @contextmanager + def check_peppol_vals(self, partner, expected, reset=True): + if reset: + partner.write({ + 'country_id': False, + 'peppol_eas': False, + 'peppol_endpoint': False, + }) + yield + partner.country_id = self.env.ref('base.dk') + self.assertEqual((partner.peppol_eas, partner.peppol_endpoint), expected) + + def _build_error_peppol_endpoint(self, eas, endpoint): + """ Mock _build_error_peppol_endpoint""" + if eas == "0184" and endpoint != "digstorg": + return f"(0184, {endpoint}) is not a valid peppol couple." + + @patch( + 'odoo.addons.account_edi_ubl_cii.models.res_partner.ResPartner._build_error_peppol_endpoint', + _build_error_peppol_endpoint, + ) + def test_peppol_eas_endpoint(self): + # NB: the EAS_MAPPING for DK is: {'DK': {'0184': 'company_registry', '0198': 'vat'}} + partner = self.company_data['company'].partner_id + + partner.company_registry = "digstorg" + partner.vat = "DK12345674" + + # Base case -> (0184, company_registry) + with self.check_peppol_vals(partner, expected=("0184", partner.company_registry)): + pass + + # No company_registry -> (0198, vat) + with self.check_peppol_vals(partner, expected=("0198", partner.vat)): + partner.company_registry = False + + # Invalid company_registry -> (0198, vat) + with self.check_peppol_vals(partner, expected=("0198", partner.vat)): + partner.company_registry = "turlututu" + + # No company_registry nor vat -> (0184, False) + with self.check_peppol_vals(partner, expected=("0184", False)): + partner.write({ + 'company_registry': False, + 'vat': False, + }) + + # Create a partner, fill the peppol fields, then set the country + partner_1 = self.env['res.partner'].create({ + 'name': "A new partner", + 'peppol_eas': '0184', + 'peppol_endpoint': 'digstorg' + }) + with self.check_peppol_vals(partner_1, expected=("0184", 'digstorg'), reset=False): + pass + + # Create a partner, set the country, then fill the peppol fields + partner_2 = self.env['res.partner'].create({ + 'name': "A new partner", + 'country_id': self.env.ref('base.dk').id, + }) + with self.check_peppol_vals(partner_2, expected=("0184", 'digstorg'), reset=False): + partner_2.peppol_eas = '0184' + partner_2.peppol_endpoint = 'digstorg' + + # Change the country, the EAS changes but we do not overwrite the existing endpoint + partner_2.country_id = self.env.ref('base.be') + self.assertEqual((partner_2.peppol_eas, partner_2.peppol_endpoint), ('0208', 'digstorg')) diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_out_invoice_no_prices.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_out_invoice_no_prices.xml index b1086f5c5cb..fa7629599b4 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_out_invoice_no_prices.xml +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_out_invoice_no_prices.xml @@ -14,7 +14,7 @@ - BE0202239951 + 0202239951 partner_1 @@ -43,7 +43,7 @@ - BE0477472701 + 0477472701 partner_2 diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py index a2749457576..028ca96bbb6 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py @@ -238,8 +238,8 @@ class TestUBLBE(TestUBLCommon, TestAccountMoveSendCommon): # Setup a public admin in Luxembourg self.partner_2.write({ 'vat': None, - 'peppol_endpoint': '00005000041', 'peppol_eas': '9938', + 'peppol_endpoint': '00005000041', 'country_id': self.env.ref('base.lu').id, })