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