From 67c0ab6a76c071417175012bbc6309c178369d63 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 9 Jul 2025 00:10:47 +0200 Subject: [PATCH 1/2] [ADD] Add modules donation_api and partner_match_or_create The module partner_match_or_create is designed to mutualize code between stay_api and donation_api --- partner_match_or_create/README.rst | 86 ++++ partner_match_or_create/__init__.py | 3 + partner_match_or_create/__manifest__.py | 22 + partner_match_or_create/i18n/fr.po | 333 ++++++++++++++ partner_match_or_create/models/__init__.py | 2 + partner_match_or_create/models/res_partner.py | 71 +++ .../models/res_partner_title.py | 21 + partner_match_or_create/post_install.py | 42 ++ .../readme/CONTRIBUTORS.md | 1 + partner_match_or_create/readme/DESCRIPTION.md | 1 + .../security/ir.model.access.csv | 2 + .../static/description/icon.png | Bin 0 -> 10254 bytes .../static/description/index.html | 427 ++++++++++++++++++ .../views/res_partner_title.xml | 40 ++ partner_match_or_create/wizards/__init__.py | 1 + .../wizards/partner_match_or_create.py | 311 +++++++++++++ .../wizards/partner_match_or_create.xml | 143 ++++++ 17 files changed, 1506 insertions(+) create mode 100644 partner_match_or_create/README.rst create mode 100644 partner_match_or_create/__init__.py create mode 100644 partner_match_or_create/__manifest__.py create mode 100644 partner_match_or_create/i18n/fr.po create mode 100644 partner_match_or_create/models/__init__.py create mode 100644 partner_match_or_create/models/res_partner.py create mode 100644 partner_match_or_create/models/res_partner_title.py create mode 100644 partner_match_or_create/post_install.py create mode 100644 partner_match_or_create/readme/CONTRIBUTORS.md create mode 100644 partner_match_or_create/readme/DESCRIPTION.md create mode 100644 partner_match_or_create/security/ir.model.access.csv create mode 100644 partner_match_or_create/static/description/icon.png create mode 100644 partner_match_or_create/static/description/index.html create mode 100644 partner_match_or_create/views/res_partner_title.xml create mode 100644 partner_match_or_create/wizards/__init__.py create mode 100644 partner_match_or_create/wizards/partner_match_or_create.py create mode 100644 partner_match_or_create/wizards/partner_match_or_create.xml diff --git a/partner_match_or_create/README.rst b/partner_match_or_create/README.rst new file mode 100644 index 00000000..4f1116c0 --- /dev/null +++ b/partner_match_or_create/README.rst @@ -0,0 +1,86 @@ +======================= +Partner Match or Create +======================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:9411adca8bbdcade1c4d70e5e297cfa4fb04b789e54f9343f8cb153bca414d7e + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |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%2Fdonation-lightgray.png?logo=github + :target: https://github.com/OCA/donation/tree/14.0/partner_match_or_create + :alt: OCA/donation +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/donation-14-0/donation-14-0-partner_match_or_create + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/donation&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This is a technical module used by the OCA module **stay_api** and the +OCA module **donation_api**. It allows to share code between those 2 +modules. + +**Table of contents** + +.. contents:: + :local: + +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 +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Akretion + +Contributors +------------ + +- Alexis de Lattre + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-alexis-via| image:: https://github.com/alexis-via.png?size=40px + :target: https://github.com/alexis-via + :alt: alexis-via + +Current `maintainer `__: + +|maintainer-alexis-via| + +This module is part of the `OCA/donation `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/partner_match_or_create/__init__.py b/partner_match_or_create/__init__.py new file mode 100644 index 00000000..379593d0 --- /dev/null +++ b/partner_match_or_create/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import wizards +from .post_install import res_partner_title_postinstall diff --git a/partner_match_or_create/__manifest__.py b/partner_match_or_create/__manifest__.py new file mode 100644 index 00000000..a51ff567 --- /dev/null +++ b/partner_match_or_create/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2025 Akretion France (https://www.akretion.com) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Partner Match or Create", + "version": "14.0.1.0.0", + "category": "Tools", + "license": "AGPL-3", + "summary": "Create a new partner or match an existing partner", + "author": "Akretion, Odoo Community Association (OCA)", + "maintainers": ["alexis-via"], + "website": "https://github.com/OCA/donation", + "depends": ["phone_validation"], + "data": [ + "security/ir.model.access.csv", + "views/res_partner_title.xml", + "wizards/partner_match_or_create.xml", + ], + "post_init_hook": "res_partner_title_postinstall", + "installable": True, +} diff --git a/partner_match_or_create/i18n/fr.po b/partner_match_or_create/i18n/fr.po new file mode 100644 index 00000000..89e75c20 --- /dev/null +++ b/partner_match_or_create/i18n/fr.po @@ -0,0 +1,333 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_match_or_create +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-01-11 21:58+0000\n" +"PO-Revision-Date: 2026-01-11 21:58+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_res_partner_title__api_code +msgid "API Code" +msgstr "Code API" + +#. module: partner_match_or_create +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.view_partner_title_form +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.view_partner_title_tree +msgid "API Code (do not modify)" +msgstr "Code API (ne pas modifier)" + +#. module: partner_match_or_create +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.partner_match_or_create_form +msgid "Address" +msgstr "Adresse" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__street +msgid "Address Line 1" +msgstr "Adresse 1ère ligne" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__street2 +msgid "Address Line 2" +msgstr "Adresse 2ème ligne" + +#. module: partner_match_or_create +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.partner_match_or_create_form +msgid "Cancel" +msgstr "Annuler" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__city +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.partner_match_or_create_form +msgid "City" +msgstr "Ville" + +#. module: partner_match_or_create +#: model:ir.model,name:partner_match_or_create.model_res_partner +msgid "Contact" +msgstr "" + +#. module: partner_match_or_create +#: code:addons/partner_match_or_create/wizards/partner_match_or_create.py:0 +#, python-format +msgid "" +"Contact %(partner_name)s created from web form information." +msgstr "Contact %(partner_name)s créé à partir des informations du formulaire Web." + +#. module: partner_match_or_create +#: code:addons/partner_match_or_create/wizards/partner_match_or_create.py:0 +#, python-format +msgid "" +"Contact created by the wizard of the module " +"partner_match_or_create." +msgstr "" +"Contact créé par l'assistant du module " +"partner_match_or_create." + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_partner_id +msgid "Contact to Update" +msgstr "Contact à mettre à jour" + +#. module: partner_match_or_create +#: code:addons/partner_match_or_create/wizards/partner_match_or_create.py:0 +#, python-format +msgid "" +"Contact updated by the wizard of the module " +"partner_match_or_create." +msgstr "" +"Contact mise à jour par l'assistant du module " +"partner_match_or_create." + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__country_id +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.partner_match_or_create_form +msgid "Country" +msgstr "Pays" + +#. module: partner_match_or_create +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.partner_match_or_create_form +msgid "Create New Contact" +msgstr "Créer une nouveau contact" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__create_or_update +msgid "Create Or Update" +msgstr "Créer ou mettre à jour" + +#. module: partner_match_or_create +#: model:ir.actions.act_window,name:partner_match_or_create.partner_match_or_create_action +msgid "Create or Update Contact" +msgstr "Créer ou mettre à jour un contact" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: partner_match_or_create +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.partner_match_or_create_form +msgid "Current Address" +msgstr "Adresse actuelle" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_partner_city +msgid "Current City" +msgstr "Ville actuelle" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_partner_country_id +msgid "Current Country" +msgstr "Pays actuel" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_partner_email +msgid "Current E-mail" +msgstr "E-mail actuel" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_partner_mobile +msgid "Current Mobile" +msgstr "Portable actuel" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_partner_phone +msgid "Current Phone" +msgstr "Téléphone actuel" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_partner_state_id +msgid "Current State" +msgstr "État actuel" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_partner_street +msgid "Current Street" +msgstr "Rue actuelle" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_partner_street2 +msgid "Current Street2" +msgstr "Rue2 actuelle" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_partner_zip +msgid "Current ZIP" +msgstr "Code postal actuel" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__display_name +#: model:ir.model.fields,field_description:partner_match_or_create.field_res_partner__display_name +#: model:ir.model.fields,field_description:partner_match_or_create.field_res_partner_title__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__email +msgid "E-mail" +msgstr "E-mail" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__firstname +msgid "Firstname" +msgstr "Prénom" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__id +#: model:ir.model.fields,field_description:partner_match_or_create.field_res_partner__id +#: model:ir.model.fields,field_description:partner_match_or_create.field_res_partner_title__id +msgid "ID" +msgstr "" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create____last_update +#: model:ir.model.fields,field_description:partner_match_or_create.field_res_partner____last_update +#: model:ir.model.fields,field_description:partner_match_or_create.field_res_partner_title____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__lastname +msgid "Lastname" +msgstr "Nom de famille" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__mobile +msgid "Mobile" +msgstr "Portable" + +#. module: partner_match_or_create +#: code:addons/partner_match_or_create/wizards/partner_match_or_create.py:0 +#, python-format +msgid "New Partner" +msgstr "Nouveau partenaire" + +#. module: partner_match_or_create +#: model:ir.model,name:partner_match_or_create.model_res_partner_title +msgid "Partner Title" +msgstr "Titre du partenaire" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__phone +msgid "Phone" +msgstr "Téléphone" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__res_id +msgid "Res" +msgstr "" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__res_model +msgid "Res Model" +msgstr "Modèle Res" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__state_id +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.partner_match_or_create_form +msgid "State" +msgstr "État" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__suggested_partner_ids +msgid "Suggested Contacts" +msgstr "Contacts suggérés" + +#. module: partner_match_or_create +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.partner_match_or_create_form +msgid "Suggested Existing Contacts" +msgstr "Contacts existants suggérés" + +#. module: partner_match_or_create +#: model:ir.model.fields,help:partner_match_or_create.field_res_partner_title__api_code +msgid "Technical code used by the API. Do not modify!" +msgstr "Code technique utilisé par l'API. Ne pas modifier !" + +#. module: partner_match_or_create +#: code:addons/partner_match_or_create/wizards/partner_match_or_create.py:0 +#, python-format +msgid "The partner to update is not set." +msgstr "Le partenaire à mettre à jour n'est pas défini." + +#. module: partner_match_or_create +#: model:ir.model.constraint,message:partner_match_or_create.constraint_res_partner_title_api_code_uniq +msgid "This API code already exists." +msgstr "Ce code API existe déjà." + +#. module: partner_match_or_create +#: model:ir.model.fields.selection,name:partner_match_or_create.selection__partner_match_or_create__create_or_update__update +msgid "This partner already exists in Odoo" +msgstr "Ce partenaire existe déjà dans Odoo" + +#. module: partner_match_or_create +#: model:ir.model.fields.selection,name:partner_match_or_create.selection__partner_match_or_create__create_or_update__create +msgid "This partner doesn't already exists in Odoo" +msgstr "Ce partenaire n'existe pas dans Odoo" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__title_id +msgid "Title" +msgstr "Titre" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_address +msgid "Update Address" +msgstr "Mettre à jour l'adresse" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_email +msgid "Update E-mail" +msgstr "Mettre à jour l'e-mail" + +#. module: partner_match_or_create +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.partner_match_or_create_form +msgid "Update Existing Contact" +msgstr "Mettre à jour un contact existant" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_mobile +msgid "Update Mobile" +msgstr "Mettre à jour le portable" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__update_phone +msgid "Update Phone" +msgstr "Mettre à jour le téléphone" + +#. module: partner_match_or_create +#: model:ir.model,name:partner_match_or_create.model_partner_match_or_create +msgid "Wizard to match/update an existing contact or create a new contact" +msgstr "Assistant pour trouver/mettre à jour un contact existant ou créer un nouveau contact" + +#. module: partner_match_or_create +#: model:ir.model.fields,field_description:partner_match_or_create.field_partner_match_or_create__zip +#: model_terms:ir.ui.view,arch_db:partner_match_or_create.partner_match_or_create_form +msgid "ZIP" +msgstr "Code postal" diff --git a/partner_match_or_create/models/__init__.py b/partner_match_or_create/models/__init__.py new file mode 100644 index 00000000..9873bb27 --- /dev/null +++ b/partner_match_or_create/models/__init__.py @@ -0,0 +1,2 @@ +from . import res_partner +from . import res_partner_title diff --git a/partner_match_or_create/models/res_partner.py b/partner_match_or_create/models/res_partner.py new file mode 100644 index 00000000..253d2fca --- /dev/null +++ b/partner_match_or_create/models/res_partner.py @@ -0,0 +1,71 @@ +# Copyright 2025 Akretion France (https://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import models + +logger = logging.getLogger(__name__) + + +class ResPartner(models.Model): + _inherit = "res.partner" + + def _controller_try_match_partner(self, vals): + email = vals["controller_email"] + mobile = vals["controller_mobile"] + partner_id = None + if "res.partner.phone" in self.env: # module base_partner_one2many_phone + partner_phone = ( + self.env["res.partner.phone"] + .sudo() + .search_read( + [ + ("type", "in", ("1_email_primary", "2_email_secondary")), + ("email", "=ilike", email), + ("partner_id", "!=", False), + ], + ["partner_id"], + limit=1, + ) + ) + if partner_phone: + partner_id = partner_phone[0]["partner_id"][0] + else: + partner = self.env["res.partner"].search_read( + [("email", "=ilike", email)], ["id"], limit=1 + ) + if partner: + partner_id = partner[0]["id"] + if partner_id: + logger.info("Match on email %s with partner ID %d", email, partner_id) + # 'and vals['controller_country_id'] to make sure the mobile phone has been reformatted + if not partner_id and mobile and vals["controller_country_id"]: + if "res.partner.phone" in self.env: # module base_partner_one2many_phone + partner_phone = ( + self.env["res.partner.phone"] + .sudo() + .search_read( + [ + ("type", "in", ("5_mobile_primary", "6_mobile_secondary")), + ("phone", "=", mobile), + ("partner_id", "!=", False), + ], + ["partner_id"], + limit=1, + ) + ) + if partner_phone: + partner_id = partner_phone[0]["partner_id"][0] + else: + partner = self.env["res.partner"].search_read( + [("mobile", "=", mobile)], ["id"], limit=1 + ) + if partner: + partner_id = partner[0]["id"] + if partner_id: + logger.info("Match on mobile %s with partner ID %d", mobile, partner_id) + if not partner_id: + logger.info("No match on an existing partner") + return partner_id diff --git a/partner_match_or_create/models/res_partner_title.py b/partner_match_or_create/models/res_partner_title.py new file mode 100644 index 00000000..b57b8826 --- /dev/null +++ b/partner_match_or_create/models/res_partner_title.py @@ -0,0 +1,21 @@ +# Copyright 2025 Akretion France (https://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResPartnerTitle(models.Model): + _inherit = "res.partner.title" + + # We need to have this code to make the API work for titles + # created by hand by the user that don't have any XMLID + api_code = fields.Char( + string="API Code", + copy=False, + help="Technical code used by the API. Do not modify!", + ) + + _sql_constraints = [ + ("api_code_uniq", "unique(api_code)", "This API code already exists.") + ] diff --git a/partner_match_or_create/post_install.py b/partner_match_or_create/post_install.py new file mode 100644 index 00000000..87da9e7e --- /dev/null +++ b/partner_match_or_create/post_install.py @@ -0,0 +1,42 @@ +# Copyright 2025 Akretion France (https://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import SUPERUSER_ID, api + +logger = logging.getLogger(__name__) + + +def res_partner_title_postinstall(cr, registry): + with api.Environment.manage(): + env = api.Environment(cr, SUPERUSER_ID, {}) + # set api_code on res.partner.title + model_datas = env["ir.model.data"].search( + [ + ("module", "=", "base"), + ("model", "=", "res.partner.title"), + ("res_id", "!=", False), + ("name", "!=", False), + ] + ) + unique_code = set() + for model_data in model_datas: + api_code = model_data.name.split("_")[-1] + if api_code in unique_code: + logger.warning( + "Skipping XMLID %s.%s because the suffix is not unique", + model_data.module, + model_data.name, + ) + continue + unique_code.add(api_code) + title = env["res.partner.title"].browse(model_data.res_id) + title.write({"api_code": api_code}) + logger.info( + "Wrote api_code=%s on title %s ID %d", + api_code, + title.display_name, + title.id, + ) diff --git a/partner_match_or_create/readme/CONTRIBUTORS.md b/partner_match_or_create/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..b61afe5d --- /dev/null +++ b/partner_match_or_create/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Alexis de Lattre \<\> diff --git a/partner_match_or_create/readme/DESCRIPTION.md b/partner_match_or_create/readme/DESCRIPTION.md new file mode 100644 index 00000000..2fc4c026 --- /dev/null +++ b/partner_match_or_create/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This is a technical module used by the OCA module **stay\_api** and the OCA module **donation\_api**. It allows to share code between those 2 modules. diff --git a/partner_match_or_create/security/ir.model.access.csv b/partner_match_or_create/security/ir.model.access.csv new file mode 100644 index 00000000..df2694d1 --- /dev/null +++ b/partner_match_or_create/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_partner_match_or_create,Full access on partner.match.or.create wizard,model_partner_match_or_create,base.group_partner_manager,1,1,1,1 diff --git a/partner_match_or_create/static/description/icon.png b/partner_match_or_create/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 GIT binary patch literal 10254 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 + + + + +Partner Match or Create + + + +
+

