details:   https://code.tryton.org/tryton/commit/06e183433933
branch:    default
user:      Cédric Krier <[email protected]>
date:      Fri Jan 16 16:56:47 2026 +0100
description:
        Format the party's identifier codes

        Closes #14485
diffstat:

 modules/account_be/account.py                                        |   4 +-
 modules/account_export_winbooks/account.py                           |   4 +-
 modules/account_export_winbooks/party.py                             |  12 +-
 modules/account_invoice/tests/scenario_invoice.rst                   |   2 +-
 modules/account_payment_sepa/party.py                                |   2 +-
 modules/account_stock_eu/stock.py                                    |   2 +-
 modules/document_incoming_ocr/document.py                            |  14 +-
 modules/edocument_peppol/account.py                                  |   2 +-
 modules/edocument_ubl/edocument.py                                   |   6 +-
 modules/edocument_ubl/template/2/base.xml                            |   6 +-
 modules/edocument_uncefact/template/16B-CII/CrossIndustryInvoice.xml |   2 +-
 modules/party/CHANGELOG                                              |   1 +
 modules/party/party.py                                               |  76 
+++++++++-
 modules/stock_package_shipping_dpd/stock.py                          |   4 +-
 modules/stock_package_shipping_sendcloud/stock.py                    |  10 +-
 modules/web_shop_vue_storefront/party.py                             |   2 +-
 modules/web_shop_vue_storefront/web.py                               |   2 +-
 17 files changed, 115 insertions(+), 36 deletions(-)

diffs (435 lines):

diff -r c81ff0ed70bc -r 06e183433933 modules/account_be/account.py
--- a/modules/account_be/account.py     Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/account_be/account.py     Fri Jan 16 16:56:47 2026 +0100
@@ -81,10 +81,10 @@
         where = ((invoice.company == context.get('company'))
             & (period.fiscalyear == context.get('fiscalyear')))
         where &= invoice.type == 'out'
