From 97f616a60a02541e8c24727307c0555a453206a3 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 8 Sep 2026 10:09:31 +0200 Subject: [PATCH 1/2] [IMP] partner_identification: add scheme field Allows to configure schemes to be used in EDI exchanges. --- partner_identification/README.rst | 23 +++++++-- .../models/res_partner_id_category.py | 36 ++++++++++++++ partner_identification/readme/CONFIGURE.md | 16 ++++++ .../static/description/index.html | 49 ++++++++++++------- .../tests/test_partner_identification.py | 42 ++++++++++++++++ .../views/res_partner_id_category_view.xml | 1 + 6 files changed, 143 insertions(+), 24 deletions(-) diff --git a/partner_identification/README.rst b/partner_identification/README.rst index 92ab6fd0d92..8b4fadb0b88 100644 --- a/partner_identification/README.rst +++ b/partner_identification/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ============================== Partner Identification Numbers ============================== @@ -17,7 +13,7 @@ Partner Identification Numbers .. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png :target: https://odoo-community.org/page/development-status :alt: Production/Stable -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpartner--contact-lightgray.png?logo=github @@ -61,6 +57,23 @@ Categories. For example, we create a category 'Driver License': | Code, abbreviation or acronym of this ID type. For example, 'driver_license' +| Scheme: +| Optional code identifying this ID type in an external coding scheme + (for example the Peppol ICD code '0088' for GLN, used for EDI/UBL + output). Falls back to ``Code`` when not set - only needed when + ``Code`` (used for other purposes, e.g. internal categorization) + differs from the code an external system expects. + +By default, two different ID Categories can share the same ``Scheme``. +To forbid this, set the +``partner_identification.id_category_scheme_unique_check`` system +parameter to ``True`` (Settings > Technical > Parameters > System +Parameters, with developer mode active). ID Categories are a global +model (not tied to a company), so this is a global setting, not a +per-company one. Once enabled, saving an ID Category whose ``Scheme`` is +already used by another one raises a validation error. Categories with +no ``Scheme`` set never conflict, even when this is enabled. + | Python validation code: | Optional python code called to validate ID numbers of this ID type. This functionality can be overridden by setting ``id_no_validate`` to diff --git a/partner_identification/models/res_partner_id_category.py b/partner_identification/models/res_partner_id_category.py index 335bd772f18..e7959b79926 100644 --- a/partner_identification/models/res_partner_id_category.py +++ b/partner_identification/models/res_partner_id_category.py @@ -12,6 +12,7 @@ from odoo import api, fields, models from odoo.exceptions import UserError, ValidationError +from odoo.tools import str2bool from odoo.tools.safe_eval import safe_eval @@ -29,6 +30,13 @@ def _get_default_color(self): help="Abbreviation or acronym of this ID type. For example, " "'driver_license'", ) + scheme = fields.Char( + help="Code identifying this ID type in an external coding scheme " + "(eg. the Peppol ICD code '0088' for GLN, for EDI/UBL output). " + "Falls back to `code` when not set - only needed when `code` " + "(used for other purposes, eg. internal categorization) differs " + "from the code an external system expects.", + ) name = fields.Char( string="ID name", required=True, @@ -40,6 +48,34 @@ def _get_default_color(self): "Python validation code", help="Python code called to validate an id number." ) + @api.constrains("scheme") + def _check_scheme_unique(self): + if not self._check_scheme_unique_enabled(): + return + for rec in self: + if not rec.scheme: + continue + duplicate = self.search( + [("scheme", "=", rec.scheme), ("id", "!=", rec.id)], limit=1 + ) + if duplicate: + raise ValidationError( + self.env._( + "The scheme code '%(scheme)s' is already used by " + "another ID category (%(other)s).", + scheme=rec.scheme, + other=duplicate.display_name, + ) + ) + + def _check_scheme_unique_enabled(self): + param = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("partner_identification.id_category_scheme_unique_check") + ) + return str2bool(param, bool(param)) + @api.model def _search_duplicate(self, category_id, id_number, force_active=False): """Find duplicates for the given category and number.""" diff --git a/partner_identification/readme/CONFIGURE.md b/partner_identification/readme/CONFIGURE.md index de05f6416b1..d9206bd84b1 100644 --- a/partner_identification/readme/CONFIGURE.md +++ b/partner_identification/readme/CONFIGURE.md @@ -8,6 +8,22 @@ Code: Code, abbreviation or acronym of this ID type. For example, 'driver_license' +Scheme: +Optional code identifying this ID type in an external coding scheme (for +example the Peppol ICD code '0088' for GLN, used for EDI/UBL output). +Falls back to `Code` when not set - only needed when `Code` (used for +other purposes, e.g. internal categorization) differs from the code an +external system expects. + +By default, two different ID Categories can share the same `Scheme`. To +forbid this, set the `partner_identification.id_category_scheme_unique_check` +system parameter to `True` (Settings \> Technical \> Parameters \> System +Parameters, with developer mode active). ID Categories are a global model +(not tied to a company), so this is a global setting, not a per-company +one. Once enabled, saving an ID Category whose `Scheme` is already used by +another one raises a validation error. Categories with no `Scheme` set +never conflict, even when this is enabled. + Python validation code: Optional python code called to validate ID numbers of this ID type. This functionality can be overridden by setting `id_no_validate` to `True` in diff --git a/partner_identification/static/description/index.html b/partner_identification/static/description/index.html index ff5aa8c04f1..2bd6e37e77f 100644 --- a/partner_identification/static/description/index.html +++ b/partner_identification/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Partner Identification Numbers -
+
+