Partner Match or Create

+ + +

Beta License: AGPL-3 OCA/donation Translate me on Weblate Try me on Runboat

+

This is a technical module used by the OCA module stay_api and the +OCA module donation_api. It allows to share code between those 2 +modules.

+

Table of contents

+ +
+

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 +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

alexis-via

+

This module is part of the OCA/donation project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/partner_match_or_create/views/res_partner_title.xml b/partner_match_or_create/views/res_partner_title.xml new file mode 100644 index 00000000..eb9fde3c --- /dev/null +++ b/partner_match_or_create/views/res_partner_title.xml @@ -0,0 +1,40 @@ + + + + + + + res.partner.title + + + + + + + + + + res.partner.title + + + + + + + + + + diff --git a/partner_match_or_create/wizards/__init__.py b/partner_match_or_create/wizards/__init__.py new file mode 100644 index 00000000..c0cbeac0 --- /dev/null +++ b/partner_match_or_create/wizards/__init__.py @@ -0,0 +1 @@ +from . import partner_match_or_create diff --git a/partner_match_or_create/wizards/partner_match_or_create.py b/partner_match_or_create/wizards/partner_match_or_create.py new file mode 100644 index 00000000..f007e44c --- /dev/null +++ b/partner_match_or_create/wizards/partner_match_or_create.py @@ -0,0 +1,311 @@ +# Copyright 2025 Akretion France (https://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import logging + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.osv import expression + +logger = logging.getLogger(__name__) + + +class PartnerMatchOrCreate(models.TransientModel): + _name = "partner.match.or.create" + _description = "Wizard to match/update an existing contact or create a new contact" + + res_model = fields.Char(required=True, readonly=True) + res_id = fields.Integer(required=True, readonly=True) + firstname = fields.Char() + lastname = fields.Char(required=True) + title_id = fields.Many2one("res.partner.title") + email = fields.Char(string="E-mail") + phone = fields.Char() + mobile = fields.Char() + street = fields.Char(string="Address Line 1") + street2 = fields.Char(string="Address Line 2") + zip = fields.Char(string="ZIP") + city = fields.Char() + state_id = fields.Many2one("res.country.state") + country_id = fields.Many2one("res.country") + update_partner_id = fields.Many2one( + "res.partner", + string="Contact to Update", + compute="_compute_update_partner_id", + store=True, + readonly=False, + ) + update_partner_email = fields.Char( + related="update_partner_id.email", string="Current E-mail" + ) + update_partner_phone = fields.Char( + related="update_partner_id.phone", string="Current Phone" + ) + update_partner_mobile = fields.Char( + related="update_partner_id.mobile", string="Current Mobile" + ) + # I can't use a related on update_partner_id because the full address + # is not displayed any more when update_partner_id is changed + update_partner_street = fields.Char( + related="update_partner_id.street", string="Current Street" + ) + update_partner_street2 = fields.Char( + related="update_partner_id.street2", string="Current Street2" + ) + update_partner_zip = fields.Char( + related="update_partner_id.zip", string="Current ZIP" + ) + update_partner_city = fields.Char( + related="update_partner_id.city", string="Current City" + ) + update_partner_state_id = fields.Many2one( + related="update_partner_id.state_id", string="Current State" + ) + update_partner_country_id = fields.Many2one( + related="update_partner_id.country_id", string="Current Country" + ) + update_email = fields.Boolean( + compute="_compute_update_bool", + readonly=False, + store=True, + string="Update E-mail", + ) + update_phone = fields.Boolean( + compute="_compute_update_bool", readonly=False, store=True + ) + update_mobile = fields.Boolean( + compute="_compute_update_bool", readonly=False, store=True + ) + update_address = fields.Boolean( + compute="_compute_update_bool", readonly=False, store=True + ) + suggested_partner_ids = fields.Many2many( + "res.partner", readonly=True, string="Suggested Contacts" + ) + create_or_update = fields.Selection( + [ + ("create", "This partner doesn't already exists in Odoo"), + ("update", "This partner already exists in Odoo"), + ], + required=True, + ) + + @api.depends( + "update_partner_id", + "email", + "phone", + "mobile", + "street", + "street2", + "city", + "state_id", + "zip", + "country_id", + ) + def _compute_update_bool(self): + for wiz in self: + update_email = False + update_phone = False + update_mobile = False + update_address = False + upartner = wiz.update_partner_id + if upartner: + if wiz.email and wiz.email != upartner.email: + update_email = True + if wiz.phone and wiz.phone != upartner.phone: + update_phone = True + if wiz.mobile and wiz.mobile != upartner.mobile: + update_mobile = True + if ( + wiz.street + and wiz.city + and wiz.zip + and wiz.country_id + and any( + [ + wiz.street != upartner.street, + wiz.street2 != upartner.street2, + wiz.city != upartner.city, + wiz.state_id != upartner.state_id, + wiz.zip != upartner.zip, + wiz.country_id != upartner.country_id, + ] + ) + ): + update_address = True + wiz.update_email = update_email + wiz.update_phone = update_phone + wiz.update_mobile = update_mobile + wiz.update_address = update_address + + @api.depends("create_or_update") + def _compute_update_partner_id(self): + for wiz in self: + if wiz.create_or_update == "create": + wiz.update_partner_id = False + + def _prepare_suggested_partner_domain(self, vals): + rpo = self.env["res.partner"] + # similar lastname + domain_or_list = [] + if vals.get("lastname"): + max_lastname_split = max(vals["lastname"].split(" "), key=len) + logger.info( + "Populating suggested partners with max_lastname_split=%s", + max_lastname_split, + ) + if hasattr(rpo, "lastname"): + domain_or_list.append([("lastname", "ilike", max_lastname_split)]) + else: + domain_or_list.append([("name", "ilike", max_lastname_split)]) + elif vals.get("zip"): + domain_or_list.append([("zip", "=", vals["zip"])]) + # same country + if vals.get("country_id"): + domain_or_list.append([("country_id", "=", vals["country_id"])]) + domain = expression.AND(domain_or_list) + return domain + + @api.model + def default_get(self, fields_list): + res = super().default_get(fields_list) + res_model = self._context.get("active_model") + assert res_model + res_id = self._context.get("active_id") + assert res_id + record = self.env[res_model].browse(res_id) + # partner may have been created in the meantime + update_partner = False + if hasattr(record, "controller_email") and record.controller_email: + update_partner = self.env["res.partner"].search( + [("email", "=ilike", record.controller_email)], limit=1 + ) + res.update( + { + "res_model": res_model, + "res_id": res_id, + "create_or_update": update_partner and "update" or "create", + "update_partner_id": update_partner and update_partner.id or False, + } + ) + for rfield in [ + "firstname", + "lastname", + "title_id", + "email", + "phone", + "mobile", + "street", + "street2", + "zip", + "city", + "state_id", + "country_id", + ]: + controller_field = f"controller_{rfield}" + if hasattr(record, controller_field): + value = record[controller_field] + if rfield == "email": + value = value and value.lower() or False + elif rfield.endswith("_id"): + value = value and value.id or False + res[rfield] = value + suggested_partner_domain = self._prepare_suggested_partner_domain(res) + suggested_partner_ids = ( + self.env["res.partner"].search(suggested_partner_domain).ids + ) + res["suggested_partner_ids"] = [(6, 0, suggested_partner_ids)] + if suggested_partner_ids: + res["create_or_update"] = "update" + else: + res["create_or_update"] = "create" + return res + + def create_partner(self): + self.ensure_one() + rpo = self.env["res.partner"] + vals = { + "email": self.email, + "phone": self.phone, + "mobile": self.mobile, + "street": self.street, + "street2": self.street2, + "zip": self.zip, + "city": self.city, + "state_id": self.state_id.id or False, + "country_id": self.country_id.id or False, + "title": self.title_id.id or False, + } + # if OCA module partner_firstname is installed + if hasattr(rpo, "firstname") and hasattr(rpo, "lastname"): + vals.update( + { + "firstname": self.firstname, + "lastname": self.lastname, + } + ) + else: + name = self.lastname + if self.firstname: + name = f"{self.firstname} {name}" + vals["name"] = name + partner = self.env["res.partner"].create(vals) + model = self.env[self.res_model] + partner.message_post( + body=_( + "Contact created by the wizard of the module partner_match_or_create." + ) + ) + record = model.browse(self.res_id) + record.write({"partner_id": partner.id}) + record.message_post( + body=_( + "Contact " + "%(partner_name)s created from web form information.", + partner_id=partner.id, + partner_name=partner.display_name, + ) + ) + action = { + "type": "ir.actions.act_window", + "name": _("New Partner"), + "res_model": "res.partner", + "view_mode": "form", + "res_id": partner.id, + } + return action + + def update_partner(self): + self.ensure_one() + if not self.update_partner_id: + raise UserError(_("The partner to update is not set.")) + vals = {} + if self.update_phone: + vals["phone"] = self.phone + if self.update_mobile: + vals["mobile"] = self.mobile + if self.update_email: + vals["email"] = self.email + if self.update_address: + vals.update( + { + "street": self.street, + "street2": self.street2, + "zip": self.zip, + "city": self.city, + "state_id": self.state_id.id or False, + "country_id": self.country_id.id or False, + } + ) + model = self.env[self.res_model] + if vals: + self.update_partner_id.write(vals) + msg = _( + "Contact updated by the wizard of the module " + "partner_match_or_create." + ) + self.update_partner_id.message_post(body=msg) + record = model.browse(self.res_id) + record.write({"partner_id": self.update_partner_id.id}) + record.message_post(body=msg) diff --git a/partner_match_or_create/wizards/partner_match_or_create.xml b/partner_match_or_create/wizards/partner_match_or_create.xml new file mode 100644 index 00000000..8302bfea --- /dev/null +++ b/partner_match_or_create/wizards/partner_match_or_create.xml @@ -0,0 +1,143 @@ + + + + + + + partner.match.or.create + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + Create or Update Contact + partner.match.or.create + form + new + + + +
From 48c2db0b2fb26753ad917a63be78894f9c602b2d Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 12 Jan 2026 16:05:57 +0100 Subject: [PATCH 2/2] [MIG] partner_match_or_create: migrate 14 -> 16 --- partner_match_or_create/README.rst | 10 +++++----- partner_match_or_create/__manifest__.py | 2 +- .../static/description/index.html | 6 +++--- .../wizards/partner_match_or_create.py | 17 ++++++++++++++--- .../wizards/partner_match_or_create.xml | 15 ++++++++++++--- .../odoo/addons/partner_match_or_create | 1 + setup/partner_match_or_create/setup.py | 6 ++++++ 7 files changed, 42 insertions(+), 15 deletions(-) create mode 120000 setup/partner_match_or_create/odoo/addons/partner_match_or_create create mode 100644 setup/partner_match_or_create/setup.py diff --git a/partner_match_or_create/README.rst b/partner_match_or_create/README.rst index 4f1116c0..8bb35690 100644 --- a/partner_match_or_create/README.rst +++ b/partner_match_or_create/README.rst @@ -17,13 +17,13 @@ Partner Match or Create :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fdonation-lightgray.png?logo=github - :target: https://github.com/OCA/donation/tree/14.0/partner_match_or_create + :target: https://github.com/OCA/donation/tree/16.0/partner_match_or_create :alt: OCA/donation .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/donation-14-0/donation-14-0-partner_match_or_create + :target: https://translation.odoo-community.org/projects/donation-16-0/donation-16-0-partner_match_or_create :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/donation&target_branch=14.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/donation&target_branch=16.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -43,7 +43,7 @@ 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 -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -81,6 +81,6 @@ Current `maintainer `__: |maintainer-alexis-via| -This module is part of the `OCA/donation `_ project on GitHub. +This module is part of the `OCA/donation `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/partner_match_or_create/__manifest__.py b/partner_match_or_create/__manifest__.py index a51ff567..714ca516 100644 --- a/partner_match_or_create/__manifest__.py +++ b/partner_match_or_create/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Partner Match or Create", - "version": "14.0.1.0.0", + "version": "16.0.1.0.0", "category": "Tools", "license": "AGPL-3", "summary": "Create a new partner or match an existing partner", diff --git a/partner_match_or_create/static/description/index.html b/partner_match_or_create/static/description/index.html index ea748ab7..e0001d1e 100644 --- a/partner_match_or_create/static/description/index.html +++ b/partner_match_or_create/static/description/index.html @@ -369,7 +369,7 @@

