From 543adc9e3f0cdb02a48a03b32277ef8443c5641d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 19 Dec 2025 09:41:39 +0100 Subject: [PATCH] Support European VAT number as EAS and VATIN Closes #14435 --- modules/edocument_ubl/party.py | 33 +++++++++++++++++----- modules/edocument_ubl/tests/test_module.py | 24 ++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/modules/edocument_ubl/party.py b/modules/edocument_ubl/party.py index b4d512b7e5..3db632a168 100644 --- a/modules/edocument_ubl/party.py +++ b/modules/edocument_ubl/party.py @@ -94,21 +94,40 @@ class Identifier(metaclass=PoolMeta): def iso_6523(self): return ISO6523_TYPES.get(self.type, '') + @property + def _eas_type(self): + if self.type == 'eu_vat': + code = self.code[:2].lower() + code = (code + .replace('xi', 'gb') + .replace('el', 'gr')) + return f'{code}_vat' + else: + return self.type + @property def eas_code(self): - return EAS_TYPES.get(self.type, '') + return EAS_TYPES.get(self._eas_type, '') @property def eas(self): if self.eas_code: - if re.match(r'[a-z]{2}_vat', self.type): - country = self.type[:2].replace('gr', 'el') - return f'{country}{self.code}'.lower() + if re.match(r'[a-z]{2}_vat', self._eas_type): + country = self._eas_type[:2].replace('gr', 'el') + if self.type == 'eu_vat': + code = self.code[2:] + else: + code = self.code + return f'{country}{code}'.lower() else: return self.code @property def vatin(self): - if re.match(r'[a-z]{2}_vat', self.type): - country = self.type[:2].replace('gr', 'el') - return f'{country}{self.code}'.lower() + if re.match(r'[a-z]{2}_vat', self._eas_type): + country = self._eas_type[:2].replace('gr', 'el') + if self.type == 'eu_vat': + code = self.code[2:] + else: + code = self.code + return f'{country}{code}'.lower() diff --git a/modules/edocument_ubl/tests/test_module.py b/modules/edocument_ubl/tests/test_module.py index 94d007da19..869a7c982c 100644 --- a/modules/edocument_ubl/tests/test_module.py +++ b/modules/edocument_ubl/tests/test_module.py @@ -44,6 +44,7 @@ def get_invoice(): type='be_vat', code='BE123456789') type(identifier).iso_6523 = Identifier.iso_6523 + type(identifier)._eas_type = Identifier._eas_type type(identifier).eas = Identifier.eas type(identifier).unece_code = Identifier.unece_code party = MagicMock( @@ -230,5 +231,28 @@ class EdocumentUblTestCase(ModuleTestCase): schema = etree.XMLSchema(etree.parse(schema_file)) schema.assertValid(invoice_xml) + @with_transaction() + def test_identifier_eas(self): + "Test EAS identifier" + pool = Pool() + Identifier = pool.get('party.identifier') + + for type, code, eas_code, eas in [ + ('gr_vat', '023456783', '9933', 'el023456783'), + ('eu_vat', 'EL023456783', '9933', 'el023456783'), + ('eu_vat', 'GR023456783', '9933', 'el023456783'), + ('be_vat', '0403019261', '9925', 'be0403019261'), + ('eu_vat', 'BE0403019261', '9925', 'be0403019261'), + ('gb_vat', '980780684', '9932', 'gb980780684'), + ('eu_vat', 'GB980780684', '9932', 'gb980780684'), + ('eu_vat', 'XI980780684', '9932', 'gb980780684'), + ]: + with self.subTest(type=type, code=code): + identifier = Identifier(type=type, code=code) + + self.assertEqual(identifier.eas_code, eas_code) + self.assertEqual(identifier.eas, eas) + self.assertEqual(identifier.vatin, eas) + del ModuleTestCase