Partner Identification Numbers

- - -Odoo Community Association - -
-

Partner Identification Numbers

-

Production/Stable License: AGPL-3 OCA/partner-contact Translate me on Weblate Try me on Runboat

+

Production/Stable License: AGPL-3 OCA/partner-contact Translate me on Weblate Try me on Runboat

This module allows to manage all sort of identification numbers and certificates which are assigned to a partner (company or individual) and vary from country to country.

@@ -403,7 +398,7 @@

Partner Identification Numbers

-

Configuration

+

Configuration

Configure all ID types you need in Contacts > Configuration > Partner ID Categories. For example, we create a category ‘Driver License’:

@@ -416,6 +411,23 @@

Configuration

‘driver_license’
+
Scheme:
+
Optional code identifying this ID type in an external coding scheme +(for example the Peppol ICD code ‘0088’ for GLN, used for EDI/UBL +output). Falls back to Code when not set - only needed when +Code (used for other purposes, e.g. internal categorization) +differs from the code an external system expects.
+
+

By default, two different ID Categories can share the same Scheme. +To forbid this, set the +partner_identification.id_category_scheme_unique_check system +parameter to True (Settings > Technical > Parameters > System +Parameters, with developer mode active). ID Categories are a global +model (not tied to a company), so this is a global setting, not a +per-company one. Once enabled, saving an ID Category whose Scheme is +already used by another one raises a validation error. Categories with +no Scheme set never conflict, even when this is enabled.

+
Python validation code:
Optional python code called to validate ID numbers of this ID type. This functionality can be overridden by setting id_no_validate to @@ -429,7 +441,7 @@

Configuration

-

Usage

+

Usage

In partner form you will see another tab called ‘ID Numbers’. You can add any IDs to this partner, defining:

@@ -471,14 +483,14 @@

Usage

-

Known issues / Roadmap

+

Known issues / Roadmap

  • If you want to search a partner by ID you will use advance search form. You can’t search by issuer, valid dates, category or notes.
-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -486,9 +498,9 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • ChriCar Beteiligungs- und Beratungs- GmbH
  • Tecnativa
  • @@ -499,7 +511,7 @@

    Authors

-

Contributors

+

Contributors

-

Other credits

+

Other credits

The development of this module has been financially supported by:

  • Camptocamp
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -536,6 +548,5 @@

