From e2fe24c4587b69eaec02bc273aaf301c322446e8 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Mon, 9 Oct 2017 11:01:02 +0200 Subject: [PATCH] Add module partner_multi_relation_parent --- partner_multi_relation_parent/README.rst | 65 +++++++ partner_multi_relation_parent/__init__.py | 4 + partner_multi_relation_parent/__manifest__.py | 19 ++ partner_multi_relation_parent/data/data.xml | 48 +++++ partner_multi_relation_parent/i18n/nl.po | 134 ++++++++++++++ .../i18n/partner_multi_relation_parent.pot | 131 ++++++++++++++ .../models/__init__.py | 6 + .../models/res_partner_relation.py | 114 ++++++++++++ .../models/res_partner_relation_all.py | 167 ++++++++++++++++++ .../models/res_partner_relation_type.py | 48 +++++ .../static/description/icon.png | Bin 0 -> 12960 bytes .../tests/__init__.py | 4 + .../test_partner_multi_relation_parent.py | 156 ++++++++++++++++ .../views/res_partner_relation_type.xml | 18 ++ 14 files changed, 914 insertions(+) create mode 100644 partner_multi_relation_parent/README.rst create mode 100644 partner_multi_relation_parent/__init__.py create mode 100644 partner_multi_relation_parent/__manifest__.py create mode 100644 partner_multi_relation_parent/data/data.xml create mode 100644 partner_multi_relation_parent/i18n/nl.po create mode 100644 partner_multi_relation_parent/i18n/partner_multi_relation_parent.pot create mode 100644 partner_multi_relation_parent/models/__init__.py create mode 100644 partner_multi_relation_parent/models/res_partner_relation.py create mode 100644 partner_multi_relation_parent/models/res_partner_relation_all.py create mode 100644 partner_multi_relation_parent/models/res_partner_relation_type.py create mode 100644 partner_multi_relation_parent/static/description/icon.png create mode 100644 partner_multi_relation_parent/tests/__init__.py create mode 100644 partner_multi_relation_parent/tests/test_partner_multi_relation_parent.py create mode 100644 partner_multi_relation_parent/views/res_partner_relation_type.xml diff --git a/partner_multi_relation_parent/README.rst b/partner_multi_relation_parent/README.rst new file mode 100644 index 000000000..4ba583e4e --- /dev/null +++ b/partner_multi_relation_parent/README.rst @@ -0,0 +1,65 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +=================================== +Parent contact Mapping in relations +=================================== + +This module maps automatically creates contact relations for contact +addresss of a partner. Inversely, you can create a contact relation between a +person and another partner, and the person will automatically become a +contact address for the other partner. A person may only have one contact +relation, because a partner may only have one parent. + + + +Known issues / Roadmap +====================== + +* hide/forbade the deletion of the "Is contact of" and "Has Contact" installed + relation types + +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 smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Giovanni Francesco Capalbo +* Ronald Portier + +Do not contact contributors directly about help with questions or problems +concerning this addon, but use the +`community mailing list `_ or the +`appropriate specialized mailinglist `_ +for help, and the bug tracker linked in `Bug Tracker`_ above for +technical issues. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +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. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/partner_multi_relation_parent/__init__.py b/partner_multi_relation_parent/__init__.py new file mode 100644 index 000000000..3e51f5587 --- /dev/null +++ b/partner_multi_relation_parent/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import models diff --git a/partner_multi_relation_parent/__manifest__.py b/partner_multi_relation_parent/__manifest__.py new file mode 100644 index 000000000..4405f8a4e --- /dev/null +++ b/partner_multi_relation_parent/__manifest__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Partner Contact Mapping in relations", + "version": "10.0.1.0.0", + "author": "Therp BV,Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Customer Relationship Management", + "summary": "Show partner addresses also as relations", + "depends": [ + 'partner_multi_relation', + ], + "data": [ + 'data/data.xml', + 'views/res_partner_relation_type.xml', + ], + "installable": True, +} diff --git a/partner_multi_relation_parent/data/data.xml b/partner_multi_relation_parent/data/data.xml new file mode 100644 index 000000000..b53179884 --- /dev/null +++ b/partner_multi_relation_parent/data/data.xml @@ -0,0 +1,48 @@ + + + + + Is contact of + Has contact + contact + + restrict + + + + Is invoice address for + Has invoice address + invoice + + restrict + + + + Is delivery address for + Has delivery address + delivery + + restrict + + + + Is alternative address for + Has alternative address + other + + restrict + + + diff --git a/partner_multi_relation_parent/i18n/nl.po b/partner_multi_relation_parent/i18n/nl.po new file mode 100644 index 000000000..e4716e4cf --- /dev/null +++ b/partner_multi_relation_parent/i18n/nl.po @@ -0,0 +1,134 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_multi_relation_parent +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-11-01 09:32+0000\n" +"PO-Revision-Date: 2017-11-01 09:32+0000\n" +"Last-Translator: <>\n" +"Language-Team: Dutch (https://www.transifex.com/oca/teams/23907/nl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: partner_multi_relation_parent +#: model:ir.model,name:partner_multi_relation_parent.model_res_partner_relation_all +msgid "All (non-inverse + inverse) relations between partners" +msgstr "Alle connecties (van beide kanten) tussen relaties" + +#. module: partner_multi_relation_parent +#: selection:res.partner.relation.type,partner_type:0 +msgid "Contact" +msgstr "Contact" + +#. module: partner_multi_relation_parent +#: code:addons/partner_multi_relation_parent/models/res_partner_relation_all.py:145 +#, python-format +msgid "Creating a relation for address with type %s is not allowed at self time." +msgstr "Het leggen van een connectie tussen partners van type %s is op dit moment niet toegestaan." + +#. module: partner_multi_relation_parent +#: code:addons/partner_multi_relation_parent/models/res_partner_relation_all.py:74 +#, python-format +msgid "Creating a relation for address with type %s is not allowed at this time." +msgstr "Het aanmaken van een adres met type %s is op dit moment niet toegestaan." + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name_inverse:partner_multi_relation_parent.relation_type_parent_other +msgid "Has alternative address" +msgstr "Heeft alternatief adres" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name_inverse:partner_multi_relation_parent.relation_type_parent_contact +msgid "Has contact" +msgstr "Heeft contact" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name_inverse:partner_multi_relation_parent.relation_type_parent_delivery +msgid "Has delivery address" +msgstr "Heeft afleveradres" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name_inverse:partner_multi_relation_parent.relation_type_parent_invoice +msgid "Has invoice address" +msgstr "Heeft factuuradres" + +#. module: partner_multi_relation_parent +#: model:ir.model.fields,help:partner_multi_relation_parent.field_res_partner_relation_type_partner_type +msgid "If filled connections will be automatically created when partners of the specified type are linked to a parent.\n" +"Also the parent of the left contact will be updated when connections of this type are added or updated." +msgstr "Indien ingevuld, dan zullen er automatisch connecties aangemaakt worden wanneer een adres van dit type voor een relatie wordt aangemaakt.\n" +"Tevens zal de linker partner automatisch gedefinieerd worden als een adres van dit type voor de relatie aan de rechterkant van de connectie." + +#. module: partner_multi_relation_parent +#: selection:res.partner.relation.type,partner_type:0 +msgid "Invoice address" +msgstr "Factuuradres" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name:partner_multi_relation_parent.relation_type_parent_other +msgid "Is alternative address for" +msgstr "Is een alternatief adres voor" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name:partner_multi_relation_parent.relation_type_parent_contact +msgid "Is contact of" +msgstr "Is contactadres voor" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name:partner_multi_relation_parent.relation_type_parent_delivery +msgid "Is delivery address for" +msgstr "Is afleveradres voor" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name:partner_multi_relation_parent.relation_type_parent_invoice +msgid "Is invoice address for" +msgstr "Is factuuradres voor" + +#. module: partner_multi_relation_parent +#: selection:res.partner.relation.type,partner_type:0 +msgid "Other address" +msgstr "Overig adres" + +#. module: partner_multi_relation_parent +#: model:ir.model,name:partner_multi_relation_parent.model_res_partner +msgid "Partner" +msgstr "Relatie" + +#. module: partner_multi_relation_parent +#: model:ir.model.fields,field_description:partner_multi_relation_parent.field_res_partner_relation_type_partner_type +msgid "Partner Address Type" +msgstr "Soort adres" + +#. module: partner_multi_relation_parent +#: model:ir.model,name:partner_multi_relation_parent.model_res_partner_relation_type +msgid "Partner Relation Type" +msgstr "Type connectie voor de relatie" + +#. module: partner_multi_relation_parent +#: model:ir.model,name:partner_multi_relation_parent.model_res_partner_relation +msgid "Partner relation" +msgstr "Connectie" + +#. module: partner_multi_relation_parent +#: selection:res.partner.relation.type,partner_type:0 +msgid "Shipping address" +msgstr "Afleveradres" + +#. module: partner_multi_relation_parent +#: model:ir.model.fields,field_description:partner_multi_relation_parent.field_res_partner_relation_type_partner_synchronization_active +msgid "Synchronize relations and addresses" +msgstr "Synchroniseer connecties en adressen" + +#. module: partner_multi_relation_parent +#: model:ir.model.fields,help:partner_multi_relation_parent.field_res_partner_relation_type_partner_synchronization_active +msgid "This field can only be true if Partner Address Type filled.\n" +" When enabled will make sure that for all these connections the left partner will have the right partner as parent." +msgstr "Dit veld kan alleen aangevinkt worden als soort adres voor relatie gevuld is." +" Indien aangevinkt, dan zullen voor alle connecties van dit type de linker partner als adres verschijnen bij de rechter partner." + diff --git a/partner_multi_relation_parent/i18n/partner_multi_relation_parent.pot b/partner_multi_relation_parent/i18n/partner_multi_relation_parent.pot new file mode 100644 index 000000000..be8656c92 --- /dev/null +++ b/partner_multi_relation_parent/i18n/partner_multi_relation_parent.pot @@ -0,0 +1,131 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_multi_relation_parent +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-11-01 09:32+0000\n" +"PO-Revision-Date: 2017-11-01 09:32+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_multi_relation_parent +#: model:ir.model,name:partner_multi_relation_parent.model_res_partner_relation_all +msgid "All (non-inverse + inverse) relations between partners" +msgstr "" + +#. module: partner_multi_relation_parent +#: selection:res.partner.relation.type,partner_type:0 +msgid "Contact" +msgstr "" + +#. module: partner_multi_relation_parent +#: code:addons/partner_multi_relation_parent/models/res_partner_relation_all.py:145 +#, python-format +msgid "Creating a relation for address with type %s is not allowed at self time." +msgstr "" + +#. module: partner_multi_relation_parent +#: code:addons/partner_multi_relation_parent/models/res_partner_relation_all.py:74 +#, python-format +msgid "Creating a relation for address with type %s is not allowed at this time." +msgstr "" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name_inverse:partner_multi_relation_parent.relation_type_parent_other +msgid "Has alternative address" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name_inverse:partner_multi_relation_parent.relation_type_parent_contact +msgid "Has contact" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name_inverse:partner_multi_relation_parent.relation_type_parent_delivery +msgid "Has delivery address" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name_inverse:partner_multi_relation_parent.relation_type_parent_invoice +msgid "Has invoice address" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:ir.model.fields,help:partner_multi_relation_parent.field_res_partner_relation_type_partner_type +msgid "If filled connections will be automatically created when partners of the specified type are linked to a parent.\n" +"Also the parent of the left contact will be updated when connections of this type are added or updated." +msgstr "" + +#. module: partner_multi_relation_parent +#: selection:res.partner.relation.type,partner_type:0 +msgid "Invoice address" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name:partner_multi_relation_parent.relation_type_parent_other +msgid "Is alternative address for" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name:partner_multi_relation_parent.relation_type_parent_contact +msgid "Is contact of" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name:partner_multi_relation_parent.relation_type_parent_delivery +msgid "Is delivery address for" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:res.partner.relation.type,name:partner_multi_relation_parent.relation_type_parent_invoice +msgid "Is invoice address for" +msgstr "" + +#. module: partner_multi_relation_parent +#: selection:res.partner.relation.type,partner_type:0 +msgid "Other address" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:ir.model,name:partner_multi_relation_parent.model_res_partner +msgid "Partner" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:ir.model.fields,field_description:partner_multi_relation_parent.field_res_partner_relation_type_partner_type +msgid "Partner Address Type" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:ir.model,name:partner_multi_relation_parent.model_res_partner_relation_type +msgid "Partner Relation Type" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:ir.model,name:partner_multi_relation_parent.model_res_partner_relation +msgid "Partner relation" +msgstr "" + +#. module: partner_multi_relation_parent +#: selection:res.partner.relation.type,partner_type:0 +msgid "Shipping address" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:ir.model.fields,field_description:partner_multi_relation_parent.field_res_partner_relation_type_partner_synchronization_active +msgid "Synchronize relations and addresses" +msgstr "" + +#. module: partner_multi_relation_parent +#: model:ir.model.fields,help:partner_multi_relation_parent.field_res_partner_relation_type_partner_synchronization_active +msgid "This field can only be true if Partner Address Type filled.\n" +" When enabled will make sure that for all these connections the left partner will have the right partner as parent." +msgstr "" + diff --git a/partner_multi_relation_parent/models/__init__.py b/partner_multi_relation_parent/models/__init__.py new file mode 100644 index 000000000..d04979419 --- /dev/null +++ b/partner_multi_relation_parent/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import res_partner_relation_type +from . import res_partner_relation_all +from . import res_partner_relation diff --git a/partner_multi_relation_parent/models/res_partner_relation.py b/partner_multi_relation_parent/models/res_partner_relation.py new file mode 100644 index 000000000..b36c293e2 --- /dev/null +++ b/partner_multi_relation_parent/models/res_partner_relation.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import _, api, models +from odoo.exceptions import ValidationError + + +class ResPartnerRelation(models.Model): + _inherit = 'res.partner.relation' + + @api.multi + def write(self, vals): + """Synchronize parent_id in left partner with connection. + + - If changed to non contact type, clear parent_id in partner; + - If changed to contact type, set parent_id and contact type + in partner. + """ + type_model = self.env['res.partner.relation.type'] + partner_model = self.env['res.partner'] + vals_type = 'type_id' in vals and \ + type_model.browse(vals['type_id']) or False + vals_left_partner = 'left_partner_id' in vals and \ + partner_model.browse(vals['left_partner_id']) or False + vals_right_partner = 'right_partner_id' in vals and \ + partner_model.browse(vals['right_partner_id']) or False + for this in self: + # Determine old and new type + old_type = this.type_id + new_type = vals_type or old_type + # First handle simple case: no address type involved + if not old_type.partner_type and not new_type.partner_type: + super(ResPartnerRelation, this).write(vals) + continue + # Store existing values + existing_left_partner = this.left_partner_id + existing_right_partner = this.right_partner_id + left_partner = vals_left_partner or existing_left_partner + right_partner = vals_right_partner or existing_right_partner + # Second relatively simple case is where non address + # connection is replaced by address connection + if not old_type.partner_type: + # Unlink existing connection + super(ResPartnerRelation, this).unlink() + # Create new connection + left_partner.write({ + 'type': new_type.partner_type, + 'parent_id': right_partner.id, + }) + continue + # Third handle case where address connection is changed into + # regular connection: + if not new_type.partner_type: + # Clear existing parent: + existing_left_partner.write({ + 'parent_id': False, + }) + # Now create new connection: + vals['left_partner_id'] = left_partner.id + vals['type_id'] = new_type.id + vals['right_partner_id'] = right_partner.id + super(ResPartnerRelation, this).create(vals) + continue + # If we get here, both old and new connection are for address: + # Check wether new type is already allowed: + if not new_type.partner_synchronization_active: + raise ValidationError( + _("Creating a relation for address with type %s is" + " not allowed at this time.") % + (new_type.partner_type, )) + # If left partner changed, clear parent on left partner: + if left_partner != existing_left_partner: + existing_left_partner.write({ + 'parent_id': False, + }) + left_partner.write({ + 'type': new_type.partner_type, + 'parent_id': right_partner.id, + }) + return True + + @api.multi + def unlink(self): + """Unlinking relations for address, means clearing parent_id.""" + for this in self: + if this.type_id.partner_type: + this.left_partner_id.write({ + 'parent_id': False, + }) + continue + super(ResPartnerRelation, this).unlink() + return True + + @api.model + def create(self, vals): + """Creating a relation for an address means updating parent.""" + type_model = self.env['res.partner.relation.type'] + partner_model = self.env['res.partner'] + relation_type = type_model.browse(vals['type_id']) + if relation_type.partner_type: + if not relation_type.partner_synchronization_active: + raise ValidationError( + _("Creating a relation for address with type %s is" + " not allowed at this time.") % + (relation_type.partner_type, )) + left_partner = partner_model.browse(vals['left_partner_id']) + left_partner.write({ + 'type': relation_type.partner_type, + 'parent_id': vals['right_partner_id'], + }) + # Return the left partner. + # Create in res_partner_relation_all will know what to do. + return left_partner + return super(ResPartnerRelation, self).create(vals) diff --git a/partner_multi_relation_parent/models/res_partner_relation_all.py b/partner_multi_relation_parent/models/res_partner_relation_all.py new file mode 100644 index 000000000..c219ccd5a --- /dev/null +++ b/partner_multi_relation_parent/models/res_partner_relation_all.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import _, api, models +from openerp.exceptions import ValidationError + +from openerp.addons.partner_multi_relation.models.res_partner_relation_all \ + import register_select_specification + + +# Register relation from address partner to parent partner: +register_select_specification( + base_name='partner_address', + is_inverse=False, + select_sql="""\ +SELECT + (partner.id * %%(padding)s) + %(key_offset)s as ID, + 'res.partner' AS res_model, + partner.id AS res_id, + partner.id AS left_partner_id, + partner.parent_id AS right_partner_id, + rprt.id AS type_id, + NULL AS date_start, + NULL AS date_end, + %(is_inverse)s AS is_inverse + FROM res_partner partner + JOIN res_partner_relation_type rprt ON partner.type = rprt.partner_type + WHERE NOT partner.parent_id IS NULL + AND rprt.partner_synchronization_active""") + +# Register relation from parent partner to address partner: +register_select_specification( + base_name='partner_address', + is_inverse=True, + select_sql="""\ +SELECT + (partner.id * %%(padding)s) + %(key_offset)s as ID, + 'res.partner' AS res_model, + partner.id AS res_id, + partner.parent_id AS left_partner_id, + partner.id AS right_partner_id, + rprt.id AS type_id, + NULL AS date_start, + NULL AS date_end, + %(is_inverse)s AS is_inverse + FROM res_partner partner + JOIN res_partner_relation_type rprt ON partner.type = rprt.partner_type + WHERE NOT partner.parent_id IS NULL + AND rprt.partner_synchronization_active""") + + +class ResPartnerRelationAll(models.AbstractModel): + """Show addresses as relations if so configured.""" + _inherit = 'res.partner.relation.all' + + def _get_active_selects(self): + """Return selects actually to be used. + + Selects are registered from all modules PRESENT. But should only be + used to build view if module actually INSTALLED. + """ + return super(ResPartnerRelationAll, self)._get_active_selects() +\ + ['partner_address', 'partner_address_inverse'] + + @api.model + def _compute_base_name(self, type_selection): + """This will be overridden for each inherit model.""" + if type_selection.type_id.partner_type: + return 'partner_address' + return super(ResPartnerRelationAll, self)._compute_base_name( + type_selection) + + @api.model + def create_resource(self, vals, type_selection): + if self._compute_base_name(type_selection) != 'partner_address': + return super(ResPartnerRelationAll, self).create_resource( + vals, type_selection) + partner_model = self.env['res.partner'] + relation_type = type_selection.type_id + if not relation_type.partner_synchronization_active: + raise ValidationError( + _("Creating a relation for address with type %s is" + " not allowed at this time.") % + (relation_type.partner_type, )) + left_partner = partner_model.browse(vals['left_partner_id']) + left_partner.write({ + 'type': relation_type.partner_type, + 'parent_id': vals['right_partner_id']}) + return left_partner + + @api.multi + def write_resource(self, base_resource, vals): + """Synchronize parent_id in left partner with connection. + + - If changed to non contact type, clear parent_id in partner; + - If changed to contact type, set parent_id and contact type + in partner. + """ + self.ensure_one() + # Determine old and new type + type_model = self.env['res.partner.relation.type'] + vals_type = 'type_id' in vals and \ + type_model.browse(vals['type_id']) or False + type_selection = self.type_selection_id + old_type = type_selection.type_id + new_type = vals_type or old_type + # If neither old, nor new type are for partner address, + # write can/should be handled by super method: + if not old_type.partner_type and not new_type.partner_type: + return super(ResPartnerRelationAll, self).write_resource( + base_resource, vals) + # We have to handle partner address: + partner_model = self.env['res.partner'] + vals_left_partner = 'left_partner_id' in vals and \ + partner_model.browse(vals['left_partner_id']) or False + vals_right_partner = 'right_partner_id' in vals and \ + partner_model.browse(vals['right_partner_id']) or False + # Store existing values + existing_left_partner = base_resource + existing_right_partner = base_resource.parent_id + left_partner = vals_left_partner or existing_left_partner + right_partner = vals_right_partner or existing_right_partner + # Relatively simple case is where non address + # connection is replaced by address connection + if not old_type.partner_type: + # Unlink existing connection + super(ResPartnerRelationAll, self).unlink() + # Create new connection + left_partner.write({ + 'type': new_type.partner_type, + 'parent_id': right_partner.id}) + return True + # Handle case where address connection is changed into + # regular connection: + if not new_type.partner_type: + # Clear existing parent: + existing_left_partner.write({'parent_id': False}) + # Now create new connection: + relation_model = self.env['res.partner.relation'] + vals['left_partner_id'] = left_partner.id + vals['type_id'] = new_type.id + vals['right_partner_id'] = right_partner.id + relation_model.create(vals) + return True + # If we get here, both old and new connection are for address: + # Check wether new type is already allowed: + if not new_type.partner_synchronization_active: + raise ValidationError( + _("Creating a relation for address with type %s is" + " not allowed at self time.") % + (new_type.partner_type, )) + # If left partner changed, clear parent on left partner: + existing_left_partner.write({'parent_id': False}) + left_partner.write({ + 'type': new_type.partner_type, + 'parent_id': right_partner.id}) + return True + + @api.multi + def unlink_resource(self, base_resource): + """Deleting an address connection is clearing parent_id.""" + self.ensure_one() + type_selection = self.type_selection_id + if self._compute_base_name(type_selection) != 'partner_address': + return super(ResPartnerRelationAll, self).unlink_resource( + base_resource) + base_resource.write({'parent_id': False}) diff --git a/partner_multi_relation_parent/models/res_partner_relation_type.py b/partner_multi_relation_parent/models/res_partner_relation_type.py new file mode 100644 index 000000000..0716b1be8 --- /dev/null +++ b/partner_multi_relation_parent/models/res_partner_relation_type.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +"""Define synchronization between relations and addresses.""" +from openerp import api, fields, models + + +class ResPartnerRelationType(models.Model): + """Model that defines relation types that might exist between partners""" + _inherit = 'res.partner.relation.type' + + partner_type = fields.Selection( + # TODO: determine automatically from selection in res.partner + selection=[ + ('contact', 'Contact'), + ('invoice', 'Invoice address'), + ('delivery', 'Shipping address'), + ('other', 'Other address'), + ], + string='Partner Address Type', + readonly=True, + help="If filled connections will be automatically created when" + " partners of the specified type are linked to a parent.\n" + "Also the parent of the left contact will be updated when" + " connections of this type are added or updated." + ) + partner_synchronization_active = fields.Boolean( + string="Synchronize relations and addresses", + default=False, + help="This field can only be true if Partner Address Type filled.\n" + " When enabled will make sure that for all these connections" + " the left partner will have the right partner as parent." + ) + + @api.multi + def write(self, vals): + """For address relation types, you can only change active flag.""" + for this in self: + if not this.partner_type: + super(ResPartnerRelationType, this).write(vals) + continue + if 'partner_synchronization_active' not in vals: + continue # Do nothing + super(ResPartnerRelationType, this).write({ + 'partner_synchronization_active': + vals['partner_synchronization_active'] + }) + return True diff --git a/partner_multi_relation_parent/static/description/icon.png b/partner_multi_relation_parent/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c6863a3e4852ea743449d6a17547b5ddc16c821c GIT binary patch literal 12960 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 ?Im#$t6v{tsveBQHiC6tSq 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 WM@fu$C{uy}0000 +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import test_partner_multi_relation_parent diff --git a/partner_multi_relation_parent/tests/test_partner_multi_relation_parent.py b/partner_multi_relation_parent/tests/test_partner_multi_relation_parent.py new file mode 100644 index 000000000..85c9aea8e --- /dev/null +++ b/partner_multi_relation_parent/tests/test_partner_multi_relation_parent.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo.addons.partner_multi_relation.tests.test_partner_relation_common \ + import TestPartnerRelationCommon + + +class TestPartnerMultiRelationParent(TestPartnerRelationCommon): + + # By default it will be false, this makes it run after the modules are + # installed + post_install = True + + def count_relations( + self, this_partner_id=False, type_id=False, + other_partner_id=False, any_partner_id=False): + """Count relations satifying criteria passed.""" + domain = [] + if this_partner_id: + domain.append(('this_partner_id', '=', this_partner_id)) + if type_id: + domain.append(('type_id', '=', type_id)) + if other_partner_id: + domain.append(('other_partner_id', '=', other_partner_id)) + if any_partner_id: + domain.append(('any_partner_id', '=', any_partner_id)) + if not domain: + return 0 + relation_all_model = self.env['res.partner.relation.all'] + return relation_all_model.search_count(domain) + + def test_address_changes(self): + """Test wether changes in address are reflected in relations.""" + # make sure that existing contact relation type enabled for sync: + contact_type = self.env.ref( + 'partner_multi_relation_parent.relation_type_parent_contact' + ) + contact_type.write({'partner_synchronization_active': True}) + # create a contact for ngo , partner n03 + ngo_contact = self.partner_model.create({ + 'name': '03 NGO ACCOUNTANT', + 'is_company': False, + 'ref': 'PR03C01', + 'type': 'contact', + 'parent_id': self.partner_03_ngo.id + }) + self.assertEqual( + self.count_relations( + this_partner_id=ngo_contact.id, + type_id=contact_type.id, + other_partner_id=ngo_contact.parent_id.id + ), 1) + # then modify partner and verify it + old_parent_id = ngo_contact.parent_id.id + ngo_contact.write( + {'parent_id': self.partner_02_company.id} + ) + # check no more relations with old_parent + self.assertEqual( + self.count_relations( + this_partner_id=ngo_contact.id, + type_id=contact_type.id, + other_partner_id=old_parent_id + ), 0) + # check relations are there with current parent + self.assertEqual( + self.count_relations( + this_partner_id=ngo_contact.id, + type_id=contact_type.id, + other_partner_id=ngo_contact.parent_id.id + ), 1) + # delete NGO ACCOUNTANT + old_id = ngo_contact.id + old_parent_id = ngo_contact.parent_id.id + ngo_contact.unlink() + self.assertEqual( + self.count_relations( + this_partner_id=old_id, + type_id=contact_type.id, + other_partner_id=old_parent_id + ), 0) + + def test_relation_type_changes(self): + """Test wether changes in address are reflected in relations.""" + # make sure that existing delivery relation type NOT enabled for sync: + delivery_type = self.env.ref( + 'partner_multi_relation_parent.relation_type_parent_delivery' + ) + delivery_type.write({'partner_synchronization_active': False}) + # create a delivery address for ngo , partner n03 + ngo_delivery = self.partner_model.create({ + 'name': '03 NGO Delivery Address', + 'is_company': False, + 'ref': 'PR03D01', + 'type': 'delivery', + 'parent_id': self.partner_03_ngo.id + }) + self.assertEqual( + self.count_relations( + this_partner_id=ngo_delivery.id, + type_id=delivery_type.id, + other_partner_id=ngo_delivery.parent_id.id + ), 0) + # after enabling delivery relation type, relations should be there: + delivery_type.write({'partner_synchronization_active': True}) + self.assertEqual( + self.count_relations( + this_partner_id=ngo_delivery.id, + type_id=delivery_type.id, + other_partner_id=ngo_delivery.parent_id.id + ), 1) + + def test_relation_changes(self): + """Test wether changes in relation are reflected in address. + + NB: left partner is the address partner, right partner the parent. + """ + # make sure that existing contact relation type enabled for sync: + contact_type = self.env.ref( + 'partner_multi_relation_parent.relation_type_parent_contact') + contact_type.write({'partner_synchronization_active': True}) + # create a contact address, to be linked to ngo partner: + ngo_contact = self.partner_model.create({ + 'name': '03 NGO ACCOUNTANT', + 'is_company': False, + 'ref': 'PR03C01', + }) + # Now create a contact address relation: + ngo_relation = self.relation_all_model.create({ + 'this_partner_id': ngo_contact.id, + 'type_id': contact_type.id, + 'other_partner_id': self.partner_03_ngo.id}) + # Now the contact address should be linked to parent as contact: + self.assertEqual(ngo_contact.parent_id.name, self.partner_03_ngo.name) + self.assertEqual(ngo_contact.type, 'contact') + # Now link the contact address to another company: + old_parent_id = ngo_contact.parent_id.id + ngo_relation.write({'other_partner_id': self.partner_02_company.id}) + # check no more relations with old_parent + self.assertEqual( + self.count_relations( + this_partner_id=ngo_contact.id, + other_partner_id=old_parent_id), 0) + # check relations are there with current parent + self.assertEqual( + self.count_relations( + this_partner_id=ngo_contact.id, + other_partner_id=ngo_contact.parent_id.id), 1) + # Remove the relation altoghether: + old_parent_id = ngo_contact.parent_id.id + ngo_relation.unlink() + self.assertEqual( + self.count_relations( + this_partner_id=ngo_contact.id, + other_partner_id=old_parent_id), 0) + self.assertEqual(ngo_contact.parent_id.id, False) diff --git a/partner_multi_relation_parent/views/res_partner_relation_type.xml b/partner_multi_relation_parent/views/res_partner_relation_type.xml new file mode 100644 index 000000000..3770f066d --- /dev/null +++ b/partner_multi_relation_parent/views/res_partner_relation_type.xml @@ -0,0 +1,18 @@ + + + + + res.partner.relation.type + + + + + + + + + +