Partner Match or Create

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:9411adca8bbdcade1c4d70e5e297cfa4fb04b789e54f9343f8cb153bca414d7e !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/donation Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/donation Translate me on Weblate Try me on Runboat

This is a technical module used by the OCA module stay_api and the OCA module donation_api. It allows to share code between those 2 modules.

@@ -390,7 +390,7 @@

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 -feedback.

+feedback.

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

@@ -418,7 +418,7 @@

Maintainers

promote its widespread use.

Current maintainer:

alexis-via

-

This module is part of the OCA/donation project on GitHub.

+

This module is part of the OCA/donation project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/partner_match_or_create/wizards/partner_match_or_create.py b/partner_match_or_create/wizards/partner_match_or_create.py index f007e44c..e99e826e 100644 --- a/partner_match_or_create/wizards/partner_match_or_create.py +++ b/partner_match_or_create/wizards/partner_match_or_create.py @@ -35,6 +35,7 @@ class PartnerMatchOrCreate(models.TransientModel): compute="_compute_update_partner_id", store=True, readonly=False, + precompute=True, ) update_partner_email = fields.Char( related="update_partner_id.email", string="Current E-mail" @@ -70,15 +71,25 @@ class PartnerMatchOrCreate(models.TransientModel): readonly=False, store=True, string="Update E-mail", + precompute=True, ) update_phone = fields.Boolean( - compute="_compute_update_bool", readonly=False, store=True + compute="_compute_update_bool", + readonly=False, + store=True, + precompute=True, ) update_mobile = fields.Boolean( - compute="_compute_update_bool", readonly=False, store=True + compute="_compute_update_bool", + readonly=False, + store=True, + precompute=True, ) update_address = fields.Boolean( - compute="_compute_update_bool", readonly=False, store=True + compute="_compute_update_bool", + readonly=False, + store=True, + precompute=True, ) suggested_partner_ids = fields.Many2many( "res.partner", readonly=True, string="Suggested Contacts" diff --git a/partner_match_or_create/wizards/partner_match_or_create.xml b/partner_match_or_create/wizards/partner_match_or_create.xml index 8302bfea..357032ba 100644 --- a/partner_match_or_create/wizards/partner_match_or_create.xml +++ b/partner_match_or_create/wizards/partner_match_or_create.xml @@ -11,7 +11,7 @@ partner.match.or.create
- + @@ -32,6 +32,7 @@ class="o_address_state" placeholder="State" options="{'no_open': True, 'no_quick_create': True}" + context="{'default_country_id': country_id}" /> @@ -73,11 +75,10 @@