From 81a6a728ed4005f56f37c1ed0088d6566b86042d Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Fri, 6 Oct 2017 17:18:32 +0200 Subject: [PATCH] Add module --- partner_multi_relation_parent/README.rst | 61 +++++++++ partner_multi_relation_parent/__init__.py | 5 + partner_multi_relation_parent/__manifest__.py | 17 +++ partner_multi_relation_parent/data/data.xml | 13 ++ partner_multi_relation_parent/hooks.py | 14 +++ .../models/__init__.py | 5 + .../models/res_partner.py | 60 +++++++++ .../models/res_partner_relation.py | 116 ++++++++++++++++++ .../static/description/icon.png | Bin 0 -> 12960 bytes .../tests/__init__.py | 4 + .../test_partner_multi_relation_parent.py | 108 ++++++++++++++++ 11 files changed, 403 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/hooks.py create mode 100644 partner_multi_relation_parent/models/__init__.py create mode 100644 partner_multi_relation_parent/models/res_partner.py create mode 100644 partner_multi_relation_parent/models/res_partner_relation.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 diff --git a/partner_multi_relation_parent/README.rst b/partner_multi_relation_parent/README.rst new file mode 100644 index 000000000..dd4331d4d --- /dev/null +++ b/partner_multi_relation_parent/README.rst @@ -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 +`_. 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 + +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..5b38452f5 --- /dev/null +++ b/partner_multi_relation_parent/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import models +from .hooks import post_init_hook diff --git a/partner_multi_relation_parent/__manifest__.py b/partner_multi_relation_parent/__manifest__.py new file mode 100644 index 000000000..cc1cab79c --- /dev/null +++ b/partner_multi_relation_parent/__manifest__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# 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, +} diff --git a/partner_multi_relation_parent/data/data.xml b/partner_multi_relation_parent/data/data.xml new file mode 100644 index 000000000..d57b0b8a2 --- /dev/null +++ b/partner_multi_relation_parent/data/data.xml @@ -0,0 +1,13 @@ + + + + + Is contact of + Has contact + p + c + restrict + + + diff --git a/partner_multi_relation_parent/hooks.py b/partner_multi_relation_parent/hooks.py new file mode 100644 index 000000000..60e3188f6 --- /dev/null +++ b/partner_multi_relation_parent/hooks.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# 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() + diff --git a/partner_multi_relation_parent/models/__init__.py b/partner_multi_relation_parent/models/__init__.py new file mode 100644 index 000000000..7f86ca245 --- /dev/null +++ b/partner_multi_relation_parent/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import res_partner +from . import res_partner_relation diff --git a/partner_multi_relation_parent/models/res_partner.py b/partner_multi_relation_parent/models/res_partner.py new file mode 100644 index 000000000..84bba2f0f --- /dev/null +++ b/partner_multi_relation_parent/models/res_partner.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# 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 + + 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..f62bc0dcc --- /dev/null +++ b/partner_multi_relation_parent/models/res_partner_relation.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# © 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 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 + + 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 zcmV;RGGEP!P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{03ZNKL_t(|+Le80bR^ex=2us!lq^}!YkU8A^~2|Q_s9OTz3cUQgVsK_b}iXf zuta5vdB_=t$vMve0}OI%G&)sRSJ)r(J>s%8oEhUB92B}=y?Qr&_rCk?d!+-|51;>;f>&Ha-Bu?BlBFdmDg703QF3u%8rwW&n)< z4zSNG+ie3d$M)51li~lv0G7n~_5w%(Xl0+f0r&wdh;hmOR38xleWYUzz;*!hVw^qz z4+9tkaIJz*0RW!_5US`G?C%AG7q|nH>qtyB0K4M5WdP3t_=X_R1F!<1h6EY_P6N;Z zSmNIE0JZ?Q3807fZvgOviZR?2*E;V0Bga<)aJGWbX#gX(A;$Xu7Mk@x~4&clD zPD*=7XvGkKU#AtRpOK1ww%z6bYCcE(RF2_Z|L^7Vb^++5(8CpNb^+)mLp4Enl-EI8 zT?D|Miu>aLcJRIfil;iI=27ZEr z0c<4$BdeUDreut^il3`U9Lfvr} zMsKTtuKGJLT_bH@q|kkL=obJUBtF+vD2+kw-%m@bA>_#C_E*9M6>S$PnArm0Tm_7n z?BpZ>{MJ6?p!pu#(XGd3Gw#NcjU$8zmjtGX@9i{48Euqlg39!l2|?EgP0avi+16q| zbL@AN@X*M<+o_=~^d){Wpq|fEKeA7du5k)yCx9uA1MH&~z&m_)7a5!pG)@q!hX5QD z{bq>Y&j9$DpxGvE-Aa4w0UQ%IdJL^T1>lzeysteS?`lfb{qfGhhA-DtM_+tkAZ(7b zk3h4wHpW*Kn-fI9j zMWxt5m`iay)$|#lbzLUIW+|95+ZCwsC)jq0A>Ol$GV+2q{rs$%VXCs2ZB)os@j1xo zy*f=)uZW}LB=qM1_CB>mU+(X${iF6&&7X9&)V9}G2a>Mm?Q|U5`u#`zrp{~xMg;2L3VPWX$Eh@;y$_A);|AuQn(8Q4NW zU!)Q>QZP5D2wSNnmqoa`1Hcd2e;t{>8^96LVQ}0Fgp|V^lP)aQPbfL4)7&q!lRTL{ z4B)*FV8rM9#Lj`_A9uIbr5fuJ@cA^9t%BZA9o*8|+WgragALDrW>2DFU!U*B%V%O5KN6*oZc)>h$h>E0^;zDNBTb(N@Drh=Intr5>JR{1XE*|O+)Ib>O9>;r zjYh0H75+k9b-3E60mWhweiSh<&>wDW?W?J(u1^>~Z&M=Tw&Zg8^QUf>Q#6^299Ro6 zJ4Oa4Ip|4-WHW*}a}-7|2i!oy8wn#Ov2TX=rf7j}WT1kM5NQIf_9}&t=l2cN-Ufz$ zc>vRmaimh!+A~jX_0R9=akD2TKJYDb>}QJiX%x(R9INyND#CeJ@mIe?csEU=)RZ-4 zYi`Y8N3ccnu(@0^Q7q=r)Q~``wHLu;4}5_tc-E>0+e|s-!op9E=f^lJR2bDyfuXQP zyZYO!|MZi4njcKYqo3D&fqot3*@-2`qfeNiBF-^%J4{6?Q3wkn(mG5*%mDBV!Q>ux zFh!>90&pc1LdU)*>jzfC`d+J;L3J^JsP3YzH3`oy!nJbn+%mkf2~G2ip^o5c%)pVX zjZs2EHwWV5U={PjG+Bd_h98NLtSF|&Dq=AzLc9pWz=8;+iUMj5@p=ojZIp0R5d9

