forked from Yaltik/golem
[ADD]Partner merger spinoff when not using CRM module
This commit is contained in:
parent
dd2802da24
commit
6c2cd40020
18
odoo_partner_merge/__init__.py
Normal file
18
odoo_partner_merge/__init__.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2017 Fabien Bourgeois <fabien@yaltik.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from . import wizard
|
30
odoo_partner_merge/__manifest__.py
Normal file
30
odoo_partner_merge/__manifest__.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2017 Fabien Bourgeois <fabien@yaltik.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Odoo spinoff partner merger from CRM',
|
||||||
|
'summary': 'Odoo spinoff partner merger from CRM',
|
||||||
|
'description': 'Odoo spinoff partner merger from CRM (wizard, mainly)',
|
||||||
|
'version': '10.0.1.0.0',
|
||||||
|
'category': 'GOLEM',
|
||||||
|
'author': 'Odoo SA, Fabien Bourgeois',
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
'application': False,
|
||||||
|
'installable': True,
|
||||||
|
'depends': ['base'],
|
||||||
|
'data': ['wizard/base_partner_merge_views.xml']
|
||||||
|
}
|
328
odoo_partner_merge/i18n/fr.po
Normal file
328
odoo_partner_merge/i18n/fr.po
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * odoo_partner_merge
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 10.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2017-06-04 15:11+0000\n"
|
||||||
|
"PO-Revision-Date: 2017-06-04 15:11+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: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_exclude_contact
|
||||||
|
msgid "A user associated to the contact"
|
||||||
|
msgstr "Utilisateur associé au contact"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:310
|
||||||
|
#, python-format
|
||||||
|
msgid "All contacts must have the same email. Only the Administrator can merge contacts with different emails."
|
||||||
|
msgstr "Tous les contacts doivent avoir la même adresse courriel. Seul l'administrateur peut fusionner des contacts avec des adresses courriel différentes."
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Are you sure to execute the automatic merge of your contacts ?"
|
||||||
|
msgstr "Voulez-vous vraiment réaliser la fusion automatique des contacts ?"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Are you sure to execute the list of automatic merges of your contacts ?"
|
||||||
|
msgstr "Voulez-vous vraiment réaliser la fusion automatique de ces contacts ?"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Automatic Merge Wizard"
|
||||||
|
msgstr "Assistant de fusion automatique"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Annuler"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Close"
|
||||||
|
msgstr "Fermer"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_partner_ids
|
||||||
|
msgid "Contacts"
|
||||||
|
msgstr "Contacts"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_create_uid
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_create_uid
|
||||||
|
msgid "Created by"
|
||||||
|
msgstr "Créé par"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_create_date
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_create_date
|
||||||
|
msgid "Created on"
|
||||||
|
msgstr "Créé le"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_current_line_id
|
||||||
|
msgid "Current Line"
|
||||||
|
msgstr "Ligne actuelle"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.actions.act_window,name:odoo_partner_merge.action_partner_deduplicate
|
||||||
|
msgid "Deduplicate Contacts"
|
||||||
|
msgstr "Fusionner les contacts"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Deduplicate the other Contacts"
|
||||||
|
msgstr "Fusionner d'autres contacts"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_dst_partner_id
|
||||||
|
msgid "Destination Contact"
|
||||||
|
msgstr "Destination Contact"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_display_name
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_display_name
|
||||||
|
msgid "Display Name"
|
||||||
|
msgstr "Nom affiché"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_group_by_email
|
||||||
|
msgid "Email"
|
||||||
|
msgstr "Courriel"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Exclude contacts having"
|
||||||
|
msgstr "Exclure les contacts avec"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: selection:base.partner.merge.automatic.wizard,state:0
|
||||||
|
msgid "Finished"
|
||||||
|
msgstr "Terminé"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:299
|
||||||
|
#, python-format
|
||||||
|
msgid "For safety reasons, you cannot merge more than 3 contacts together. You can re-open the wizard several times if needed."
|
||||||
|
msgstr "Pour des raisons de sécurité, vous ne pouvez pas fusionner plus de 3 contacts. Si besoin, vous pouvez ré-ouvrir l'assistant autant de fois que nécessaire."
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_number_group
|
||||||
|
msgid "Group of Contacts"
|
||||||
|
msgstr "Groupe de contacts"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_id
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_id
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_aggr_ids
|
||||||
|
msgid "Ids"
|
||||||
|
msgstr "IDS"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_group_by_is_company
|
||||||
|
msgid "Is Company"
|
||||||
|
msgstr "Est une société"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_exclude_journal_item
|
||||||
|
msgid "Journal Items associated to the contact"
|
||||||
|
msgstr "Écritures comptables associées au contact"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard___last_update
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line___last_update
|
||||||
|
msgid "Last Modified on"
|
||||||
|
msgstr "Dernière Modification le"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_write_uid
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_write_uid
|
||||||
|
msgid "Last Updated by"
|
||||||
|
msgstr "Dernière mise à jour par"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_write_date
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_write_date
|
||||||
|
msgid "Last Updated on"
|
||||||
|
msgstr "Dernière mise à jour le"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_line_ids
|
||||||
|
msgid "Lines"
|
||||||
|
msgstr "Lignes"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_maximum_group
|
||||||
|
msgid "Maximum of Group of Contacts"
|
||||||
|
msgstr "Limite Maximum du Groupe de Contacts"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Merge Automatically"
|
||||||
|
msgstr "Fusionner automatiquement"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Merge Automatically all process"
|
||||||
|
msgstr "Fusionne automatiquement tous les processus"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Merge Contacts"
|
||||||
|
msgstr "Fusionner les contacts"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.actions.act_window,name:odoo_partner_merge.action_partner_merge
|
||||||
|
msgid "Merge Selected Contacts"
|
||||||
|
msgstr "Fusionner les contacts sélectionnés"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Merge the following contacts"
|
||||||
|
msgstr "Fusionner les contacts suivants"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Merge with Manual Check"
|
||||||
|
msgstr "Fusionner avec vérification manuelle"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:331
|
||||||
|
#, python-format
|
||||||
|
msgid "Merged with the following partners:"
|
||||||
|
msgstr "Fusionnés avec les partenaires suivants"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_min_id
|
||||||
|
msgid "MinID"
|
||||||
|
msgstr "MinID"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_group_by_name
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Nom"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:323
|
||||||
|
#, python-format
|
||||||
|
msgid "Only the destination contact may be linked to existing Journal Items. Please ask the Administrator if you need to merge several contacts linked to existing Journal Items."
|
||||||
|
msgstr "Seul le contact de destination peut être lié à des écritures comptables existantes. Si vous avez besoin de fusionner plusieurs contacts liés à des écritures comptables existantes, veuillez contacter l'administrateur ."
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: selection:base.partner.merge.automatic.wizard,state:0
|
||||||
|
msgid "Option"
|
||||||
|
msgstr "Option"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Options"
|
||||||
|
msgstr "Options"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_group_by_parent_id
|
||||||
|
msgid "Parent Company"
|
||||||
|
msgstr "Société mère"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Partners"
|
||||||
|
msgstr "Partenaires"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Search duplicates based on duplicated data in"
|
||||||
|
msgstr "Recherche de doublons basés sur les données dupliquées dans"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Select the list of fields used to search for\n"
|
||||||
|
" duplicated records. If you select several fields,\n"
|
||||||
|
" Odoo will propose you to merge only those having\n"
|
||||||
|
" all these fields in common. (not one of the fields)."
|
||||||
|
msgstr "Sélectionnez la liste des champs utilisés pour chercher\n"
|
||||||
|
"les enregistrements dupliqués. Si vous sélectionnez plusieurs champs,\n"
|
||||||
|
"Odoo vous proposera de fusionner seulement ceux qui ont\n"
|
||||||
|
"tous ces champs en commun (pas uniquement l'un des champs)."
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Selected contacts will be merged together.\n"
|
||||||
|
" All documents linked to one of these contacts\n"
|
||||||
|
" will be redirected to the destination contact.\n"
|
||||||
|
" You can remove contacts from this list to avoid merging them."
|
||||||
|
msgstr ""
|
||||||
|
"Sélectionnez les contacts qui seront fusionnés.\n"
|
||||||
|
"Tous les documents liés à un de ces contacts\n"
|
||||||
|
"sera redirigé au contact de destination.\n"
|
||||||
|
"Vous pouvez enlever les contacts de cette liste pour éviter de les fusionner."
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: selection:base.partner.merge.automatic.wizard,state:0
|
||||||
|
msgid "Selection"
|
||||||
|
msgstr "Sélection"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Skip these contacts"
|
||||||
|
msgstr "Ignorez ces contacts"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_state
|
||||||
|
msgid "State"
|
||||||
|
msgstr "État"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "There is no more contacts to merge for this request..."
|
||||||
|
msgstr "Il n'y a plus de contacts a fusionner pour cette requête"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_group_by_vat
|
||||||
|
msgid "VAT"
|
||||||
|
msgstr "TVA"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_wizard_id
|
||||||
|
msgid "Wizard"
|
||||||
|
msgstr "Assistant"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:306
|
||||||
|
#, python-format
|
||||||
|
msgid "You cannot merge a contact with one of his parent."
|
||||||
|
msgstr "Vous ne pouvez pas fusionner un contact avec l'un de ses parents."
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:398
|
||||||
|
#, python-format
|
||||||
|
msgid "You have to specify a filter for your selection"
|
||||||
|
msgstr "Vous devez spécifier un filtre a cette sélection"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model,name:odoo_partner_merge.model_base_partner_merge_automatic_wizard
|
||||||
|
msgid "base.partner.merge.automatic.wizard"
|
||||||
|
msgstr "base.partner.merge.automatic.wizard"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model,name:odoo_partner_merge.model_base_partner_merge_line
|
||||||
|
msgid "base.partner.merge.line"
|
||||||
|
msgstr "base.partner.merge.line"
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "or"
|
||||||
|
msgstr "ou"
|
||||||
|
|
321
odoo_partner_merge/i18n/odoo_partner_merge.pot
Normal file
321
odoo_partner_merge/i18n/odoo_partner_merge.pot
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * odoo_partner_merge
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 10.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2017-06-04 15:11+0000\n"
|
||||||
|
"PO-Revision-Date: 2017-06-04 15:11+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: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_exclude_contact
|
||||||
|
msgid "A user associated to the contact"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:310
|
||||||
|
#, python-format
|
||||||
|
msgid "All contacts must have the same email. Only the Administrator can merge contacts with different emails."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Are you sure to execute the automatic merge of your contacts ?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Are you sure to execute the list of automatic merges of your contacts ?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Automatic Merge Wizard"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Close"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_partner_ids
|
||||||
|
msgid "Contacts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_create_uid
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_create_uid
|
||||||
|
msgid "Created by"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_create_date
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_create_date
|
||||||
|
msgid "Created on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_current_line_id
|
||||||
|
msgid "Current Line"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.actions.act_window,name:odoo_partner_merge.action_partner_deduplicate
|
||||||
|
msgid "Deduplicate Contacts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Deduplicate the other Contacts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_dst_partner_id
|
||||||
|
msgid "Destination Contact"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_display_name
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_display_name
|
||||||
|
msgid "Display Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_group_by_email
|
||||||
|
msgid "Email"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Exclude contacts having"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: selection:base.partner.merge.automatic.wizard,state:0
|
||||||
|
msgid "Finished"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:299
|
||||||
|
#, python-format
|
||||||
|
msgid "For safety reasons, you cannot merge more than 3 contacts together. You can re-open the wizard several times if needed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_number_group
|
||||||
|
msgid "Group of Contacts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_id
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_id
|
||||||
|
msgid "ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_aggr_ids
|
||||||
|
msgid "Ids"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_group_by_is_company
|
||||||
|
msgid "Is Company"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_exclude_journal_item
|
||||||
|
msgid "Journal Items associated to the contact"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard___last_update
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line___last_update
|
||||||
|
msgid "Last Modified on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_write_uid
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_write_uid
|
||||||
|
msgid "Last Updated by"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_write_date
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_write_date
|
||||||
|
msgid "Last Updated on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_line_ids
|
||||||
|
msgid "Lines"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_maximum_group
|
||||||
|
msgid "Maximum of Group of Contacts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Merge Automatically"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Merge Automatically all process"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Merge Contacts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.actions.act_window,name:odoo_partner_merge.action_partner_merge
|
||||||
|
msgid "Merge Selected Contacts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Merge the following contacts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Merge with Manual Check"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:331
|
||||||
|
#, python-format
|
||||||
|
msgid "Merged with the following partners:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_min_id
|
||||||
|
msgid "MinID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_group_by_name
|
||||||
|
msgid "Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:323
|
||||||
|
#, python-format
|
||||||
|
msgid "Only the destination contact may be linked to existing Journal Items. Please ask the Administrator if you need to merge several contacts linked to existing Journal Items."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: selection:base.partner.merge.automatic.wizard,state:0
|
||||||
|
msgid "Option"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Options"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_group_by_parent_id
|
||||||
|
msgid "Parent Company"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Partners"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Search duplicates based on duplicated data in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Select the list of fields used to search for\n"
|
||||||
|
" duplicated records. If you select several fields,\n"
|
||||||
|
" Odoo will propose you to merge only those having\n"
|
||||||
|
" all these fields in common. (not one of the fields)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Selected contacts will be merged together.\n"
|
||||||
|
" All documents linked to one of these contacts\n"
|
||||||
|
" will be redirected to the destination contact.\n"
|
||||||
|
" You can remove contacts from this list to avoid merging them."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: selection:base.partner.merge.automatic.wizard,state:0
|
||||||
|
msgid "Selection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "Skip these contacts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_state
|
||||||
|
msgid "State"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "There is no more contacts to merge for this request..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_automatic_wizard_group_by_vat
|
||||||
|
msgid "VAT"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model.fields,field_description:odoo_partner_merge.field_base_partner_merge_line_wizard_id
|
||||||
|
msgid "Wizard"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:306
|
||||||
|
#, python-format
|
||||||
|
msgid "You cannot merge a contact with one of his parent."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: code:addons/odoo_partner_merge/wizard/base_partner_merge.py:398
|
||||||
|
#, python-format
|
||||||
|
msgid "You have to specify a filter for your selection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model,name:odoo_partner_merge.model_base_partner_merge_automatic_wizard
|
||||||
|
msgid "base.partner.merge.automatic.wizard"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.model,name:odoo_partner_merge.model_base_partner_merge_line
|
||||||
|
msgid "base.partner.merge.line"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: odoo_partner_merge
|
||||||
|
#: model:ir.ui.view,arch_db:odoo_partner_merge.base_partner_merge_automatic_wizard_form
|
||||||
|
msgid "or"
|
||||||
|
msgstr ""
|
||||||
|
|
18
odoo_partner_merge/wizard/__init__.py
Normal file
18
odoo_partner_merge/wizard/__init__.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2017 Fabien Bourgeois <fabien@yaltik.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from . import base_partner_merge
|
654
odoo_partner_merge/wizard/base_partner_merge.py
Normal file
654
odoo_partner_merge/wizard/base_partner_merge.py
Normal file
@ -0,0 +1,654 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from ast import literal_eval
|
||||||
|
from email.utils import parseaddr
|
||||||
|
import functools
|
||||||
|
import htmlentitydefs
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import operator
|
||||||
|
import psycopg2
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Validation Library https://pypi.python.org/pypi/validate_email/1.1
|
||||||
|
from .validate_email import validate_email
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
from odoo import SUPERUSER_ID, _
|
||||||
|
from odoo.exceptions import ValidationError, UserError
|
||||||
|
from odoo.tools import mute_logger
|
||||||
|
|
||||||
|
|
||||||
|
_logger = logging.getLogger('base.partner.merge')
|
||||||
|
|
||||||
|
|
||||||
|
# http://www.php2python.com/wiki/function.html-entity-decode/
|
||||||
|
def html_entity_decode_char(m, defs=htmlentitydefs.entitydefs):
|
||||||
|
try:
|
||||||
|
return defs[m.group(1)]
|
||||||
|
except KeyError:
|
||||||
|
return m.group(0)
|
||||||
|
|
||||||
|
|
||||||
|
def html_entity_decode(string):
|
||||||
|
pattern = re.compile("&(\w+?);")
|
||||||
|
return pattern.sub(html_entity_decode_char, string)
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_email(email):
|
||||||
|
assert isinstance(email, basestring) and email
|
||||||
|
|
||||||
|
result = re.subn(r';|/|:', ',', html_entity_decode(email or ''))[0].split(',')
|
||||||
|
|
||||||
|
emails = [parseaddr(email)[1]
|
||||||
|
for item in result
|
||||||
|
for email in item.split()]
|
||||||
|
|
||||||
|
return [email.lower()
|
||||||
|
for email in emails
|
||||||
|
if validate_email(email)]
|
||||||
|
|
||||||
|
|
||||||
|
class MergePartnerLine(models.TransientModel):
|
||||||
|
|
||||||
|
_name = 'base.partner.merge.line'
|
||||||
|
_order = 'min_id asc'
|
||||||
|
|
||||||
|
wizard_id = fields.Many2one('base.partner.merge.automatic.wizard', 'Wizard')
|
||||||
|
min_id = fields.Integer('MinID')
|
||||||
|
aggr_ids = fields.Char('Ids', required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class MergePartnerAutomatic(models.TransientModel):
|
||||||
|
"""
|
||||||
|
The idea behind this wizard is to create a list of potential partners to
|
||||||
|
merge. We use two objects, the first one is the wizard for the end-user.
|
||||||
|
And the second will contain the partner list to merge.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = 'base.partner.merge.automatic.wizard'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def default_get(self, fields):
|
||||||
|
res = super(MergePartnerAutomatic, self).default_get(fields)
|
||||||
|
active_ids = self.env.context.get('active_ids')
|
||||||
|
if self.env.context.get('active_model') == 'res.partner' and active_ids:
|
||||||
|
res['state'] = 'selection'
|
||||||
|
res['partner_ids'] = active_ids
|
||||||
|
res['dst_partner_id'] = self._get_ordered_partner(active_ids)[-1].id
|
||||||
|
return res
|
||||||
|
|
||||||
|
# Group by
|
||||||
|
group_by_email = fields.Boolean('Email')
|
||||||
|
group_by_name = fields.Boolean('Name')
|
||||||
|
group_by_is_company = fields.Boolean('Is Company')
|
||||||
|
group_by_vat = fields.Boolean('VAT')
|
||||||
|
group_by_parent_id = fields.Boolean('Parent Company')
|
||||||
|
|
||||||
|
state = fields.Selection([
|
||||||
|
('option', 'Option'),
|
||||||
|
('selection', 'Selection'),
|
||||||
|
('finished', 'Finished')
|
||||||
|
], readonly=True, required=True, string='State', default='option')
|
||||||
|
|
||||||
|
number_group = fields.Integer('Group of Contacts', readonly=True)
|
||||||
|
current_line_id = fields.Many2one('base.partner.merge.line', string='Current Line')
|
||||||
|
line_ids = fields.One2many('base.partner.merge.line', 'wizard_id', string='Lines')
|
||||||
|
partner_ids = fields.Many2many('res.partner', string='Contacts')
|
||||||
|
dst_partner_id = fields.Many2one('res.partner', string='Destination Contact')
|
||||||
|
|
||||||
|
exclude_contact = fields.Boolean('A user associated to the contact')
|
||||||
|
exclude_journal_item = fields.Boolean('Journal Items associated to the contact')
|
||||||
|
maximum_group = fields.Integer('Maximum of Group of Contacts')
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# Update method. Core methods to merge steps
|
||||||
|
# ----------------------------------------
|
||||||
|
|
||||||
|
def _get_fk_on(self, table):
|
||||||
|
""" return a list of many2one relation with the given table.
|
||||||
|
:param table : the name of the sql table to return relations
|
||||||
|
:returns a list of tuple 'table name', 'column name'.
|
||||||
|
"""
|
||||||
|
query = """
|
||||||
|
SELECT cl1.relname as table, att1.attname as column
|
||||||
|
FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, pg_attribute as att1, pg_attribute as att2
|
||||||
|
WHERE con.conrelid = cl1.oid
|
||||||
|
AND con.confrelid = cl2.oid
|
||||||
|
AND array_lower(con.conkey, 1) = 1
|
||||||
|
AND con.conkey[1] = att1.attnum
|
||||||
|
AND att1.attrelid = cl1.oid
|
||||||
|
AND cl2.relname = %s
|
||||||
|
AND att2.attname = 'id'
|
||||||
|
AND array_lower(con.confkey, 1) = 1
|
||||||
|
AND con.confkey[1] = att2.attnum
|
||||||
|
AND att2.attrelid = cl2.oid
|
||||||
|
AND con.contype = 'f'
|
||||||
|
"""
|
||||||
|
self._cr.execute(query, (table,))
|
||||||
|
return self._cr.fetchall()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _update_foreign_keys(self, src_partners, dst_partner):
|
||||||
|
""" Update all foreign key from the src_partner to dst_partner. All many2one fields will be updated.
|
||||||
|
:param src_partners : merge source res.partner recordset (does not include destination one)
|
||||||
|
:param dst_partner : record of destination res.partner
|
||||||
|
"""
|
||||||
|
_logger.debug('_update_foreign_keys for dst_partner: %s for src_partners: %s', dst_partner.id, str(src_partners.ids))
|
||||||
|
|
||||||
|
# find the many2one relation to a partner
|
||||||
|
Partner = self.env['res.partner']
|
||||||
|
relations = self._get_fk_on('res_partner')
|
||||||
|
|
||||||
|
for table, column in relations:
|
||||||
|
if 'base_partner_merge_' in table: # ignore two tables
|
||||||
|
continue
|
||||||
|
|
||||||
|
# get list of columns of current table (exept the current fk column)
|
||||||
|
query = "SELECT column_name FROM information_schema.columns WHERE table_name LIKE '%s'" % (table)
|
||||||
|
self._cr.execute(query, ())
|
||||||
|
columns = []
|
||||||
|
for data in self._cr.fetchall():
|
||||||
|
if data[0] != column:
|
||||||
|
columns.append(data[0])
|
||||||
|
|
||||||
|
# do the update for the current table/column in SQL
|
||||||
|
query_dic = {
|
||||||
|
'table': table,
|
||||||
|
'column': column,
|
||||||
|
'value': columns[0],
|
||||||
|
}
|
||||||
|
if len(columns) <= 1:
|
||||||
|
# unique key treated
|
||||||
|
query = """
|
||||||
|
UPDATE "%(table)s" as ___tu
|
||||||
|
SET %(column)s = %%s
|
||||||
|
WHERE
|
||||||
|
%(column)s = %%s AND
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM "%(table)s" as ___tw
|
||||||
|
WHERE
|
||||||
|
%(column)s = %%s AND
|
||||||
|
___tu.%(value)s = ___tw.%(value)s
|
||||||
|
)""" % query_dic
|
||||||
|
for partner in src_partners:
|
||||||
|
self._cr.execute(query, (dst_partner.id, partner.id, dst_partner.id))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
with mute_logger('odoo.sql_db'), self._cr.savepoint():
|
||||||
|
query = 'UPDATE "%(table)s" SET %(column)s = %%s WHERE %(column)s IN %%s' % query_dic
|
||||||
|
self._cr.execute(query, (dst_partner.id, tuple(src_partners.ids),))
|
||||||
|
|
||||||
|
# handle the recursivity with parent relation
|
||||||
|
if column == Partner._parent_name and table == 'res_partner':
|
||||||
|
query = """
|
||||||
|
WITH RECURSIVE cycle(id, parent_id) AS (
|
||||||
|
SELECT id, parent_id FROM res_partner
|
||||||
|
UNION
|
||||||
|
SELECT cycle.id, res_partner.parent_id
|
||||||
|
FROM res_partner, cycle
|
||||||
|
WHERE res_partner.id = cycle.parent_id AND
|
||||||
|
cycle.id != cycle.parent_id
|
||||||
|
)
|
||||||
|
SELECT id FROM cycle WHERE id = parent_id AND id = %s
|
||||||
|
"""
|
||||||
|
self._cr.execute(query, (dst_partner.id,))
|
||||||
|
# NOTE JEM : shouldn't we fetch the data ?
|
||||||
|
except psycopg2.Error:
|
||||||
|
# updating fails, most likely due to a violated unique constraint
|
||||||
|
# keeping record with nonexistent partner_id is useless, better delete it
|
||||||
|
query = 'DELETE FROM %(table)s WHERE %(column)s IN %%s' % query_dic
|
||||||
|
self._cr.execute(query, (tuple(src_partners.ids),))
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _update_reference_fields(self, src_partners, dst_partner):
|
||||||
|
""" Update all reference fields from the src_partner to dst_partner.
|
||||||
|
:param src_partners : merge source res.partner recordset (does not include destination one)
|
||||||
|
:param dst_partner : record of destination res.partner
|
||||||
|
"""
|
||||||
|
_logger.debug('_update_reference_fields for dst_partner: %s for src_partners: %r', dst_partner.id, src_partners.ids)
|
||||||
|
|
||||||
|
def update_records(model, src, field_model='model', field_id='res_id'):
|
||||||
|
Model = self.env[model] if model in self.env else None
|
||||||
|
if Model is None:
|
||||||
|
return
|
||||||
|
records = Model.sudo().search([(field_model, '=', 'res.partner'), (field_id, '=', src.id)])
|
||||||
|
try:
|
||||||
|
with mute_logger('odoo.sql_db'), self._cr.savepoint():
|
||||||
|
return records.sudo().write({field_id: dst_partner.id})
|
||||||
|
except psycopg2.Error:
|
||||||
|
# updating fails, most likely due to a violated unique constraint
|
||||||
|
# keeping record with nonexistent partner_id is useless, better delete it
|
||||||
|
return records.sudo().unlink()
|
||||||
|
|
||||||
|
update_records = functools.partial(update_records)
|
||||||
|
|
||||||
|
for partner in src_partners:
|
||||||
|
update_records('calendar', src=partner, field_model='model_id.model')
|
||||||
|
update_records('ir.attachment', src=partner, field_model='res_model')
|
||||||
|
update_records('mail.followers', src=partner, field_model='res_model')
|
||||||
|
update_records('mail.message', src=partner)
|
||||||
|
update_records('marketing.campaign.workitem', src=partner, field_model='object_id.model')
|
||||||
|
update_records('ir.model.data', src=partner)
|
||||||
|
|
||||||
|
records = self.env['ir.model.fields'].search([('ttype', '=', 'reference')])
|
||||||
|
for record in records.sudo():
|
||||||
|
try:
|
||||||
|
Model = self.env[record.model]
|
||||||
|
field = Model._fields[record.name]
|
||||||
|
except KeyError:
|
||||||
|
# unknown model or field => skip
|
||||||
|
continue
|
||||||
|
|
||||||
|
if field.compute is not None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for partner in src_partners:
|
||||||
|
records_ref = Model.sudo().search([(record.name, '=', 'res.partner,%d' % partner.id)])
|
||||||
|
values = {
|
||||||
|
record.name: 'res.partner,%d' % dst_partner.id,
|
||||||
|
}
|
||||||
|
records_ref.sudo().write(values)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _update_values(self, src_partners, dst_partner):
|
||||||
|
""" Update values of dst_partner with the ones from the src_partners.
|
||||||
|
:param src_partners : recordset of source res.partner
|
||||||
|
:param dst_partner : record of destination res.partner
|
||||||
|
"""
|
||||||
|
_logger.debug('_update_values for dst_partner: %s for src_partners: %r', dst_partner.id, src_partners.ids)
|
||||||
|
|
||||||
|
model_fields = dst_partner._fields
|
||||||
|
|
||||||
|
def write_serializer(item):
|
||||||
|
if isinstance(item, models.BaseModel):
|
||||||
|
return item.id
|
||||||
|
else:
|
||||||
|
return item
|
||||||
|
# get all fields that are not computed or x2many
|
||||||
|
values = dict()
|
||||||
|
for column, field in model_fields.iteritems():
|
||||||
|
if field.type not in ('many2many', 'one2many') and field.compute is None:
|
||||||
|
for item in itertools.chain(src_partners, [dst_partner]):
|
||||||
|
if item[column]:
|
||||||
|
values[column] = write_serializer(item[column])
|
||||||
|
# remove fields that can not be updated (id and parent_id)
|
||||||
|
values.pop('id', None)
|
||||||
|
parent_id = values.pop('parent_id', None)
|
||||||
|
dst_partner.write(values)
|
||||||
|
# try to update the parent_id
|
||||||
|
if parent_id and parent_id != dst_partner.id:
|
||||||
|
try:
|
||||||
|
dst_partner.write({'parent_id': parent_id})
|
||||||
|
except ValidationError:
|
||||||
|
_logger.info('Skip recursive partner hierarchies for parent_id %s of partner: %s', parent_id, dst_partner.id)
|
||||||
|
|
||||||
|
def _merge(self, partner_ids, dst_partner=None):
|
||||||
|
""" private implementation of merge partner
|
||||||
|
:param partner_ids : ids of partner to merge
|
||||||
|
:param dst_partner : record of destination res.partner
|
||||||
|
"""
|
||||||
|
Partner = self.env['res.partner']
|
||||||
|
partner_ids = Partner.browse(partner_ids).exists()
|
||||||
|
if len(partner_ids) < 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(partner_ids) > 3:
|
||||||
|
raise UserError(_("For safety reasons, you cannot merge more than 3 contacts together. You can re-open the wizard several times if needed."))
|
||||||
|
|
||||||
|
# check if the list of partners to merge contains child/parent relation
|
||||||
|
child_ids = self.env['res.partner']
|
||||||
|
for partner_id in partner_ids:
|
||||||
|
child_ids |= Partner.search([('id', 'child_of', [partner_id.id])]) - partner_id
|
||||||
|
if partner_ids & child_ids:
|
||||||
|
raise UserError(_("You cannot merge a contact with one of his parent."))
|
||||||
|
|
||||||
|
# check only admin can merge partners with different emails
|
||||||
|
if SUPERUSER_ID != self.env.uid and len(set(partner.email for partner in partner_ids)) > 1:
|
||||||
|
raise UserError(_("All contacts must have the same email. Only the Administrator can merge contacts with different emails."))
|
||||||
|
|
||||||
|
# remove dst_partner from partners to merge
|
||||||
|
if dst_partner and dst_partner in partner_ids:
|
||||||
|
src_partners = partner_ids - dst_partner
|
||||||
|
else:
|
||||||
|
ordered_partners = self._get_ordered_partner(partner_ids.ids)
|
||||||
|
dst_partner = ordered_partners[-1]
|
||||||
|
src_partners = ordered_partners[:-1]
|
||||||
|
_logger.info("dst_partner: %s", dst_partner.id)
|
||||||
|
|
||||||
|
# FIXME: is it still required to make and exception for account.move.line since accounting v9.0 ?
|
||||||
|
if SUPERUSER_ID != self.env.uid and 'account.move.line' in self.env and self.env['account.move.line'].sudo().search([('partner_id', 'in', [partner.id for partner in src_partners])]):
|
||||||
|
raise UserError(_("Only the destination contact may be linked to existing Journal Items. Please ask the Administrator if you need to merge several contacts linked to existing Journal Items."))
|
||||||
|
|
||||||
|
# call sub methods to do the merge
|
||||||
|
self._update_foreign_keys(src_partners, dst_partner)
|
||||||
|
self._update_reference_fields(src_partners, dst_partner)
|
||||||
|
self._update_values(src_partners, dst_partner)
|
||||||
|
|
||||||
|
_logger.info('(uid = %s) merged the partners %r with %s', self._uid, src_partners.ids, dst_partner.id)
|
||||||
|
dst_partner.message_post(body='%s %s' % (_("Merged with the following partners:"), ", ".join('%s <%s> (ID %s)' % (p.name, p.email or 'n/a', p.id) for p in src_partners)))
|
||||||
|
|
||||||
|
# delete source partner, since they are merged
|
||||||
|
src_partners.unlink()
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ----------------------------------------
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _generate_query(self, fields, maximum_group=100):
|
||||||
|
""" Build the SQL query on res.partner table to group them according to given criteria
|
||||||
|
:param fields : list of column names to group by the partners
|
||||||
|
:param maximum_group : limit of the query
|
||||||
|
"""
|
||||||
|
# make the list of column to group by in sql query
|
||||||
|
sql_fields = []
|
||||||
|
for field in fields:
|
||||||
|
if field in ['email', 'name']:
|
||||||
|
sql_fields.append('lower(%s)' % field)
|
||||||
|
elif field in ['vat']:
|
||||||
|
sql_fields.append("replace(%s, ' ', '')" % field)
|
||||||
|
else:
|
||||||
|
sql_fields.append(field)
|
||||||
|
group_fields = ', '.join(sql_fields)
|
||||||
|
|
||||||
|
# where clause : for given group by columns, only keep the 'not null' record
|
||||||
|
filters = []
|
||||||
|
for field in fields:
|
||||||
|
if field in ['email', 'name', 'vat']:
|
||||||
|
filters.append((field, 'IS NOT', 'NULL'))
|
||||||
|
criteria = ' AND '.join('%s %s %s' % (field, operator, value) for field, operator, value in filters)
|
||||||
|
|
||||||
|
# build the query
|
||||||
|
text = [
|
||||||
|
"SELECT min(id), array_agg(id)",
|
||||||
|
"FROM res_partner",
|
||||||
|
]
|
||||||
|
|
||||||
|
if criteria:
|
||||||
|
text.append('WHERE %s' % criteria)
|
||||||
|
|
||||||
|
text.extend([
|
||||||
|
"GROUP BY %s" % group_fields,
|
||||||
|
"HAVING COUNT(*) >= 2",
|
||||||
|
"ORDER BY min(id)",
|
||||||
|
])
|
||||||
|
|
||||||
|
if maximum_group:
|
||||||
|
text.append("LIMIT %s" % maximum_group,)
|
||||||
|
|
||||||
|
return ' '.join(text)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _compute_selected_groupby(self):
|
||||||
|
""" Returns the list of field names the partner can be grouped (as merge
|
||||||
|
criteria) according to the option checked on the wizard
|
||||||
|
"""
|
||||||
|
groups = []
|
||||||
|
group_by_prefix = 'group_by_'
|
||||||
|
|
||||||
|
for field_name in self._fields:
|
||||||
|
if field_name.startswith(group_by_prefix):
|
||||||
|
if getattr(self, field_name, False):
|
||||||
|
groups.append(field_name[len(group_by_prefix):])
|
||||||
|
|
||||||
|
if not groups:
|
||||||
|
raise UserError(_("You have to specify a filter for your selection"))
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _partner_use_in(self, aggr_ids, models):
|
||||||
|
""" Check if there is no occurence of this group of partner in the selected model
|
||||||
|
:param aggr_ids : stringified list of partner ids separated with a comma (sql array_agg)
|
||||||
|
:param models : dict mapping a model name with its foreign key with res_partner table
|
||||||
|
"""
|
||||||
|
return any(
|
||||||
|
self.env[model].search_count([(field, 'in', aggr_ids)])
|
||||||
|
for model, field in models.iteritems()
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_ordered_partner(self, partner_ids):
|
||||||
|
""" Helper : returns a `res.partner` recordset ordered by create_date/active fields
|
||||||
|
:param partner_ids : list of partner ids to sort
|
||||||
|
"""
|
||||||
|
return self.env['res.partner'].browse(partner_ids).sorted(
|
||||||
|
key=lambda p: (p.active, p.create_date),
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _compute_models(self):
|
||||||
|
""" Compute the different models needed by the system if you want to exclude some partners. """
|
||||||
|
model_mapping = {}
|
||||||
|
if self.exclude_contact:
|
||||||
|
model_mapping['res.users'] = 'partner_id'
|
||||||
|
if 'account.move.line' in self.env and self.exclude_journal_item:
|
||||||
|
model_mapping['account.move.line'] = 'partner_id'
|
||||||
|
return model_mapping
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# Actions
|
||||||
|
# ----------------------------------------
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_skip(self):
|
||||||
|
""" Skip this wizard line. Don't compute any thing, and simply redirect to the new step."""
|
||||||
|
if self.current_line_id:
|
||||||
|
self.current_line_id.unlink()
|
||||||
|
return self._action_next_screen()
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _action_next_screen(self):
|
||||||
|
""" return the action of the next screen ; this means the wizard is set to treat the
|
||||||
|
next wizard line. Each line is a subset of partner that can be merged together.
|
||||||
|
If no line left, the end screen will be displayed (but an action is still returned).
|
||||||
|
"""
|
||||||
|
self.invalidate_cache() # FIXME: is this still necessary?
|
||||||
|
values = {}
|
||||||
|
if self.line_ids:
|
||||||
|
# in this case, we try to find the next record.
|
||||||
|
current_line = self.line_ids[0]
|
||||||
|
current_partner_ids = literal_eval(current_line.aggr_ids)
|
||||||
|
values.update({
|
||||||
|
'current_line_id': current_line.id,
|
||||||
|
'partner_ids': [(6, 0, current_partner_ids)],
|
||||||
|
'dst_partner_id': self._get_ordered_partner(current_partner_ids)[-1].id,
|
||||||
|
'state': 'selection',
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
values.update({
|
||||||
|
'current_line_id': False,
|
||||||
|
'partner_ids': [],
|
||||||
|
'state': 'finished',
|
||||||
|
})
|
||||||
|
|
||||||
|
self.write(values)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': self._name,
|
||||||
|
'res_id': self.id,
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'new',
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _process_query(self, query):
|
||||||
|
""" Execute the select request and write the result in this wizard
|
||||||
|
:param query : the SQL query used to fill the wizard line
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
model_mapping = self._compute_models()
|
||||||
|
|
||||||
|
# group partner query
|
||||||
|
self._cr.execute(query)
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
for min_id, aggr_ids in self._cr.fetchall():
|
||||||
|
# To ensure that the used partners are accessible by the user
|
||||||
|
partners = self.env['res.partner'].search([('id', 'in', aggr_ids)])
|
||||||
|
if len(partners) < 2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# exclude partner according to options
|
||||||
|
if model_mapping and self._partner_use_in(partners.ids, model_mapping):
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.env['base.partner.merge.line'].create({
|
||||||
|
'wizard_id': self.id,
|
||||||
|
'min_id': min_id,
|
||||||
|
'aggr_ids': partners.ids,
|
||||||
|
})
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
self.write({
|
||||||
|
'state': 'selection',
|
||||||
|
'number_group': counter,
|
||||||
|
})
|
||||||
|
|
||||||
|
_logger.info("counter: %s", counter)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_start_manual_process(self):
|
||||||
|
""" Start the process 'Merge with Manual Check'. Fill the wizard according to the group_by and exclude
|
||||||
|
options, and redirect to the first step (treatment of first wizard line). After, for each subset of
|
||||||
|
partner to merge, the wizard will be actualized.
|
||||||
|
- Compute the selected groups (with duplication)
|
||||||
|
- If the user has selected the 'exclude_xxx' fields, avoid the partners
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
groups = self._compute_selected_groupby()
|
||||||
|
query = self._generate_query(groups, self.maximum_group)
|
||||||
|
self._process_query(query)
|
||||||
|
return self._action_next_screen()
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_start_automatic_process(self):
|
||||||
|
""" Start the process 'Merge Automatically'. This will fill the wizard with the same mechanism as 'Merge
|
||||||
|
with Manual Check', but instead of refreshing wizard with the current line, it will automatically process
|
||||||
|
all lines by merging partner grouped according to the checked options.
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
self.action_start_manual_process() # here we don't redirect to the next screen, since it is automatic process
|
||||||
|
self.invalidate_cache() # FIXME: is this still necessary?
|
||||||
|
|
||||||
|
for line in self.line_ids:
|
||||||
|
partner_ids = literal_eval(line.aggr_ids)
|
||||||
|
self._merge(partner_ids)
|
||||||
|
line.unlink()
|
||||||
|
self._cr.commit() # TODO JEM : explain why
|
||||||
|
|
||||||
|
self.write({'state': 'finished'})
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': self._name,
|
||||||
|
'res_id': self.id,
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'new',
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def parent_migration_process_cb(self):
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
query = """
|
||||||
|
SELECT
|
||||||
|
min(p1.id),
|
||||||
|
array_agg(DISTINCT p1.id)
|
||||||
|
FROM
|
||||||
|
res_partner as p1
|
||||||
|
INNER join
|
||||||
|
res_partner as p2
|
||||||
|
ON
|
||||||
|
p1.email = p2.email AND
|
||||||
|
p1.name = p2.name AND
|
||||||
|
(p1.parent_id = p2.id OR p1.id = p2.parent_id)
|
||||||
|
WHERE
|
||||||
|
p2.id IS NOT NULL
|
||||||
|
GROUP BY
|
||||||
|
p1.email,
|
||||||
|
p1.name,
|
||||||
|
CASE WHEN p1.parent_id = p2.id THEN p2.id
|
||||||
|
ELSE p1.id
|
||||||
|
END
|
||||||
|
HAVING COUNT(*) >= 2
|
||||||
|
ORDER BY
|
||||||
|
min(p1.id)
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._process_query(query)
|
||||||
|
|
||||||
|
for line in self.line_ids:
|
||||||
|
partner_ids = literal_eval(line.aggr_ids)
|
||||||
|
self._merge(partner_ids)
|
||||||
|
line.unlink()
|
||||||
|
self._cr.commit()
|
||||||
|
|
||||||
|
self.write({'state': 'finished'})
|
||||||
|
|
||||||
|
self._cr.execute("""
|
||||||
|
UPDATE
|
||||||
|
res_partner
|
||||||
|
SET
|
||||||
|
is_company = NULL,
|
||||||
|
parent_id = NULL
|
||||||
|
WHERE
|
||||||
|
parent_id = id
|
||||||
|
""")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': self._name,
|
||||||
|
'res_id': self.id,
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'new',
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_update_all_process(self):
|
||||||
|
self.ensure_one()
|
||||||
|
self.parent_migration_process_cb()
|
||||||
|
|
||||||
|
# NOTE JEM : seems louche to create a new wizard instead of reuse the current one with updated options.
|
||||||
|
# since it is like this from the initial commit of this wizard, I don't change it. yet ...
|
||||||
|
wizard = self.create({'group_by_vat': True, 'group_by_email': True, 'group_by_name': True})
|
||||||
|
wizard.action_start_automatic_process()
|
||||||
|
|
||||||
|
# NOTE JEM : no idea if this query is usefull
|
||||||
|
self._cr.execute("""
|
||||||
|
UPDATE
|
||||||
|
res_partner
|
||||||
|
SET
|
||||||
|
is_company = NULL
|
||||||
|
WHERE
|
||||||
|
parent_id IS NOT NULL AND
|
||||||
|
is_company IS NOT NULL
|
||||||
|
""")
|
||||||
|
|
||||||
|
return self._action_next_screen()
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_merge(self):
|
||||||
|
""" Merge Contact button. Merge the selected partners, and redirect to
|
||||||
|
the end screen (since there is no other wizard line to process.
|
||||||
|
"""
|
||||||
|
if not self.partner_ids:
|
||||||
|
self.write({'state': 'finished'})
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': self._name,
|
||||||
|
'res_id': self.id,
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'new',
|
||||||
|
}
|
||||||
|
|
||||||
|
self._merge(self.partner_ids.ids, self.dst_partner_id)
|
||||||
|
|
||||||
|
if self.current_line_id:
|
||||||
|
self.current_line_id.unlink()
|
||||||
|
|
||||||
|
return self._action_next_screen()
|
115
odoo_partner_merge/wizard/base_partner_merge_views.xml
Normal file
115
odoo_partner_merge/wizard/base_partner_merge_views.xml
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="action_partner_deduplicate" model="ir.actions.act_window">
|
||||||
|
<field name="name">Deduplicate Contacts</field>
|
||||||
|
<field name="res_model">base.partner.merge.automatic.wizard</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
<field name="context">{'active_test': False}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="base_partner_merge_automatic_wizard_form" model="ir.ui.view">
|
||||||
|
<field name='name'>base.partner.merge.automatic.wizard.form</field>
|
||||||
|
<field name='model'>base.partner.merge.automatic.wizard</field>
|
||||||
|
<field name='arch' type='xml'>
|
||||||
|
<form string='Automatic Merge Wizard'>
|
||||||
|
<sheet>
|
||||||
|
<group attrs="{'invisible': [('state', '!=', 'finished')]}" col="1">
|
||||||
|
<h2>There is no more contacts to merge for this request...</h2>
|
||||||
|
<button name="%(action_partner_deduplicate)d" string="Deduplicate the other Contacts" class="oe_highlight" type="action"/>
|
||||||
|
</group>
|
||||||
|
<p class="oe_grey" attrs="{'invisible': [('state', '!=', ('option'))]}">
|
||||||
|
Select the list of fields used to search for
|
||||||
|
duplicated records. If you select several fields,
|
||||||
|
Odoo will propose you to merge only those having
|
||||||
|
all these fields in common. (not one of the fields).
|
||||||
|
</p>
|
||||||
|
<group attrs="{'invisible': ['|', ('state', 'not in', ('selection', 'finished')), ('number_group', '=', 0)]}">
|
||||||
|
<field name="state" invisible="1" />
|
||||||
|
<field name="number_group"/>
|
||||||
|
</group>
|
||||||
|
<group string="Search duplicates based on duplicated data in"
|
||||||
|
attrs="{'invisible': [('state', 'not in', ('option',))]}">
|
||||||
|
<field name='group_by_email' />
|
||||||
|
<field name='group_by_name' />
|
||||||
|
<field name='group_by_is_company' />
|
||||||
|
<field name='group_by_vat' />
|
||||||
|
<field name='group_by_parent_id' />
|
||||||
|
</group>
|
||||||
|
<group string="Exclude contacts having"
|
||||||
|
attrs="{'invisible': [('state', 'not in', ('option',))]}">
|
||||||
|
<field name='exclude_contact' />
|
||||||
|
<field name='exclude_journal_item' />
|
||||||
|
</group>
|
||||||
|
<separator string="Options" attrs="{'invisible': [('state', 'not in', ('option',))]}"/>
|
||||||
|
<group attrs="{'invisible': [('state', 'not in', ('option','finished'))]}">
|
||||||
|
<field name='maximum_group' attrs="{'readonly': [('state', 'in', ('finished'))]}"/>
|
||||||
|
</group>
|
||||||
|
<separator string="Merge the following contacts"
|
||||||
|
attrs="{'invisible': [('state', 'in', ('option', 'finished'))]}"/>
|
||||||
|
<group attrs="{'invisible': [('state', 'in', ('option', 'finished'))]}" col="1">
|
||||||
|
<p class="oe_grey">
|
||||||
|
Selected contacts will be merged together.
|
||||||
|
All documents linked to one of these contacts
|
||||||
|
will be redirected to the destination contact.
|
||||||
|
You can remove contacts from this list to avoid merging them.
|
||||||
|
</p>
|
||||||
|
<group col="2">
|
||||||
|
<field name="dst_partner_id" domain="[('id', 'in', partner_ids and partner_ids[0] and partner_ids[0][2] or False)]" attrs="{'required': [('state', '=', 'selection')]}"/>
|
||||||
|
</group>
|
||||||
|
<field name="partner_ids" nolabel="1">
|
||||||
|
<tree string="Partners">
|
||||||
|
<field name="id" />
|
||||||
|
<field name="display_name" />
|
||||||
|
<field name="email" />
|
||||||
|
<field name="is_company" />
|
||||||
|
<field name="vat" />
|
||||||
|
<field name="country_id" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
<footer>
|
||||||
|
<button name='action_merge' string='Merge Contacts'
|
||||||
|
class='oe_highlight'
|
||||||
|
type='object'
|
||||||
|
attrs="{'invisible': [('state', 'in', ('option', 'finished' ))]}" />
|
||||||
|
<button name='action_skip' string='Skip these contacts'
|
||||||
|
type='object'
|
||||||
|
attrs="{'invisible': [('state', '!=', 'selection')]}" />
|
||||||
|
<button name='action_start_manual_process'
|
||||||
|
string='Merge with Manual Check'
|
||||||
|
type='object' class='oe_highlight'
|
||||||
|
attrs="{'invisible': [('state', '!=', 'option')]}" />
|
||||||
|
<button name='action_start_automatic_process'
|
||||||
|
string='Merge Automatically'
|
||||||
|
type='object' class='oe_highlight'
|
||||||
|
confirm="Are you sure to execute the automatic merge of your contacts ?"
|
||||||
|
attrs="{'invisible': [('state', '!=', 'option')]}" />
|
||||||
|
<button name='action_update_all_process'
|
||||||
|
string='Merge Automatically all process'
|
||||||
|
type='object'
|
||||||
|
confirm="Are you sure to execute the list of automatic merges of your contacts ?"
|
||||||
|
attrs="{'invisible': [('state', '!=', 'option')]}" />
|
||||||
|
<span class="or_cancel" attrs="{'invisible': [('state', '=', 'finished')]} ">or
|
||||||
|
<button special="cancel" string="Cancel" type="object" class="oe_link oe_inline"/>
|
||||||
|
</span>
|
||||||
|
<span class="or_cancel" attrs="{'invisible': [('state', '!=', 'finished')]} ">
|
||||||
|
<button special="cancel" string="Close" type="object" class="oe_link oe_inline"/>
|
||||||
|
</span>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<act_window
|
||||||
|
id="action_partner_merge"
|
||||||
|
res_model="base.partner.merge.automatic.wizard"
|
||||||
|
src_model="res.partner"
|
||||||
|
target="new"
|
||||||
|
multi="True"
|
||||||
|
key2="client_action_multi"
|
||||||
|
view_mode="form"
|
||||||
|
name="Merge Selected Contacts"/>
|
||||||
|
</odoo>
|
123
odoo_partner_merge/wizard/validate_email.py
Normal file
123
odoo_partner_merge/wizard/validate_email.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# RFC 2822 - style email validation for Python
|
||||||
|
# (c) 2012 Syrus Akbary <me@syrusakbary.com>
|
||||||
|
# Extended from (c) 2011 Noel Bush <noel@aitools.org>
|
||||||
|
# for support of mx and user check
|
||||||
|
# This code is made available to you under the GNU LGPL v3.
|
||||||
|
#
|
||||||
|
# This module provides a single method, valid_email_address(),
|
||||||
|
# which returns True or False to indicate whether a given address
|
||||||
|
# is valid according to the 'addr-spec' part of the specification
|
||||||
|
# given in RFC 2822. Ideally, we would like to find this
|
||||||
|
# in some other library, already thoroughly tested and well-
|
||||||
|
# maintained. The standard Python library email.utils
|
||||||
|
# contains a parse_addr() function, but it is not sufficient
|
||||||
|
# to detect many malformed addresses.
|
||||||
|
#
|
||||||
|
# This implementation aims to be faithful to the RFC, with the
|
||||||
|
# exception of a circular definition (see comments below), and
|
||||||
|
# with the omission of the pattern components marked as "obsolete".
|
||||||
|
|
||||||
|
import re
|
||||||
|
import smtplib
|
||||||
|
import socket
|
||||||
|
|
||||||
|
try:
|
||||||
|
import DNS
|
||||||
|
ServerError = DNS.ServerError
|
||||||
|
except:
|
||||||
|
DNS = None
|
||||||
|
class ServerError(Exception): pass
|
||||||
|
# All we are really doing is comparing the input string to one
|
||||||
|
# gigantic regular expression. But building that regexp, and
|
||||||
|
# ensuring its correctness, is made much easier by assembling it
|
||||||
|
# from the "tokens" defined by the RFC. Each of these tokens is
|
||||||
|
# tested in the accompanying unit test file.
|
||||||
|
#
|
||||||
|
# The section of RFC 2822 from which each pattern component is
|
||||||
|
# derived is given in an accompanying comment.
|
||||||
|
#
|
||||||
|
# (To make things simple, every string below is given as 'raw',
|
||||||
|
# even when it's not strictly necessary. This way we don't forget
|
||||||
|
# when it is necessary.)
|
||||||
|
#
|
||||||
|
WSP = r'[ \t]' # see 2.2.2. Structured Header Field Bodies
|
||||||
|
CRLF = r'(?:\r\n)' # see 2.2.3. Long Header Fields
|
||||||
|
NO_WS_CTL = r'\x01-\x08\x0b\x0c\x0f-\x1f\x7f' # see 3.2.1. Primitive Tokens
|
||||||
|
QUOTED_PAIR = r'(?:\\.)' # see 3.2.2. Quoted characters
|
||||||
|
FWS = r'(?:(?:' + WSP + r'*' + CRLF + r')?' + \
|
||||||
|
WSP + r'+)' # see 3.2.3. Folding white space and comments
|
||||||
|
CTEXT = r'[' + NO_WS_CTL + \
|
||||||
|
r'\x21-\x27\x2a-\x5b\x5d-\x7e]' # see 3.2.3
|
||||||
|
CCONTENT = r'(?:' + CTEXT + r'|' + \
|
||||||
|
QUOTED_PAIR + r')' # see 3.2.3 (NB: The RFC includes COMMENT here
|
||||||
|
# as well, but that would be circular.)
|
||||||
|
COMMENT = r'\((?:' + FWS + r'?' + CCONTENT + \
|
||||||
|
r')*' + FWS + r'?\)' # see 3.2.3
|
||||||
|
CFWS = r'(?:' + FWS + r'?' + COMMENT + ')*(?:' + \
|
||||||
|
FWS + '?' + COMMENT + '|' + FWS + ')' # see 3.2.3
|
||||||
|
ATEXT = r'[\w!#$%&\'\*\+\-/=\?\^`\{\|\}~]' # see 3.2.4. Atom
|
||||||
|
ATOM = CFWS + r'?' + ATEXT + r'+' + CFWS + r'?' # see 3.2.4
|
||||||
|
DOT_ATOM_TEXT = ATEXT + r'+(?:\.' + ATEXT + r'+)*' # see 3.2.4
|
||||||
|
DOT_ATOM = CFWS + r'?' + DOT_ATOM_TEXT + CFWS + r'?' # see 3.2.4
|
||||||
|
QTEXT = r'[' + NO_WS_CTL + \
|
||||||
|
r'\x21\x23-\x5b\x5d-\x7e]' # see 3.2.5. Quoted strings
|
||||||
|
QCONTENT = r'(?:' + QTEXT + r'|' + \
|
||||||
|
QUOTED_PAIR + r')' # see 3.2.5
|
||||||
|
QUOTED_STRING = CFWS + r'?' + r'"(?:' + FWS + \
|
||||||
|
r'?' + QCONTENT + r')*' + FWS + \
|
||||||
|
r'?' + r'"' + CFWS + r'?'
|
||||||
|
LOCAL_PART = r'(?:' + DOT_ATOM + r'|' + \
|
||||||
|
QUOTED_STRING + r')' # see 3.4.1. Addr-spec specification
|
||||||
|
DTEXT = r'[' + NO_WS_CTL + r'\x21-\x5a\x5e-\x7e]' # see 3.4.1
|
||||||
|
DCONTENT = r'(?:' + DTEXT + r'|' + \
|
||||||
|
QUOTED_PAIR + r')' # see 3.4.1
|
||||||
|
DOMAIN_LITERAL = CFWS + r'?' + r'\[' + \
|
||||||
|
r'(?:' + FWS + r'?' + DCONTENT + \
|
||||||
|
r')*' + FWS + r'?\]' + CFWS + r'?' # see 3.4.1
|
||||||
|
DOMAIN = r'(?:' + DOT_ATOM + r'|' + \
|
||||||
|
DOMAIN_LITERAL + r')' # see 3.4.1
|
||||||
|
ADDR_SPEC = LOCAL_PART + r'@' + DOMAIN # see 3.4.1
|
||||||
|
|
||||||
|
# A valid address will match exactly the 3.4.1 addr-spec.
|
||||||
|
VALID_ADDRESS_REGEXP = '^' + ADDR_SPEC + '$'
|
||||||
|
|
||||||
|
def validate_email(email, check_mx=False,verify=False):
|
||||||
|
|
||||||
|
"""Indicate whether the given string is a valid email address
|
||||||
|
according to the 'addr-spec' portion of RFC 2822 (see section
|
||||||
|
3.4.1). Parts of the spec that are marked obsolete are *not*
|
||||||
|
included in this test, and certain arcane constructions that
|
||||||
|
depend on circular definitions in the spec may not pass, but in
|
||||||
|
general this should correctly identify any email address likely
|
||||||
|
to be in use as of 2011."""
|
||||||
|
try:
|
||||||
|
assert re.match(VALID_ADDRESS_REGEXP, email) is not None
|
||||||
|
check_mx |= verify
|
||||||
|
if check_mx:
|
||||||
|
if not DNS: raise Exception('For check the mx records or check if the email exists you must have installed pyDNS python package')
|
||||||
|
DNS.DiscoverNameServers()
|
||||||
|
hostname = email[email.find('@')+1:]
|
||||||
|
mx_hosts = DNS.mxlookup(hostname)
|
||||||
|
for mx in mx_hosts:
|
||||||
|
try:
|
||||||
|
smtp = smtplib.SMTP()
|
||||||
|
smtp.connect(mx[1])
|
||||||
|
if not verify: return True
|
||||||
|
status, _ = smtp.helo()
|
||||||
|
if status != 250: continue
|
||||||
|
smtp.mail('')
|
||||||
|
status, _ = smtp.rcpt(email)
|
||||||
|
if status != 250: return False
|
||||||
|
break
|
||||||
|
except smtplib.SMTPServerDisconnected: #Server not permits verify user
|
||||||
|
break
|
||||||
|
except smtplib.SMTPConnectError:
|
||||||
|
continue
|
||||||
|
except (AssertionError, ServerError):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# import sys
|
||||||
|
|
||||||
|
# sys.modules[__name__],sys.modules['validate_email_module'] = validate_email,sys.modules[__name__]
|
||||||
|
# from validate_email_module import *
|
Loading…
Reference in New Issue
Block a user