-        where &= ((company_identifier.code.ilike('BE%')
+        where &= ((company_identifier.code_compact.ilike('BE%')
                 & (company_identifier.type == 'eu_vat'))
             | (company_identifier.type == 'be_vat'))
-        where &= ((party_identifier.code.ilike('BE%')
+        where &= ((party_identifier.code_compact.ilike('BE%')
                 & (party_identifier.type == 'eu_vat'))
             | (party_identifier.type == 'be_vat'))
         where &= tax.group.in_(groups)
diff -r c81ff0ed70bc -r 06e183433933 modules/account_export_winbooks/account.py
--- a/modules/account_export_winbooks/account.py        Fri Jan 16 17:01:25 
2026 +0100
+++ b/modules/account_export_winbooks/account.py        Fri Jan 16 16:56:47 
2026 +0100
@@ -309,11 +309,11 @@
         if line.account.type.receivable and line.party:
             doctype = DOCTYPE.CUSTOMER
             if identifier := line.party.winbooks_customer_identifier:
-                accountrp = identifier.code
+                accountrp = identifier.code_compact
         elif line.account.type.payable and line.party:
             doctype = DOCTYPE.SUPPLIER
             if identifier := line.party.winbooks_supplier_identifier:
-                accountrp = identifier.code
+                accountrp = identifier.code_compact
         else:
             doctype = DOCTYPE.GENERAL
         return {
diff -r c81ff0ed70bc -r 06e183433933 modules/account_export_winbooks/party.py
--- a/modules/account_export_winbooks/party.py  Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/account_export_winbooks/party.py  Fri Jan 16 16:56:47 2026 +0100
@@ -54,11 +54,19 @@
                     & (t.active == Literal(True))),
                 'account_export_winbooks.'
                 'msg_party_identifier_winbooks_party_unique'),
-            ('winbooks_code_unique',
-                Exclude(t, (t.code, Equal),
+            ('winbooks_code_compact_unique',
+                Exclude(t, (t.code_compact, Equal),
                     where=t.type.in_(
                         ['winbooks_supplier', 'winbooks_customer'])
                     & (t.active == Literal(True))),
                 'account_export_winbooks.'
                 'msg_party_identifier_winbooks_code_unique'),
             ]
+
+    @classmethod
+    def __register__(cls, module):
+        table_h = cls.__table_handler__(module)
+        super().__register__(module)
+
+        # Migration from 7.8: replace winbooks_code_unique
+        table_h.drop_constraint('winbooks_code_unique')
diff -r c81ff0ed70bc -r 06e183433933 
modules/account_invoice/tests/scenario_invoice.rst
--- a/modules/account_invoice/tests/scenario_invoice.rst        Fri Jan 16 
17:01:25 2026 +0100
+++ b/modules/account_invoice/tests/scenario_invoice.rst        Fri Jan 16 
16:56:47 2026 +0100
@@ -189,7 +189,7 @@
     >>> assertEqual(invoice.posted_by, employee)
     >>> invoice.state
     'posted'
-    >>> invoice.tax_identifier.code
+    >>> invoice.tax_identifier.code_compact
     'BE0897290877'
     >>> assertTrue(iso11649.validate(invoice.customer_payment_reference))
     >>> bool(invoice.has_report_cache)
diff -r c81ff0ed70bc -r 06e183433933 modules/account_payment_sepa/party.py
--- a/modules/account_payment_sepa/party.py     Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/account_payment_sepa/party.py     Fri Jan 16 16:56:47 2026 +0100
@@ -28,7 +28,7 @@
     def get_sepa_creditor_identifier_used(self, name):
         for identifier in self.identifiers:
             if identifier.type == 'eu_at_02':
-                return identifier.code
+                return identifier.code_compact
 
     def get_sepa_identifier(self, name):
         pool = Pool()
diff -r c81ff0ed70bc -r 06e183433933 modules/account_stock_eu/stock.py
--- a/modules/account_stock_eu/stock.py Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/account_stock_eu/stock.py Fri Jan 16 16:56:47 2026 +0100
@@ -319,7 +319,7 @@
                             if not fallback:
                                 fallback = identifier
                             if (self.intrastat_country
-                                    and identifier.code.startswith(
+                                    and identifier.code_compact.startswith(
                                         self.intrastat_country.code)):
                                 break
                     else:
diff -r c81ff0ed70bc -r 06e183433933 modules/document_incoming_ocr/document.py
--- a/modules/document_incoming_ocr/document.py Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/document_incoming_ocr/document.py Fri Jan 16 16:56:47 2026 +0100
@@ -91,7 +91,9 @@
                 tax_identifier_types = Party.tax_identifier_types()
                 for identifier in invoice.party.identifiers:
                     if (identifier.type in tax_identifier_types
-                            and identifier.code == tax_identifier):
+                            and (
+                                identifier.code == tax_identifier
+                                or identifier.code_compact == tax_identifier)):
                         invoice.party_tax_identifier = identifier
 
             currency = invoice_data.get('currency')
@@ -302,7 +304,10 @@
             tax_identifier = invoice_data.get('company_tax_identifier')
             if tax_identifier:
                 identifiers = Identifier.search([
-                        ('code', '=', tax_identifier),
+                        ['OR',
+                            ('code', '=', tax_identifier),
+                            ('code_compact', '=', tax_identifier),
+                            ],
                         ('type', 'in', Party.tax_identifier_types()),
                         ])
                 if len(identifiers) == 1:
@@ -342,7 +347,10 @@
             tax_identifier = invoice_data.get('tax_identifier')
             if tax_identifier:
                 identifiers = Identifier.search([
-                        ('code', '=', tax_identifier),
+                        ['OR',
+                            ('code', '=', tax_identifier),
+                            ('code_compact', '=', tax_identifier),
+                            ],
                         ('type', 'in', Party.tax_identifier_types()),
                         ])
                 if len(identifiers) == 1:
diff -r c81ff0ed70bc -r 06e183433933 modules/edocument_peppol/account.py
--- a/modules/edocument_peppol/account.py       Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/edocument_peppol/account.py       Fri Jan 16 16:56:47 2026 +0100
@@ -24,7 +24,7 @@
             return (identifier
                 and (identifier.type == 'be_vat'
                     or (identifier.type == 'eu_vat'
-                        and identifier.code.startswith('BE'))))
+                        and identifier.code_compact.startswith('BE'))))
         if (self.invoice_date
                 and self.invoice_date.year >= 2026
                 and is_be_vat(self.tax_identifier)
diff -r c81ff0ed70bc -r 06e183433933 modules/edocument_ubl/edocument.py
--- a/modules/edocument_ubl/edocument.py        Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/edocument_ubl/edocument.py        Fri Jan 16 16:56:47 2026 +0100
@@ -881,7 +881,7 @@
                 ):
             if identifier.text:
                 domain = [
-                    ('code', '=', identifier.text),
+                    ('code_compact', '=', identifier.text),
                     ]
                 if schemeId := identifier.get('schemeID'):
                     if type := ISO6523.get(schemeId):
@@ -999,7 +999,7 @@
                 for identifier in party.identifiers:
                     if (identifier.type in tax_identifier_types
                             and identifier.iso_6523 == scheme_id
-                            and identifier.code == value):
+                            and identifier.code_compact == value):
                         return identifier
                 else:
                     if create and scheme_id in ISO6523:
@@ -1042,7 +1042,7 @@
                 ):
             if identifier.text:
                 domain = [
-                    ('code', '=', identifier.text),
+                    ('code_compact', '=', identifier.text),
                     ]
                 if schemeId := identifier.get('schemeID'):
                     if type := ISO6523.get(schemeId):
diff -r c81ff0ed70bc -r 06e183433933 modules/edocument_ubl/template/2/base.xml
--- a/modules/edocument_ubl/template/2/base.xml Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/edocument_ubl/template/2/base.xml Fri Jan 16 16:56:47 2026 +0100
@@ -13,7 +13,7 @@
             </py:with>
             <py:for each="identifier in identifications or party.identifiers">
                 <cac:PartyIdentification py:if="identifier and 
identifier.iso_6523">
-                    <cbc:ID py:attrs="{'schemeID': 
identifier.iso_6523}">${identifier.code}</cbc:ID>
+                    <cbc:ID py:attrs="{'schemeID': 
identifier.iso_6523}">${identifier.code_compact}</cbc:ID>
                 </cac:PartyIdentification>
             </py:for>
             <cac:PostalAddress py:if="address">${Address(address, 
specification=specification)}</cac:PostalAddress>
@@ -23,7 +23,7 @@
                         <cbc:CompanyID>${tax_identifier.vatin}</cbc:CompanyID>
                     </py:when>
                     <py:otherwise>
-                        <cbc:CompanyID py:attrs="{'schemeID': 
tax_identifier.iso_6523}">${tax_identifier.code}</cbc:CompanyID>
+                        <cbc:CompanyID py:attrs="{'schemeID': 
tax_identifier.iso_6523}">${tax_identifier.code_compact}</cbc:CompanyID>
                     </py:otherwise>
                 </py:choose>
                 <cac:TaxScheme>
@@ -34,7 +34,7 @@
                 <cbc:RegistrationName>${party.name}</cbc:RegistrationName>
                 <py:with vars="identifier = party.identifier_iso6523">
                     <py:if test="identifier">
-                        <cbc:CompanyID py:attrs="{'schemeID': 
identifier.iso_6523}">${identifier.code}</cbc:CompanyID>
+                        <cbc:CompanyID py:attrs="{'schemeID': 
identifier.iso_6523}">${identifier.code_compact}</cbc:CompanyID>
                     </py:if>
                 </py:with>
             </cac:PartyLegalEntity>
diff -r c81ff0ed70bc -r 06e183433933 
modules/edocument_uncefact/template/16B-CII/CrossIndustryInvoice.xml
--- a/modules/edocument_uncefact/template/16B-CII/CrossIndustryInvoice.xml      
Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/edocument_uncefact/template/16B-CII/CrossIndustryInvoice.xml      
Fri Jan 16 16:56:47 2026 +0100
@@ -20,7 +20,7 @@
         </ram:SpecifiedLegalOrganization>
         <ram:PostalTradeAddress 
py:if="address">${TradeAddress(address)}</ram:PostalTradeAddress>
         <ram:SpecifiedTaxRegistration py:if="tax_identifier and 
tax_identifier.type == 'eu_vat'">
-            <ram:ID schemeID='VA'>${tax_identifier.code}</ram:ID>
+            <ram:ID schemeID='VA'>${tax_identifier.code_compact}</ram:ID>
         </ram:SpecifiedTaxRegistration>
     </py:def>
     <py:def function="TradeAddress(address)">
diff -r c81ff0ed70bc -r 06e183433933 modules/party/CHANGELOG
--- a/modules/party/CHANGELOG   Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/party/CHANGELOG   Fri Jan 16 16:56:47 2026 +0100
@@ -1,3 +1,4 @@
+* Format identifier codes
 * Add Senegal Tax Number identifier
 * Add Azerbaijan Tax Number identifier
 * Add French Trade Registration Number identifier