Pr7y7N;{i)A2)>eg0)5Of8(Z84}rT*RH;Xi4v3AQ-a+e ze=p!dS?jM+*lL3GE~A?x2E(IS4)g{aEYOs``q1NvcdM&|f9wwz+wNw46isU_UDPjRZ4@Z!r}*G8YEp!RIsiUPXLIcila4SodqBjz8;OZA z{%t4bx@^-g5j;0XrcQs#akIM5aAAajoUaFy=0m7L0a14zj+w@MA%I^_cHtWnkDr^LUtF^D3o-V6 zgAbgi#3KR%rb%p^GqetxUWts*89B%(B*X7-kclVxeSp_ccwc*_Z@!j!gFeUv^|3H5v)z+VkRMpiQK5tWhvlbm{^WOgZH}hvW*T@he4w2DY z*r&_!4FM?W^o;LNJ0griwlJ{IkL-vK8HVps$2B$2b5XQvu&d{!l7JW;n103fdL!7{@C<0Q=zNp_H*cXk2ssX>2DQ`u=|1lJtAXe-o z^qNhm@7|7IUJYZp;Dg`LBZjW)TJyfwvNzt$vj1KHZJc_fG-e4P5Y+mPloCPpUzpu>RiD_ z(X!Cm7)P=?0iQnt&oN;%U)FC6t|GWw{@^UAO!Ct_3w zKrX#v6>Pg~8-~78%(%^Y0mu;9O3*)nEuu3Lio6t3w^Vg1?G8Zn@ z5u<|CYL!h>`P_@tws|sX2O*@5Gsx={z$rq*MFDTKyyqmXLQQmnY&$Aq>^^GtB=NhR z;aLtq&r{DPbIr}sKMTgKzLLL)701MiTS7AIM@!5PSgUaI3-HV|vIR5qcdsm7JbJzO zHgUg1rp6e9Z=q7f*?(Cv+9#aI7HY!{npaQBMC1Id^W8$B`0o5d;f;(lm<>kjh608W z_5j$nh4fkhrdfgyHgva$Vj*{A_VkZSPHF8RwQ!b}(m=-;pcdQAti^e4i)nC<|ED@@ z5v-4EPj}V(Qu&fsU$W6m!Ujc-;s*OoQXokHZ_<2s6Dsm_T5l8cUj*>-){*#gk*M~< z{rRD8yuPyM*z!vDl2ce(;#k+{f0h|4dW6OyTB)WJC!>^1t-!oA%`5_7Wi5k+Od2b> zl|^5;u6Nw$S?z}Al-$x`N-6Yb3!=Ce%`Z>_#fu) z47K_M1@mcIQh`=;6u@A6S7>`Utna&DKjcPR+dVzFvU>B_S@LI;_}^BMo3!;0tK?28tMGb0sp0~8)|-ITW{l6dRl8+s=`K_ z?$d^20jK=$KOVSjmbK2m`)YLY9}ks6-fd=vNJ9r1Tth3m#OLOOC0I25Y1efgsjH84 zMx&wl{rDln=f%UZWOni9xrk$CG8AN)3ZoV=>N$=efJ3^-A1j9U(;03PYhR0GMiRi3 zwKknAEMB$B`Q_PiZs}rab!yH`-+H%{zSZE`<~QM%Rn~4Bnbtt8RPpmG#O`gJWOQ&~ zhiF}60Gg|#Msjmk;y?Db*VU>Vi)H1F=GxFeOKa0}4-Gedrf3KD=_prUnJHz<4&rp| zt#q=6NLF<-0$AjAkfuK5dd_RQZm(D+&0i?!X3pDUxq%H;ck@GDxnSf>@1M+ti)$iV zm#07IWO#R;5YnTI^{}&)#591XsR1`dt~NynrIJBC%vUT^lRm@Qoy$JH!aTqn8N7qv z9imrW;NL1@W`l@BlK>jS0WG|}H+G;p8fuiW?ciX4DAm@VOe7ns;t^l><~Gg0p}zd{ zm(Q361=h#sw$dV9YMP2<&eG9717N9S;@ZrVeR}GaY0S+QUU#Z?oeoB7wi!OH>ZANn z$utj}ZseP}iFY(&vPs3!Xq}hEthQBW6rfhH)lY)>HA0Q(6`{&37bI=stw#+xN4%dT zvyO8%H%*O=FuGWw#5V$Xnc6l*!Sw;SKzu#q>27;}d$8?Zv90cw>gSfTxsB=kKO9~` zErqEvbtV~ph#EdjNK>(TnS$Nq*!Zzo#C ?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..3e24571cd --- /dev/null +++ b/partner_multi_relation_parent/tests/test_partner_multi_relation_parent.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# 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 +