Maintainers

-
diff --git a/partner_identification/tests/test_partner_identification.py b/partner_identification/tests/test_partner_identification.py index edfe0a7569b..b30b9541b10 100644 --- a/partner_identification/tests/test_partner_identification.py +++ b/partner_identification/tests/test_partner_identification.py @@ -43,6 +43,48 @@ def test_update_partner_with_category(self): self.assertEqual(len(partner_1.id_numbers), 0) +class TestPartnerIdCategorySchemeUnique(common.TransactionCase): + def _set_check_enabled(self, enabled): + # Global config parameter - `res.partner.id_category` has no + # `company_id`, so this can't be a per-company setting. + self.env["ir.config_parameter"].sudo().set_param( + "partner_identification.id_category_scheme_unique_check", enabled + ) + + def test_duplicate_scheme_allowed_by_default(self): + # The check is off by default - duplicates are allowed. + param = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("partner_identification.id_category_scheme_unique_check") + ) + self.assertFalse(param) + self.env["res.partner.id_category"].create( + {"code": "cat1", "name": "Cat 1", "scheme": "TESTSCHEME"} + ) + # Does not raise. + self.env["res.partner.id_category"].create( + {"code": "cat2", "name": "Cat 2", "scheme": "TESTSCHEME"} + ) + + def test_duplicate_scheme_blocked_when_enabled(self): + self._set_check_enabled(True) + self.env["res.partner.id_category"].create( + {"code": "cat1", "name": "Cat 1", "scheme": "TESTSCHEME"} + ) + with self.assertRaises(ValidationError), self.cr.savepoint(): + self.env["res.partner.id_category"].create( + {"code": "cat2", "name": "Cat 2", "scheme": "TESTSCHEME"} + ) + + def test_no_scheme_never_blocked(self): + # Categories with no `scheme` set never conflict, even when enabled. + self._set_check_enabled(True) + self.env["res.partner.id_category"].create({"code": "cat1", "name": "Cat 1"}) + # Does not raise. + self.env["res.partner.id_category"].create({"code": "cat2", "name": "Cat 2"}) + + class TestPartnerCategoryValidation(common.TransactionCase): def test_partner_id_number_validation(self): partner_id_category = self.env["res.partner.id_category"].create( diff --git a/partner_identification/views/res_partner_id_category_view.xml b/partner_identification/views/res_partner_id_category_view.xml index 7ce1a68aac2..3a8321cece5 100644 --- a/partner_identification/views/res_partner_id_category_view.xml +++ b/partner_identification/views/res_partner_id_category_view.xml @@ -11,6 +11,7 @@ + From 6cbecde73b4174d5294f799a3e5e17060893c75c Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 8 Sep 2026 10:31:50 +0200 Subject: [PATCH 2/2] [IMP] partner_identification: add search views --- .../views/res_partner_id_category_view.xml | 12 ++++++++++ .../views/res_partner_id_number_view.xml | 22 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/partner_identification/views/res_partner_id_category_view.xml b/partner_identification/views/res_partner_id_category_view.xml index 3a8321cece5..4c5760a3f80 100644 --- a/partner_identification/views/res_partner_id_category_view.xml +++ b/partner_identification/views/res_partner_id_category_view.xml @@ -61,6 +61,18 @@ failed = not id_number.name.startswith(self.code) and True or False + + res.partner.id_category.search + res.partner.id_category + search + + + + + + + + Partner ID Categories res.partner.id_category diff --git a/partner_identification/views/res_partner_id_number_view.xml b/partner_identification/views/res_partner_id_number_view.xml index b8791e95b1e..0a89654df70 100644 --- a/partner_identification/views/res_partner_id_number_view.xml +++ b/partner_identification/views/res_partner_id_number_view.xml @@ -42,6 +42,28 @@ + + res.partner.id_number.search + res.partner.id_number + search + + + + + + + + + + Partner ID Numbers res.partner.id_number