diff -r c81ff0ed70bc -r 06e183433933 modules/party/party.py
--- a/modules/party/party.py    Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/party/party.py    Fri Jan 16 16:56:47 2026 +0100
@@ -343,6 +343,7 @@
         return [bool_op,
             ('code', operator, code_value, *extra),
             ('identifiers.code', operator, code_value, *extra),
+            ('identifiers.code_compact', operator, code_value, *extra),
             ('name', operator, operand, *extra),
             ('contact_mechanisms.rec_name', operator, operand, *extra),
             ]
@@ -843,9 +844,12 @@
     type_address = fields.Function(
         fields.Boolean("Type of Address"), 'on_change_with_type_address')
     code = fields.Char('Code', required=True)
+    code_compact = fields.Char("Code Compact", readonly=True, required=True)
 
     @classmethod
     def __setup__(cls):
+        cls.code.search_unaccented = False
+        cls.code_compact.search_unaccented = False
         super().__setup__()
         cls.__access__.add('party')
 
@@ -853,6 +857,11 @@
     def __register__(cls, module_name):
         cursor = Transaction().connection.cursor()
         table = cls.__table__()
+        table_h = cls.__table_handler__(module_name)
+
+        fill_code_compact = (
+            table_h.column_exist('code')
+            and not table_h.column_exist('code_compact'))
 
         super().__register__(module_name)
 
@@ -871,6 +880,10 @@
         cursor.execute(*table.update([table.type], ['co_nit'],
                 where=(table.type == 'co_rut')))
 
+        # Migration from 7.8: Fill code_compact
+        if fill_code_compact:
+            cursor.execute(*table.update([table.code_compact], [table.code]))
+
     @classmethod
     def get_types(cls):
         pool = Pool()
@@ -893,14 +906,20 @@
 
     @fields.depends('type', 'code')
     def on_change_with_code(self):
+        code = self.code
         if self.type and '_' in self.type:
-            module = get_cc_module(*self.type.split('_', 1))
-            if module:
-                try:
-                    return module.compact(self.code)
-                except stdnum.exceptions.ValidationError:
-                    pass
-        return self.code
+            if module := get_cc_module(*self.type.split('_', 1)):
+                if hasattr(module, 'compact'):
+                    try:
+                        code = module.compact(code)
+                    except stdnum.exceptions.ValidationError:
+                        pass
+                if hasattr(module, 'format'):
+                    try:
+                        code = module.format(code)
+                    except stdnum.exceptions.ValidationError:
+                        pass
+        return code
 
     def pre_validate(self):
         super().pre_validate()
@@ -922,6 +941,49 @@
                             code=self.code,
                             party=party))
 
+    @classmethod
+    def preprocess_values(cls, mode, values):
+        values = super().preprocess_values(mode, values)
+        if mode == 'create':
+            values['code_compact'] = values.get('code')
+            if ((type := values.get('type')) and '_' in type
+                    and (code := values.get('code'))):
+                if module := get_cc_module(*type.split('_', 1)):
+                    if hasattr(module, 'format'):
+                        try:
+                            values['code'] = module.format(code)
+                        except stdnum.exceptions.ValidationError:
+                            pass
+                    if hasattr(module, 'compact'):
+                        try:
+                            values['code_compact'] = module.compact(code)
+                        except stdnum.exceptions.ValidationError:
+                            pass
+        return values
+
+    def compute_fields(self, field_names=None):
+        values = super().compute_fields(field_names=field_names)
+        if field_names is None or {'type', 'code'} & field_names:
+            code = getattr(self, 'code', None)
+            code_compact = getattr(self, 'code_compact', None)
+            if (type := getattr(self, 'type', None)) and '_' in type and code:
+                if module := get_cc_module(*type.split('_', 1)):
+                    if hasattr(module, 'format'):
+                        code = module.format(code)
+                    if hasattr(module, 'compact'):
+                        code_compact = module.compact(code)
+                    else:
+                        code_compact = code
+                    if getattr(self, 'code', None) != code:
+                        values['code'] = code
+                    if getattr(self, 'code_compact', None) != code_compact:
+                        values['code_compact'] = code_compact
+                elif code_compact != code:
+                    values['code_compact'] = code
+            elif code_compact != code:
+                values['code_compact'] = code
+        return values
+
     @fields.depends(methods=['_notify_duplicate'])
     def on_change_notify(self):
         notifications = super().on_change_notify()
