11 changed files with 403 additions and 0 deletions
-
61partner_multi_relation_parent/README.rst
-
5partner_multi_relation_parent/__init__.py
-
17partner_multi_relation_parent/__manifest__.py
-
13partner_multi_relation_parent/data/data.xml
-
14partner_multi_relation_parent/hooks.py
-
5partner_multi_relation_parent/models/__init__.py
-
60partner_multi_relation_parent/models/res_partner.py
-
116partner_multi_relation_parent/models/res_partner_relation.py
-
BINpartner_multi_relation_parent/static/description/icon.png
-
4partner_multi_relation_parent/tests/__init__.py
-
108partner_multi_relation_parent/tests/test_partner_multi_relation_parent.py
@ -0,0 +1,61 @@ |
|||
.. 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 Hierarchy Mapping in relations |
|||
============================================= |
|||
|
|||
This module maps automatically the relations between parent partners and their |
|||
children as relations. It has an init hook that will create such relations for |
|||
existing partners and their children partner. If a child partner changes it's |
|||
parent the relation mapping will update automatically. It will automatically |
|||
create the relations "has contact" and "is contact of" for partners and their |
|||
contacts. This will allow to search using this key, and therefore have an |
|||
updated search option for partners and their contacts. |
|||
|
|||
|
|||
|
|||
Known issues / Roadmap |
|||
====================== |
|||
|
|||
* hide/forbade the delition of the "Is contact of" and "Has Contact" installed |
|||
relation types |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues |
|||
<https://github.com/OCA/partner_multi_relation/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 <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Giovanni Francesco Capalbo <giovanni@therp.nl> |
|||
|
|||
Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list <mailto:community@mail.odoo.com>`_ or the `appropriate specialized mailinglist <https://odoo-community.org/groups>`_ 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. |
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2017 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import models |
|||
from .hooks import post_init_hook |
@ -0,0 +1,17 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2017 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
{ |
|||
"name": "Partner Contact Hierarchy Mapping in relations", |
|||
"version": "10.0.1.0.0", |
|||
"author": "Therp BV,Odoo Community Association (OCA)", |
|||
"license": "AGPL-3", |
|||
"category": "CRM", |
|||
"summary": "Syncs the hierarchy partner's contacts with CRM relations", |
|||
"depends": ['partner_multi_relation'], |
|||
"data": [ |
|||
'data/data.xml', |
|||
], |
|||
"post_init_hook": "post_init_hook", |
|||
"installable": True, |
|||
} |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<odoo> |
|||
<data> |
|||
<record id="parent_relation_type" |
|||
model="res.partner.relation.type"> |
|||
<field name="name">Is contact of</field> |
|||
<field name="name_inverse">Has contact</field> |
|||
<field name="contact_type_left">p</field> |
|||
<field name="contact_type_right">c</field> |
|||
<field name="handle_invalid_onchange">restrict</field> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -0,0 +1,14 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2017 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from odoo import api, SUPERUSER_ID |
|||
|
|||
def post_init_hook(cr, registry): |
|||
env = api.Environment(cr, SUPERUSER_ID, {}) |
|||
partner_model = env['res.partner'] |
|||
# get all fields with a parent |
|||
partners = partner_model.search(['!', ('parent_id', 'in', [False])]) |
|||
import pudb |
|||
pudb.set_trace() |
|||
partners.update_relations() |
|||
|
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2017 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import res_partner |
|||
from . import res_partner_relation |
@ -0,0 +1,60 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2017 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from openerp import api, models |
|||
|
|||
|
|||
class ResPartner(models.Model): |
|||
_inherit = 'res.partner' |
|||
|
|||
# called without parameters from the init hook |
|||
def find_current_relation(self, left, type_relation, right): |
|||
par_rel_mod = self.env['res.partner.relation'] |
|||
return par_rel_mod.search([ |
|||
('left_partner_id', '=', left), |
|||
('type_id', '=', type_relation), |
|||
('right_partner_id', '=', right) |
|||
]) |
|||
|
|||
|
|||
def update_relations(self, old_parent_id=None, parent_id=None): |
|||
par_rel_mod = self.env['res.partner.relation'] |
|||
type_relation = self.env.ref( |
|||
'partner_multi_relation_parent.parent_relation_type' |
|||
).id |
|||
for this in self: |
|||
if not parent_id: |
|||
parent_id = this.parent_id.id |
|||
if not old_parent_id: |
|||
old_parent_id = this.parent_id.id |
|||
# unlink previous relation |
|||
if old_parent_id: |
|||
previous = self.find_current_relation( |
|||
this.id, type_relation, old_parent_id |
|||
) |
|||
previous.unlink() |
|||
# create new relations |
|||
par_rel_mod.create( |
|||
{'left_partner_id' : this.id, |
|||
'type_id': type_relation, |
|||
'right_partner_id': parent_id, |
|||
} |
|||
) |
|||
|
|||
@api.model |
|||
def create(self, vals): |
|||
res = super(ResPartner, self).create(vals=vals) |
|||
if "parent_id" in vals: |
|||
res.update_relations(None, vals['parent_id']) |
|||
return res |
|||
|
|||
@api.multi |
|||
def write(self, vals): |
|||
if self.env.context.get('relation_create'): |
|||
for this in self: |
|||
if "parent_id" in vals and vals(['parent_id']): |
|||
this.update_relations(self.parent_id.id, vals['parent_id']) |
|||
res = super(ResPartner, self).write(vals=vals) |
|||
return res |
|||
|
|||
|
@ -0,0 +1,116 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2017 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from openerp import _, api, models |
|||
from odoo.exceptions import UserError |
|||
|
|||
|
|||
class ResPartnerRelation(models.Model): |
|||
_inherit = 'res.partner.relation' |
|||
|
|||
def relation_exists(self, left, type_rel, right): |
|||
relation = self.search([ |
|||
('left_partner_id', '=', left), |
|||
('type_id', '=', type_rel), |
|||
('right_partner_id', '=', right) |
|||
]) |
|||
return relation |
|||
|
|||
@api.multi |
|||
def write(self, vals): |
|||
part_mod = self.env['res.partner'] |
|||
type_relation = self.env.ref( |
|||
'partner_multi_relation_parent.parent_relation_type' |
|||
).id |
|||
for this in self: |
|||
if this.type_id != type_relation: |
|||
continue |
|||
# check that whatever relation will come out of this write does |
|||
# not exist already , but check only if type_id doesn't change, if |
|||
# it does we don't care about uniqueness |
|||
if vals.get('type_id', this.type_id) == type_relation: |
|||
relation = this.relation_exists( |
|||
vals.get('left_partner_id', this.left_partner_id), |
|||
type_relation, |
|||
vals.get('right_partner_id', this.right_partner_id) |
|||
) |
|||
if relation: |
|||
raise UserError(_( |
|||
"The relation you are creating exists and has id %s" |
|||
"there can only be one relation of type %s" % ( |
|||
str(relation.id), relation.type_id.name |
|||
))) |
|||
if 'type_id' in vals and vals['type_id'] != type_relation: |
|||
this.left_partner_id.with_context( |
|||
relation_create=False |
|||
).write({'parent_id' : False}) |
|||
elif 'right_partner_id' in vals and 'left_partner_id' not in vals: |
|||
new_parent = vals.get('right_partner_id') |
|||
contact_id = this.left_partner_id |
|||
contact_id.with_context(relation_create=False).write( |
|||
{'parent_id' : new_parent} |
|||
) |
|||
elif 'left_partner_id' in vals and 'right_partner_id' not in vals: |
|||
old_contact_id = part_mod.browse(this.left_partner_id) |
|||
old_contact_id.with_context(relation_create=False).write( |
|||
{'parent_id': False} |
|||
) |
|||
contact_id = part_mod.browse(vals['left_partner_id']) |
|||
contact_id.with_context(relation_create=False).write( |
|||
{'parent_id': this.right_partner_id} |
|||
) |
|||
elif 'left_partner_id' in vals and 'right_partner_id' in vals: |
|||
old_contact_id = this.left_partner_id |
|||
old_contact_id.with_context(relation_create=False).write( |
|||
{'parent_id': False} |
|||
) |
|||
contact_id = part_mod.browse(vals['left_partner_id']) |
|||
contact_id.with_context(relation_create=False).write( |
|||
{'parent_id': vals['right_partner_id']}, |
|||
) |
|||
res = super(ResPartnerRelation, self).write(vals=vals) |
|||
return res |
|||
|
|||
@api.multi |
|||
def unlink(self): |
|||
type_relation = self.env.ref( |
|||
'partner_multi_relation_parent.parent_relation_type' |
|||
).id |
|||
for this in self: |
|||
if this.type_id.id != type_relation: |
|||
continue |
|||
this.left_partner_id.with_context(relation_create=False).write( |
|||
{'parent_id': False} |
|||
) |
|||
res = super(ResPartnerRelation, self).unlink() |
|||
return res |
|||
|
|||
|
|||
@api.model |
|||
def create(self, vals): |
|||
type_relation = self.env.ref( |
|||
'partner_multi_relation_parent.parent_relation_type' |
|||
).id |
|||
current = self.relation_exists( |
|||
vals['left_partner_id'], |
|||
type_relation, |
|||
vals['right_partner_id'] |
|||
) |
|||
if current: |
|||
# we are creating a relation but one already exists, raise an |
|||
# exception to warn the user relations of type_relation must be |
|||
# unique. |
|||
raise UserError(_( |
|||
"The relation you are creating exists and has id %s" |
|||
"there can only be one relation of type %s" % ( |
|||
str(current.id), current.type_id.name |
|||
))) |
|||
# there is no relation, so we can create it, but we must update |
|||
# the parent_id of the left contact of this new relation |
|||
res = super(ResPartnerRelation, self).create(vals=vals) |
|||
res.left_partner_id.with_context(relation_create=False).write( |
|||
{'parent_id': vals['right_partner_id']} |
|||
) |
|||
return res |
|||
|
|||
|
After Width: 90 | Height: 90 | Size: 13 KiB |
@ -0,0 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2017 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import test_partner_multi_relation_parent |
@ -0,0 +1,108 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2017 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from openerp.addons.partner_multi_relation.tests.test_partner_relation_common \ |
|||
import TestPartnerRelationCommon |
|||
|
|||
|
|||
|
|||
class TestPartnerMultiRelationParent(TestPartnerRelationCommon): |
|||
# pylint: disable=invalid-name |
|||
def setUp(self): |
|||
super(TestPartnerMultiRelationParent, self).setUp() |
|||
self.par_rel_mod = self.env['res.partner.relation'] |
|||
self.type_relation = self.env.ref( |
|||
'partner_multi_relation_parent.parent_relation_type' |
|||
).id |
|||
|
|||
# By default it will be false, this makes it run after the modules are |
|||
# installed |
|||
post_install = True |
|||
|
|||
def count_partner_relation_left_right(self, partner_id, parent_id=None): |
|||
# returns number relations , should be always 1 |
|||
# todo extend this with relations_all calculations (left, right) |
|||
if not parent_id: |
|||
return 0 |
|||
hits = self.par_rel_mod.search([ |
|||
('left_partner_id', '=', partner_id), |
|||
('type_id', '=', self.type_relation), |
|||
('right_partner_id', '=', parent_id) |
|||
]) |
|||
return len(hits) |
|||
|
|||
def count_relations_of_parent(self, parent_id): |
|||
hits = self.par_rel_mod.search([ |
|||
('type_id', '=', self.type_relation), |
|||
('right_partner_id', '=', parent_id) |
|||
]) |
|||
return len(hits) |
|||
|
|||
|
|||
def test_partner_multi_relation_parent(self): |
|||
#verify that existing partners do have relations |
|||
relations = self.count_partner_relation_left_right( |
|||
self.partner_01_person.id, self.partner_01_person.parent_id.id |
|||
) |
|||
self.assertEqual(relations, 0) |
|||
# create a contact for ngo , partner n03 |
|||
ngo_contact = self.partner_model.create({ |
|||
'name': '03 NGO ACCOUNTANT', |
|||
'is_company': False, |
|||
'ref' : 'PR03C01', |
|||
'parent_id': self.partner_03_ngo.id |
|||
}) |
|||
relations = self.count_partner_relation_left_right( |
|||
ngo_contact.id, ngo_contact.parent_id.id |
|||
) |
|||
self.assertEqual(relations, 1) |
|||
# then modify partner and verify it |
|||
old_parent = ngo_contact.parent_id.id |
|||
ngo_contact.write({'parent_id': self.partner_02_company.id}) |
|||
# check no more relations with old_parent |
|||
relations = self.count_partner_relation_left_right( |
|||
ngo_contact.id, old_parent |
|||
) |
|||
self.assertEqual(relations, 0) |
|||
# check relations are there with current parent |
|||
|
|||
relations = self.count_partner_relation_left_right( |
|||
ngo_contact.id, ngo_contact.parent_id.id |
|||
) |
|||
self.assertEqual(relations, 1) |
|||
# delete NGO ACCOUNTANT |
|||
old_id = ngo_contact.id |
|||
old_parent_id = ngo_contact.parent_id.id |
|||
ngo_contact.unlink() |
|||
|
|||
relations = self.count_partner_relation_left_right( |
|||
old_id, old_parent_id |
|||
) |
|||
self.assertEqual(relations, 0) |
|||
# test with multiple contacts |
|||
# 15 for ngo and 15 for company |
|||
for partners in range(30): |
|||
if partners % 2 == 0: |
|||
self.partner_model.create({ |
|||
'name': '03 NGO %s' % str(partners), |
|||
'is_company': False, |
|||
'ref' : 'PR03C%s' % str(partners), |
|||
'parent_id': self.partner_03_ngo.id |
|||
}) |
|||
continue |
|||
self.partner_model.create({ |
|||
'name': '02 Company %s' % str(partners), |
|||
'is_company': False, |
|||
'ref' : 'PR02C%s' % str(partners), |
|||
'parent_id': self.partner_02_company.id |
|||
}) |
|||
# try to delete the has contact type , forbade |
|||
contact_relations = self.count_relations_of_parent( |
|||
self.partner_02_company.id |
|||
) |
|||
self.assertEqual(contact_relations, 15) |
|||
|
|||
|
|||
#TODO Modify that changing the relation.all transient model will change |
|||
#the actual relation and it's partner |
|||
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue