diff --git a/modules/account_be/account.py b/modules/account_be/account.py
index 81ee9e1eef..1472ea1b08 100644
--- a/modules/account_be/account.py
+++ b/modules/account_be/account.py
@@ -81,10 +81,10 @@ class BEVATCustomer(ModelSQL, ModelView):
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 --git a/modules/account_export_winbooks/account.py b/modules/account_export_winbooks/account.py
index ac8692b27a..fc396a5fbb 100644
--- a/modules/account_export_winbooks/account.py
+++ b/modules/account_export_winbooks/account.py
@@ -309,11 +309,11 @@ class MoveExport(metaclass=PoolMeta):
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 --git a/modules/account_export_winbooks/party.py b/modules/account_export_winbooks/party.py
index b03ca393bf..416dd4919e 100644
--- a/modules/account_export_winbooks/party.py
+++ b/modules/account_export_winbooks/party.py
@@ -54,11 +54,19 @@ class Identifier(metaclass=PoolMeta):
& (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 --git a/modules/account_invoice/tests/scenario_invoice.rst b/modules/account_invoice/tests/scenario_invoice.rst
index 6dffa295f2..350264d468 100644
--- a/modules/account_invoice/tests/scenario_invoice.rst
+++ b/modules/account_invoice/tests/scenario_invoice.rst
@@ -189,7 +189,7 @@ Post invoice::
>>> 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 --git a/modules/account_payment_sepa/party.py b/modules/account_payment_sepa/party.py
index 9e7b7f5752..72e2f81620 100644
--- a/modules/account_payment_sepa/party.py
+++ b/modules/account_payment_sepa/party.py
@@ -28,7 +28,7 @@ class Party(metaclass=PoolMeta):
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 --git a/modules/account_stock_eu/stock.py b/modules/account_stock_eu/stock.py
index 47f305285e..a76449c1f3 100644
--- a/modules/account_stock_eu/stock.py
+++ b/modules/account_stock_eu/stock.py
@@ -319,7 +319,7 @@ class Move(metaclass=PoolMeta):
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 --git a/modules/document_incoming_ocr/document.py b/modules/document_incoming_ocr/document.py
index 98f067f581..4c273a4138 100644
--- a/modules/document_incoming_ocr/document.py
+++ b/modules/document_incoming_ocr/document.py
@@ -91,7 +91,9 @@ class IncomingSupplierInvoice(metaclass=PoolMeta):
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 @@ class IncomingSupplierInvoice(metaclass=PoolMeta):
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 @@ class IncomingSupplierInvoice(metaclass=PoolMeta):
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 --git a/modules/edocument_peppol/account.py b/modules/edocument_peppol/account.py
index d06f5c7db9..0b36299ae0 100644
--- a/modules/edocument_peppol/account.py
+++ b/modules/edocument_peppol/account.py
@@ -24,7 +24,7 @@ class Invoice(metaclass=PoolMeta):
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 --git a/modules/edocument_ubl/edocument.py b/modules/edocument_ubl/edocument.py
index 13718613f0..4bc8209748 100644
--- a/modules/edocument_ubl/edocument.py
+++ b/modules/edocument_ubl/edocument.py
@@ -881,7 +881,7 @@ class Invoice(Model):
):
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 @@ class Invoice(Model):
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 @@ class Invoice(Model):
):
if identifier.text:
domain = [
- ('code', '=', identifier.text),
+ ('code_compact', '=', identifier.text),
]
if schemeId := identifier.get('schemeID'):
if type := ISO6523.get(schemeId):
diff --git a/modules/edocument_ubl/template/2/base.xml b/modules/edocument_ubl/template/2/base.xml
index c94dceaf95..f9e59bd646 100644
--- a/modules/edocument_ubl/template/2/base.xml
+++ b/modules/edocument_ubl/template/2/base.xml
@@ -13,7 +13,7 @@ this repository contains the full copyright notices and license terms. -->
- ${identifier.code}
+ ${identifier.code_compact}
${Address(address, specification=specification)}
@@ -23,7 +23,7 @@ this repository contains the full copyright notices and license terms. -->
${tax_identifier.vatin}
- ${tax_identifier.code}
+ ${tax_identifier.code_compact}
@@ -34,7 +34,7 @@ this repository contains the full copyright notices and license terms. -->
${party.name}
- ${identifier.code}
+ ${identifier.code_compact}
diff --git a/modules/edocument_uncefact/template/16B-CII/CrossIndustryInvoice.xml b/modules/edocument_uncefact/template/16B-CII/CrossIndustryInvoice.xml
index ead5453aa4..a0d443505a 100644
--- a/modules/edocument_uncefact/template/16B-CII/CrossIndustryInvoice.xml
+++ b/modules/edocument_uncefact/template/16B-CII/CrossIndustryInvoice.xml
@@ -20,7 +20,7 @@ this repository contains the full copyright notices and license terms. -->
${TradeAddress(address)}
- ${tax_identifier.code}
+ ${tax_identifier.code_compact}
diff --git a/modules/party/CHANGELOG b/modules/party/CHANGELOG
index ba988ac6a2..9d5812f319 100644
--- a/modules/party/CHANGELOG
+++ b/modules/party/CHANGELOG
@@ -1,3 +1,4 @@
+* Format identifier codes
* Add Senegal Tax Number identifier
* Add Azerbaijan Tax Number identifier
* Add French Trade Registration Number identifier
diff --git a/modules/party/party.py b/modules/party/party.py
index d50651c61b..e4b8dc9cab 100644
--- a/modules/party/party.py
+++ b/modules/party/party.py
@@ -343,6 +343,7 @@ class Party(
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 @@ class Identifier(sequence_ordered(), DeactivableMixin, ModelSQL, ModelView):
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 @@ class Identifier(sequence_ordered(), DeactivableMixin, ModelSQL, ModelView):
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 @@ class Identifier(sequence_ordered(), DeactivableMixin, ModelSQL, ModelView):
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 @@ class Identifier(sequence_ordered(), DeactivableMixin, ModelSQL, ModelView):
@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 @@ class Identifier(sequence_ordered(), DeactivableMixin, ModelSQL, ModelView):
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 --git a/modules/stock_package_shipping_dpd/stock.py b/modules/stock_package_shipping_dpd/stock.py
index 702062d05a..c96cd3417d 100644
--- a/modules/stock_package_shipping_dpd/stock.py
+++ b/modules/stock_package_shipping_dpd/stock.py
@@ -385,7 +385,7 @@ class CreateDPDShipping_Customs(metaclass=PoolMeta):
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 @@ class CreateDPDShipping_Customs(metaclass=PoolMeta):
})
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 --git a/modules/stock_package_shipping_sendcloud/stock.py b/modules/stock_package_shipping_sendcloud/stock.py
index f5a579beaa..a3fcba22ba 100644
--- a/modules/stock_package_shipping_sendcloud/stock.py
+++ b/modules/stock_package_shipping_sendcloud/stock.py
@@ -281,32 +281,32 @@ class CreateShippingSendcloud_Customs(metaclass=PoolMeta):
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 --git a/modules/web_shop_vue_storefront/party.py b/modules/web_shop_vue_storefront/party.py
index a94144f5ad..f294d4c4e1 100644
--- a/modules/web_shop_vue_storefront/party.py
+++ b/modules/web_shop_vue_storefront/party.py
@@ -53,7 +53,7 @@ class Address(metaclass=PoolMeta):
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 --git a/modules/web_shop_vue_storefront/web.py b/modules/web_shop_vue_storefront/web.py
index e69a139117..9b0f2bac66 100644
--- a/modules/web_shop_vue_storefront/web.py
+++ b/modules/web_shop_vue_storefront/web.py
@@ -647,7 +647,7 @@ class User(metaclass=PoolMeta):
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')