diff -r c81ff0ed70bc -r 06e183433933 modules/stock_package_shipping_dpd/stock.py
--- a/modules/stock_package_shipping_dpd/stock.py       Fri Jan 16 17:01:25 
2026 +0100
+++ b/modules/stock_package_shipping_dpd/stock.py       Fri Jan 16 16:56:47 
2026 +0100
@@ -385,7 +385,7 @@
             if customs_agent := shipment.customs_agent:
                 international.update({
                         'commercialInvoiceConsigneeVatNumber': (
-                            customs_agent.tax_identifier.code)[:20],
+                            customs_agent.tax_identifier.code_compact)[:20],
                         'commercialInvoiceConsignee': self.shipping_party(
                             customs_agent.party,
                             customs_agent.address,
@@ -393,7 +393,7 @@
                         })
             if shipment.tax_identifier:
                 international['commercialInvoiceConsignorVatNumber'] = (
-                    shipment.tax_identifier.code[:17])
+                    shipment.tax_identifier.code_compact[:17])
             international['commercialInvoiceConsignor'] = self.shipping_party(
                 shipment.company.party, shipment.customs_from_address)
             international['additionalInvoiceLines'] = [
diff -r c81ff0ed70bc -r 06e183433933 
modules/stock_package_shipping_sendcloud/stock.py
--- a/modules/stock_package_shipping_sendcloud/stock.py Fri Jan 16 17:01:25 
2026 +0100
+++ b/modules/stock_package_shipping_sendcloud/stock.py Fri Jan 16 16:56:47 
2026 +0100
@@ -281,32 +281,32 @@
                 yield {
                     'name': 'CNP',
                     'country_code': 'BR',
-                    'value': tax_identifier.code[:100],
+                    'value': tax_identifier.code_compact[:100],
                     }
             elif tax_identifier.type == 'ru_vat':
                 yield {
                     'name': 'INN',
                     'country_code': 'RU',
-                    'value': tax_identifier.code[:100],
+                    'value': tax_identifier.code_compact[:100],
                     }
             elif tax_identifier.type == 'eu_vat':
                 yield {
                     'name': 'VAT',
                     'country_code': tax_identifier.code[:2],
-                    'value': tax_identifier.code[2:][:100],
+                    'value': tax_identifier.code_compact[2:][:100],
                     }
             elif tax_identifier.type.endswith('_vat'):
                 yield {
                     'name': 'VAT',
                     'country_code': tax_identifier.type[:2].upper(),
-                    'value': tax_identifier.code[:100],
+                    'value': tax_identifier.code_compact[:100],
                     }
             elif tax_identifier.type in {'us_ein', 'us_ssn'}:
                 country, name = tax_identifier.type.upper().split('_')
                 yield {
                     'name': name,
                     'country_code': country,
-                    'value': tax_identifier.code[:100],
+                    'value': tax_identifier.code_compact[:100],
                     }
 
     def get_parcel_item(
diff -r c81ff0ed70bc -r 06e183433933 modules/web_shop_vue_storefront/party.py
--- a/modules/web_shop_vue_storefront/party.py  Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/web_shop_vue_storefront/party.py  Fri Jan 16 16:56:47 2026 +0100
@@ -53,7 +53,7 @@
         if for_party:
             address['company'] = self.party.name
             address['vat_id'] = (
-                self.party.tax_identifier.code
+                self.party.tax_identifier.code_compact
                 if self.party.tax_identifier else None)
         return address
 
diff -r c81ff0ed70bc -r 06e183433933 modules/web_shop_vue_storefront/web.py
--- a/modules/web_shop_vue_storefront/web.py    Fri Jan 16 17:01:25 2026 +0100
+++ b/modules/web_shop_vue_storefront/web.py    Fri Jan 16 16:56:47 2026 +0100
@@ -647,7 +647,7 @@
         if address_data.get('company'):
             for company_party in self.secondary_parties:
                 tax_code = (
-                    company_party.tax_identifier.code
+                    company_party.tax_identifier.code_compact
                     if company_party.tax_identifier else '')
                 if (company_party.name == address_data['company']
                         and (not address_data.get('vat_id')

Reply via email to