Merge lp:~akretion-team/partner-contact-management/add-partner_relation into lp:~partner-contact-core-editors/partner-contact-management/7.0

Proposed by Alexis de Lattre on 2014-05-22
Status: Needs review
Proposed branch: lp:~akretion-team/partner-contact-management/add-partner_relation
Merge into: lp:~partner-contact-core-editors/partner-contact-management/7.0
Diff against target: 914 lines (+866/-0)
8 files modified
partner_relation/__init__.py (+23/-0)
partner_relation/__openerp__.py (+57/-0)
partner_relation/i18n/fr.po (+171/-0)
partner_relation/i18n/partner_relation.pot (+171/-0)
partner_relation/partner_relation.py (+217/-0)
partner_relation/partner_relation_demo.xml (+104/-0)
partner_relation/partner_relation_view.xml (+118/-0)
partner_relation/security/ir.model.access.csv (+5/-0)
To merge this branch: bzr merge lp:~akretion-team/partner-contact-management/add-partner_relation
Reviewer Review Type Date Requested Status
Lorenzo Battistini - Agile BG (community) Resubmit on 2014-07-23
Frère Bernard (community) functionnal tests Approve on 2014-06-10
Partner and Contact Core Editors 2014-05-22 Pending
Review via email: mp+220726@code.launchpad.net

Description of the change

This merge proposal adds a new module partner_relation. Here is the description :

===========
This module adds relations between partners. The type of relation is configurable ; it supports symetric and asymetric relations.

For example, you will be able to define on the form view of partner A that :

* Partner A is a competitor of Partner B (symetric relation : B is a competitor of A),

* Partner A has been recommended by Partner C (asymetric relation : C recommands A),

* Partner A is the editor of Partner D (asymetric relation : D is the integrator of A).

The relations that you define on Partner A towards Partner B will automatically be visible on the form view of Partner B.
============

Technically, it is not easy to implement in OpenERP. This implementation inherit the create() of the relation object to automatically generate a reverse relation (src_partner_id and dest_partner_id are swapped and the type of relation is the reverse if the relation is asymetric).

I have tried other implementations that would avoid to create a double entry for each relation, but all the other implementations were not good enough because I wanted relations to be simple and easy-to-use inside the partner form view.

This module is flake8 compliant, has demo data, ACLs, POT file and even an icon ! :)

To post a comment you must log in.
Holger Brunn (Therp) (hbrunn) wrote :

as we discussed before, here my take to the same problem: https://code.launchpad.net/~hbrunn/+junk/partner_relations

maybe we can throw that together

Stéphane Bidoul (Acsone) (sbi) wrote :

My colleague Olivier Laurent has also done something similar on an ongoing project (not yet public code but will be).

Maybe we can chat over this at the OpenDays

Alexis de Lattre (alexis-via) wrote :

No problem ; I'm in Louvain la neuve until Friday afternoon.

Alexis de Lattre (alexis-via) wrote :

@Holger

I had a quick look at your code. Let's try to look at it together tomorrow or Friday to see what's common, what's different, and see if we can enhance the partner_relation module with some ideas from your module.

Frère Bernard (brother-bernard) wrote :

Relation types : import OK.
Relations between partners : import OK.
Module is fully functionnal.

review: Approve (functionnal tests)
41. By Alexis de Lattre on 2014-06-11

Sort relation types by alphabetical order.

Holger Brunn (Therp) (hbrunn) wrote :

Sorry Alexis for my late reply! Meanwhile, we added quite a bit to our approach to partner relations, so I made an alternative MP: https://code.launchpad.net/~therp-nl/partner-contact-management/7.0_partner_relations/+merge/223734

The differences are: We use views to duplicate data instead of code, I personally like this approach more. Then we also enable restrictions on the operands of a relation, and some data (validity date, active) on the relation itself.

Further, I took some care that it is simple to search for partners using relations they have among each other.

This project is now hosted on https://github.com/OCA/partner-contact. Please move your proposal there. This guide may help you https://github.com/OCA/maintainers-tools/wiki/How-to-move-a-Merge-Proposal-to-GitHub

review: Resubmit

Unmerged revisions

41. By Alexis de Lattre on 2014-06-11

Sort relation types by alphabetical order.

40. By Alexis de Lattre on 2014-05-22

Initial check-in of the module partner_relation.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'partner_relation'
2=== added file 'partner_relation/__init__.py'
3--- partner_relation/__init__.py 1970-01-01 00:00:00 +0000
4+++ partner_relation/__init__.py 2014-06-11 07:18:53 +0000
5@@ -0,0 +1,23 @@
6+# -*- encoding: utf-8 -*-
7+##############################################################################
8+#
9+# Partner Relation module for OpenERP
10+# Copyright (C) 2014 Artisanat Monastique de Provence (www.barroux.org)
11+# @author: Alexis de Lattre <alexis.delattre@akretion.com>
12+#
13+# This program is free software: you can redistribute it and/or modify
14+# it under the terms of the GNU Affero General Public License as
15+# published by the Free Software Foundation, either version 3 of the
16+# License, or (at your option) any later version.
17+#
18+# This program is distributed in the hope that it will be useful,
19+# but WITHOUT ANY WARRANTY; without even the implied warranty of
20+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21+# GNU Affero General Public License for more details.
22+#
23+# You should have received a copy of the GNU Affero General Public License
24+# along with this program. If not, see <http://www.gnu.org/licenses/>.
25+#
26+##############################################################################
27+
28+from . import partner_relation
29
30=== added file 'partner_relation/__openerp__.py'
31--- partner_relation/__openerp__.py 1970-01-01 00:00:00 +0000
32+++ partner_relation/__openerp__.py 2014-06-11 07:18:53 +0000
33@@ -0,0 +1,57 @@
34+# -*- encoding: utf-8 -*-
35+##############################################################################
36+#
37+# Partner Relation module for OpenERP
38+# Copyright (C) 2014 Artisanat Monastique de Provence (www.barroux.org)
39+# @author: Alexis de Lattre <alexis.delattre@akretion.com>
40+#
41+# This program is free software: you can redistribute it and/or modify
42+# it under the terms of the GNU Affero General Public License as
43+# published by the Free Software Foundation, either version 3 of the
44+# License, or (at your option) any later version.
45+#
46+# This program is distributed in the hope that it will be useful,
47+# but WITHOUT ANY WARRANTY; without even the implied warranty of
48+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
49+# GNU Affero General Public License for more details.
50+#
51+# You should have received a copy of the GNU Affero General Public License
52+# along with this program. If not, see <http://www.gnu.org/licenses/>.
53+#
54+##############################################################################
55+
56+
57+{
58+ 'name': 'Partner Relation',
59+ 'version': '0.1',
60+ 'category': 'Partner',
61+ 'license': 'AGPL-3',
62+ 'summary': 'Adds relations between partners',
63+ 'description': """
64+Partner Relation
65+================
66+
67+This module adds relations between partners. The type of relation is configurable ; it supports symetric and asymetric relations.
68+
69+For example, you will be able to define on the form view of partner A that :
70+
71+* Partner A is a competitor of Partner B (symetric relation : B is a competitor of A),
72+
73+* Partner A has been recommended by Partner C (asymetric relation : C recommands A),
74+
75+* Partner A is the editor of Partner D (asymetric relation : D is the integrator of A).
76+
77+The relations that you define on Partner A towards Partner B will automatically be visible on the form view of Partner B.
78+ """,
79+ 'author': 'Barroux Abbey, Akretion',
80+ 'website': 'http://www.barroux.org',
81+ 'depends': ['base'],
82+ 'data': [
83+ 'partner_relation_view.xml',
84+ 'security/ir.model.access.csv',
85+ ],
86+ 'demo': [
87+ 'partner_relation_demo.xml',
88+ ],
89+ 'active': False,
90+}
91
92=== added directory 'partner_relation/i18n'
93=== added file 'partner_relation/i18n/fr.po'
94--- partner_relation/i18n/fr.po 1970-01-01 00:00:00 +0000
95+++ partner_relation/i18n/fr.po 2014-06-11 07:18:53 +0000
96@@ -0,0 +1,171 @@
97+# Translation of OpenERP Server.
98+# This file contains the translation of the following modules:
99+# * partner_relation
100+#
101+msgid ""
102+msgstr ""
103+"Project-Id-Version: OpenERP Server 7.0\n"
104+"Report-Msgid-Bugs-To: \n"
105+"POT-Creation-Date: 2014-05-22 19:57+0000\n"
106+"PO-Revision-Date: 2014-05-22 19:57+0000\n"
107+"Last-Translator: <>\n"
108+"Language-Team: \n"
109+"MIME-Version: 1.0\n"
110+"Content-Type: text/plain; charset=UTF-8\n"
111+"Content-Transfer-Encoding: \n"
112+"Plural-Forms: \n"
113+
114+#. module: partner_relation
115+#: field:res.partner.relation.type,active:0
116+msgid "Active"
117+msgstr "Actif"
118+
119+#. module: partner_relation
120+#: view:res.partner:0
121+msgid "Contacts"
122+msgstr "Contacts"
123+
124+#. module: partner_relation
125+#: field:res.partner.relation,dest_partner_id:0
126+msgid "Destination Partner"
127+msgstr "Partenaire destination"
128+
129+#. module: partner_relation
130+#: code:addons/partner_relation/partner_relation.py:90
131+#: code:addons/partner_relation/partner_relation.py:167
132+#, python-format
133+msgid "Error:"
134+msgstr "Erreur :"
135+
136+#. module: partner_relation
137+#: view:res.partner:0
138+msgid "Go to Relation Partner"
139+msgstr "Aller au partenaire lié"
140+
141+#. module: partner_relation
142+#: view:res.partner.relation:0
143+msgid "Group By..."
144+msgstr "Grouper par..."
145+
146+#. module: partner_relation
147+#: help:res.partner.relation.type,reverse_id:0
148+msgid "If the relation type is asymetric, select the corresponding reverse relation type. For exemple, 'A recommends B' is an asymetric relation ; it's reverse relation is 'B is recommended by A'. If the relation type is symetric, leave the field empty. For example, 'A is a competitor of B' is a symetric relation because we also have 'B is the competitor of A'."
149+msgstr "Si le type de relation est asymetrique, sélectionnez le type de relation inverse correspondant. Par exemple, 'A recommande B' est une relation asymetrique ; sa relation inverse est 'B recommande A'. Si le type de relation est symétrique, laissez le champ vide. Par exemple, 'A est un concurrent de B' est une relation symétrique car on a également 'B est un concurrent de A'."
150+
151+#. module: partner_relation
152+#: code:addons/partner_relation/partner_relation.py:91
153+#, python-format
154+msgid "It is not possible to modify the reverse of a relation type. You should desactivate or delete this relation type and create a new one."
155+msgstr "Il n'est pas possible de modifier l'inverse d'un type de relation. Vous devriez désactiver ou supprimer ce type de relation et en créer un nouveau."
156+
157+#. module: partner_relation
158+#: model:ir.model,name:partner_relation.model_res_partner
159+#: view:res.partner:0
160+#: view:res.partner.relation:0
161+msgid "Partner"
162+msgstr "Partenaire"
163+
164+#. module: partner_relation
165+#: model:ir.model,name:partner_relation.model_res_partner_relation_type
166+msgid "Partner Relation Type"
167+msgstr "Type de relation partenaire"
168+
169+#. module: partner_relation
170+#: model:ir.actions.act_window,name:partner_relation.partner_relation_type_action
171+#: model:ir.ui.menu,name:partner_relation.partner_relation_type_menu
172+#: view:res.partner.relation.type:0
173+msgid "Partner Relation Types"
174+msgstr "Types de relation partenaire"
175+
176+#. module: partner_relation
177+#: model:ir.actions.act_window,name:partner_relation.partner_relation_action
178+#: model:ir.ui.menu,name:partner_relation.partner_relation_menu
179+#: view:res.partner:0
180+#: field:res.partner,relation_ids:0
181+#: view:res.partner.relation:0
182+msgid "Partner Relations"
183+msgstr "Relations partenaires"
184+
185+#. module: partner_relation
186+#: field:res.partner.relation.type,name:0
187+msgid "Relation Name"
188+msgstr "Nom de la relation"
189+
190+#. module: partner_relation
191+#: view:res.partner.relation:0
192+#: field:res.partner.relation,relation_type_id:0
193+msgid "Relation Type"
194+msgstr "Type de relation"
195+
196+#. module: partner_relation
197+#: model:ir.ui.menu,name:partner_relation.partner_relation_config_menu
198+#: view:res.partner:0
199+msgid "Relations"
200+msgstr "Relations"
201+
202+#. module: partner_relation
203+#: field:res.partner.relation.type,reverse_id:0
204+msgid "Reverse Relation Type"
205+msgstr "Type de relation inverse"
206+
207+#. module: partner_relation
208+#: view:res.partner.relation:0
209+msgid "Search Partner Relations"
210+msgstr "Recherche dans les relations partenaires"
211+
212+#. module: partner_relation
213+#: field:res.partner.relation,src_partner_id:0
214+msgid "Source Partner"
215+msgstr "Partenaire source"
216+
217+#. module: partner_relation
218+#: sql_constraint:res.partner.relation:0
219+msgid "This relation already exists!"
220+msgstr "Cette relation existe déjà !"
221+
222+#. module: partner_relation
223+#: code:addons/partner_relation/partner_relation.py:168
224+#, python-format
225+msgid "You cannot write the same values on the relation and it's reverse relation."
226+msgstr "Vous ne pouvez pas écrire les mêmes valeurs sur une relation et sa relation inverse."
227+
228+#. module: partner_relation
229+#: model:res.partner.relation.type,name:partner_relation.is_competitor_of
230+msgid "is a competitor of"
231+msgstr "est un concurrent de"
232+
233+#. module: partner_relation
234+#: model:res.partner.relation.type,name:partner_relation.is_customer_of
235+msgid "is a customer of"
236+msgstr "est un client de"
237+
238+#. module: partner_relation
239+#: model:res.partner.relation.type,name:partner_relation.is_supplier_of
240+msgid "is a supplier of"
241+msgstr "est un fournisseur de"
242+
243+#. module: partner_relation
244+#: model:res.partner.relation.type,name:partner_relation.is_integrator_of
245+msgid "is an integrator of"
246+msgstr "est un intégrateur de"
247+
248+#. module: partner_relation
249+#: model:res.partner.relation.type,name:partner_relation.is_recommended_by
250+msgid "is recommended by"
251+msgstr "est recommandé par"
252+
253+#. module: partner_relation
254+#: model:res.partner.relation.type,name:partner_relation.is_editor_of
255+msgid "is the editor of"
256+msgstr "est l'éditeur de"
257+
258+#. module: partner_relation
259+#: model:res.partner.relation.type,name:partner_relation.recommends
260+msgid "recommends"
261+msgstr "recommande"
262+
263+#. module: partner_relation
264+#: model:ir.model,name:partner_relation.model_res_partner_relation
265+msgid "res.partner.relation"
266+msgstr "res.partner.relation"
267+
268
269=== added file 'partner_relation/i18n/partner_relation.pot'
270--- partner_relation/i18n/partner_relation.pot 1970-01-01 00:00:00 +0000
271+++ partner_relation/i18n/partner_relation.pot 2014-06-11 07:18:53 +0000
272@@ -0,0 +1,171 @@
273+# Translation of OpenERP Server.
274+# This file contains the translation of the following modules:
275+# * partner_relation
276+#
277+msgid ""
278+msgstr ""
279+"Project-Id-Version: OpenERP Server 7.0\n"
280+"Report-Msgid-Bugs-To: \n"
281+"POT-Creation-Date: 2014-05-22 19:57+0000\n"
282+"PO-Revision-Date: 2014-05-22 19:57+0000\n"
283+"Last-Translator: <>\n"
284+"Language-Team: \n"
285+"MIME-Version: 1.0\n"
286+"Content-Type: text/plain; charset=UTF-8\n"
287+"Content-Transfer-Encoding: \n"
288+"Plural-Forms: \n"
289+
290+#. module: partner_relation
291+#: field:res.partner.relation.type,active:0
292+msgid "Active"
293+msgstr ""
294+
295+#. module: partner_relation
296+#: view:res.partner:0
297+msgid "Contacts"
298+msgstr ""
299+
300+#. module: partner_relation
301+#: field:res.partner.relation,dest_partner_id:0
302+msgid "Destination Partner"
303+msgstr ""
304+
305+#. module: partner_relation
306+#: code:addons/partner_relation/partner_relation.py:90
307+#: code:addons/partner_relation/partner_relation.py:167
308+#, python-format
309+msgid "Error:"
310+msgstr ""
311+
312+#. module: partner_relation
313+#: view:res.partner:0
314+msgid "Go to Relation Partner"
315+msgstr ""
316+
317+#. module: partner_relation
318+#: view:res.partner.relation:0
319+msgid "Group By..."
320+msgstr ""
321+
322+#. module: partner_relation
323+#: help:res.partner.relation.type,reverse_id:0
324+msgid "If the relation type is asymetric, select the corresponding reverse relation type. For exemple, 'A recommends B' is an asymetric relation ; it's reverse relation is 'B is recommended by A'. If the relation type is symetric, leave the field empty. For example, 'A is a competitor of B' is a symetric relation because we also have 'B is the competitor of A'."
325+msgstr ""
326+
327+#. module: partner_relation
328+#: code:addons/partner_relation/partner_relation.py:91
329+#, python-format
330+msgid "It is not possible to modify the reverse of a relation type. You should desactivate or delete this relation type and create a new one."
331+msgstr ""
332+
333+#. module: partner_relation
334+#: model:ir.model,name:partner_relation.model_res_partner
335+#: view:res.partner:0
336+#: view:res.partner.relation:0
337+msgid "Partner"
338+msgstr ""
339+
340+#. module: partner_relation
341+#: model:ir.model,name:partner_relation.model_res_partner_relation_type
342+msgid "Partner Relation Type"
343+msgstr ""
344+
345+#. module: partner_relation
346+#: model:ir.actions.act_window,name:partner_relation.partner_relation_type_action
347+#: model:ir.ui.menu,name:partner_relation.partner_relation_type_menu
348+#: view:res.partner.relation.type:0
349+msgid "Partner Relation Types"
350+msgstr ""
351+
352+#. module: partner_relation
353+#: model:ir.actions.act_window,name:partner_relation.partner_relation_action
354+#: model:ir.ui.menu,name:partner_relation.partner_relation_menu
355+#: view:res.partner:0
356+#: field:res.partner,relation_ids:0
357+#: view:res.partner.relation:0
358+msgid "Partner Relations"
359+msgstr ""
360+
361+#. module: partner_relation
362+#: field:res.partner.relation.type,name:0
363+msgid "Relation Name"
364+msgstr ""
365+
366+#. module: partner_relation
367+#: view:res.partner.relation:0
368+#: field:res.partner.relation,relation_type_id:0
369+msgid "Relation Type"
370+msgstr ""
371+
372+#. module: partner_relation
373+#: model:ir.ui.menu,name:partner_relation.partner_relation_config_menu
374+#: view:res.partner:0
375+msgid "Relations"
376+msgstr ""
377+
378+#. module: partner_relation
379+#: field:res.partner.relation.type,reverse_id:0
380+msgid "Reverse Relation Type"
381+msgstr ""
382+
383+#. module: partner_relation
384+#: view:res.partner.relation:0
385+msgid "Search Partner Relations"
386+msgstr ""
387+
388+#. module: partner_relation
389+#: field:res.partner.relation,src_partner_id:0
390+msgid "Source Partner"
391+msgstr ""
392+
393+#. module: partner_relation
394+#: sql_constraint:res.partner.relation:0
395+msgid "This relation already exists!"
396+msgstr ""
397+
398+#. module: partner_relation
399+#: code:addons/partner_relation/partner_relation.py:168
400+#, python-format
401+msgid "You cannot write the same values on the relation and it's reverse relation."
402+msgstr ""
403+
404+#. module: partner_relation
405+#: model:res.partner.relation.type,name:partner_relation.is_competitor_of
406+msgid "is a competitor of"
407+msgstr ""
408+
409+#. module: partner_relation
410+#: model:res.partner.relation.type,name:partner_relation.is_customer_of
411+msgid "is a customer of"
412+msgstr ""
413+
414+#. module: partner_relation
415+#: model:res.partner.relation.type,name:partner_relation.is_supplier_of
416+msgid "is a supplier of"
417+msgstr ""
418+
419+#. module: partner_relation
420+#: model:res.partner.relation.type,name:partner_relation.is_integrator_of
421+msgid "is an integrator of"
422+msgstr ""
423+
424+#. module: partner_relation
425+#: model:res.partner.relation.type,name:partner_relation.is_recommended_by
426+msgid "is recommended by"
427+msgstr ""
428+
429+#. module: partner_relation
430+#: model:res.partner.relation.type,name:partner_relation.is_editor_of
431+msgid "is the editor of"
432+msgstr ""
433+
434+#. module: partner_relation
435+#: model:res.partner.relation.type,name:partner_relation.recommends
436+msgid "recommends"
437+msgstr ""
438+
439+#. module: partner_relation
440+#: model:ir.model,name:partner_relation.model_res_partner_relation
441+msgid "res.partner.relation"
442+msgstr ""
443+
444
445=== added file 'partner_relation/partner_relation.py'
446--- partner_relation/partner_relation.py 1970-01-01 00:00:00 +0000
447+++ partner_relation/partner_relation.py 2014-06-11 07:18:53 +0000
448@@ -0,0 +1,217 @@
449+# -*- encoding: utf-8 -*-
450+##############################################################################
451+#
452+# Partner Relation module for OpenERP
453+# Copyright (C) 2014 Artisanat Monastique de Provence (www.barroux.org)
454+# @author: Alexis de Lattre <alexis.delattre@akretion.com>
455+#
456+# This program is free software: you can redistribute it and/or modify
457+# it under the terms of the GNU Affero General Public License as
458+# published by the Free Software Foundation, either version 3 of the
459+# License, or (at your option) any later version.
460+#
461+# This program is distributed in the hope that it will be useful,
462+# but WITHOUT ANY WARRANTY; without even the implied warranty of
463+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
464+# GNU Affero General Public License for more details.
465+#
466+# You should have received a copy of the GNU Affero General Public License
467+# along with this program. If not, see <http://www.gnu.org/licenses/>.
468+#
469+##############################################################################
470+
471+from openerp.osv import orm, fields
472+from openerp.tools.translate import _
473+
474+
475+class res_partner_relation_type(orm.Model):
476+ _name = 'res.partner.relation.type'
477+ _description = "Partner Relation Type"
478+ _order = 'name'
479+
480+ _columns = {
481+ 'name': fields.char(
482+ 'Relation Name', size=32, required=True, translate=True),
483+ 'reverse_id': fields.many2one(
484+ 'res.partner.relation.type', 'Reverse Relation Type',
485+ help="If the relation type is asymetric, select the corresponding "
486+ "reverse relation type. For exemple, 'A recommends B' is an "
487+ "asymetric relation ; it's reverse relation is 'B is recommended "
488+ "by A'. If the relation type is symetric, leave the field empty. "
489+ "For example, 'A is a competitor of B' is a symetric relation "
490+ "because we also have 'B is the competitor of A'."),
491+ 'active': fields.boolean('Active'),
492+ }
493+
494+ _defaults = {
495+ 'active': True,
496+ }
497+
498+ def copy(self, cr, uid, id, default=None, context=None):
499+ if default is None:
500+ default = {}
501+ current = self.browse(cr, uid, id, context=context)
502+ default.update({
503+ 'name': u'%s (copy)' % current.name,
504+ 'reverse_id': False,
505+ })
506+ return super(res_partner_relation_type, self).copy(
507+ cr, uid, id, default=default, context=context)
508+
509+ def _get_reverse_relation_type_id(
510+ self, cr, uid, relation_type_id, context=None):
511+ relation_type = self.browse(
512+ cr, uid, relation_type_id, context=context)
513+ if relation_type.reverse_id:
514+ reverse_relation_type_id = relation_type.reverse_id.id
515+ else:
516+ reverse_relation_type_id = relation_type_id
517+ return reverse_relation_type_id
518+
519+ def create(self, cr, uid, vals, context=None):
520+ if context is None:
521+ context = {}
522+ new_id = super(res_partner_relation_type, self).create(
523+ cr, uid, vals, context=context)
524+ if vals.get('reverse_id'):
525+ ctx_write = context.copy()
526+ ctx_write['allow_write_reverse_id'] = True
527+ self.write(
528+ cr, uid, vals['reverse_id'],
529+ {'reverse_id': new_id}, context=ctx_write)
530+ return new_id
531+
532+ def write(self, cr, uid, ids, vals, context=None):
533+ if context is None:
534+ context = {}
535+ if (
536+ 'reverse_id' in vals
537+ and not context.get('allow_write_reverse_id')):
538+ raise orm.except_orm(
539+ _('Error:'),
540+ _('It is not possible to modify the reverse of a relation '
541+ 'type. You should desactivate or delete this relation '
542+ 'type and create a new one.'))
543+ return super(res_partner_relation_type, self).write(
544+ cr, uid, ids, vals, context=context)
545+
546+
547+class res_partner_relation(orm.Model):
548+ _name = 'res.partner.relation'
549+ _description = 'Partner Relation'
550+
551+ _columns = {
552+ 'src_partner_id': fields.many2one(
553+ 'res.partner', 'Source Partner', required=True),
554+ 'relation_type_id': fields.many2one(
555+ 'res.partner.relation.type', 'Relation Type', required=True),
556+ 'dest_partner_id': fields.many2one(
557+ 'res.partner', 'Destination Partner', required=True),
558+ }
559+
560+ _sql_constraints = [(
561+ 'src_dest_partner_relation_uniq',
562+ 'unique(src_partner_id, dest_partner_id, relation_type_id)',
563+ 'This relation already exists!'
564+ )]
565+
566+ def create(self, cr, uid, vals, context=None):
567+ '''When a user creates a relation, OpenERP creates the reverse
568+ relation automatically'''
569+ reverse_rel_type_id = self.pool['res.partner.relation.type'].\
570+ _get_reverse_relation_type_id(
571+ cr, uid, vals['relation_type_id'], context=context)
572+ # Create reverse relation
573+ super(res_partner_relation, self).create(
574+ cr, uid, {
575+ 'relation_type_id': reverse_rel_type_id,
576+ 'src_partner_id': vals['dest_partner_id'],
577+ 'dest_partner_id': vals['src_partner_id'],
578+ }, context=context)
579+ return super(res_partner_relation, self).create(
580+ cr, uid, vals, context=context)
581+
582+ def _get_reverse_relation_id(self, cr, uid, relation, context=None):
583+ reverse_rel_type_id = self.pool['res.partner.relation.type'].\
584+ _get_reverse_relation_type_id(
585+ cr, uid, relation.relation_type_id.id, context=context)
586+ reverse_rel_ids = self.search(
587+ cr, uid, [
588+ ('src_partner_id', '=', relation.dest_partner_id.id),
589+ ('dest_partner_id', '=', relation.src_partner_id.id),
590+ ('relation_type_id', '=', reverse_rel_type_id)
591+ ], context=context)
592+ assert len(reverse_rel_ids) == 1, \
593+ 'A relation always has one reverse relation'
594+ return reverse_rel_ids[0]
595+
596+ def unlink(self, cr, uid, ids, context=None):
597+ '''When a user deletes a relation, OpenERP deletes the reverse
598+ relation automatically'''
599+ for relation in self.browse(cr, uid, ids, context=None):
600+ reverse_rel_id = self._get_reverse_relation_id(
601+ cr, uid, relation, context=context)
602+ if reverse_rel_id not in ids:
603+ ids.append(reverse_rel_id)
604+ return super(res_partner_relation, self).unlink(
605+ cr, uid, ids, context=context)
606+
607+ def write(self, cr, uid, ids, vals, context=None):
608+ '''When a user writes on a relation, we also have to update
609+ the reverse relation'''
610+ reverse_relation_ids = []
611+ for relation in self.browse(cr, uid, ids, context=None):
612+ reverse_rel_id = self._get_reverse_relation_id(
613+ cr, uid, relation, context=context)
614+ if reverse_rel_id in ids:
615+ raise orm.except_orm(
616+ _('Error:'),
617+ _("You cannot write the same values on the relation "
618+ "and it's reverse relation."))
619+ assert reverse_rel_id not in reverse_relation_ids, \
620+ "Impossible: it's relation has it's own reverse relation."
621+ reverse_relation_ids.append(reverse_rel_id)
622+ reverse_vals = {}
623+ if 'src_partner_id' in vals:
624+ reverse_vals['dest_partner_id'] = vals['src_partner_id']
625+ if 'dest_partner_id' in vals:
626+ reverse_vals['src_partner_id'] = vals['dest_partner_id']
627+ if 'relation_type_id' in vals:
628+ reverse_vals['relation_type_id'] = \
629+ self.pool['res.partner.relation.type'].\
630+ _get_reverse_relation_type_id(
631+ cr, uid, vals['relation_type_id'], context=context)
632+ super(res_partner_relation, self).write(
633+ cr, uid, reverse_relation_ids, reverse_vals, context=context)
634+ return super(res_partner_relation, self).write(
635+ cr, uid, ids, vals, context=context)
636+
637+ def go_to_dest_partner(self, cr, uid, ids, context=None):
638+ assert len(ids) == 1, 'Only 1 ID'
639+ relation = self.browse(cr, uid, ids[0], context=context)
640+ action = {
641+ 'name': self.pool['res.partner']._description,
642+ 'type': 'ir.actions.act_window',
643+ 'res_model': 'res.partner',
644+ 'view_type': 'form',
645+ 'view_mode': 'form,tree,kanban',
646+ 'target': 'current',
647+ 'res_id': relation.dest_partner_id.id,
648+ }
649+ return action
650+
651+
652+class res_partner(orm.Model):
653+ _inherit = 'res.partner'
654+
655+ _columns = {
656+ 'relation_ids': fields.one2many(
657+ 'res.partner.relation', 'src_partner_id', 'Partner Relations'),
658+ }
659+
660+ def copy(self, cr, uid, id, default=None, context=None):
661+ if default is None:
662+ default = {}
663+ default['relation_ids'] = False
664+ return super(res_partner, self).copy(
665+ cr, uid, id, default=default, context=context)
666
667=== added file 'partner_relation/partner_relation_demo.xml'
668--- partner_relation/partner_relation_demo.xml 1970-01-01 00:00:00 +0000
669+++ partner_relation/partner_relation_demo.xml 2014-06-11 07:18:53 +0000
670@@ -0,0 +1,104 @@
671+<?xml version="1.0" encoding="utf-8"?>
672+<!--
673+ Copyright (C) 2014 Artisanat Monastique de Provence (www.barroux.org)
674+ @author: Alexis de Lattre <alexis.delattre@akretion.com>
675+ The licence is in the file __openerp__.py
676+-->
677+
678+<openerp>
679+<data noupdate="1">
680+
681+<!-- RELATION TYPES -->
682+<record id="is_editor_of" model="res.partner.relation.type">
683+ <field name="name">is the editor of</field>
684+</record>
685+
686+<record id="is_integrator_of" model="res.partner.relation.type">
687+ <field name="name">is an integrator of</field>
688+ <field name="reverse_id" ref="is_editor_of"/>
689+</record>
690+
691+<record id="is_recommended_by" model="res.partner.relation.type">
692+ <field name="name">is recommended by</field>
693+</record>
694+
695+<record id="recommends" model="res.partner.relation.type">
696+ <field name="name">recommends</field>
697+ <field name="reverse_id" ref="is_recommended_by"/>
698+</record>
699+
700+<record id="is_competitor_of" model="res.partner.relation.type">
701+ <field name="name">is a competitor of</field>
702+ <!-- This is a symetric relation -->
703+</record>
704+
705+<record id="is_supplier_of" model="res.partner.relation.type">
706+ <field name="name">is a supplier of</field>
707+</record>
708+
709+<record id="is_customer_of" model="res.partner.relation.type">
710+ <field name="name">is a customer of</field>
711+ <field name="reverse_id" ref="is_supplier_of"/>
712+</record>
713+
714+
715+<!-- PARTNER RELATIONS -->
716+<!-- Elec Import is a customer of China Export -->
717+<record id="relation_6_3_customer" model="res.partner.relation">
718+ <field name="src_partner_id" ref="base.res_partner_6"/>
719+ <field name="relation_type_id" ref="is_customer_of"/>
720+ <field name="dest_partner_id" ref="base.res_partner_3"/>
721+</record>
722+
723+<!-- Delta PC is a customer of Asustek -->
724+<record id="relation_4_1_customer" model="res.partner.relation">
725+ <field name="src_partner_id" ref="base.res_partner_4"/>
726+ <field name="relation_type_id" ref="is_customer_of"/>
727+ <field name="dest_partner_id" ref="base.res_partner_1"/>
728+</record>
729+
730+<!-- Delta PC is a customer of Seagate -->
731+<record id="relation_4_19_customer" model="res.partner.relation">
732+ <field name="src_partner_id" ref="base.res_partner_4"/>
733+ <field name="relation_type_id" ref="is_customer_of"/>
734+ <field name="dest_partner_id" ref="base.res_partner_19"/>
735+</record>
736+
737+<!-- Maxtor is a competitor of Seagate -->
738+<record id="relation_20_19_competitor" model="res.partner.relation">
739+ <field name="src_partner_id" ref="base.res_partner_20"/>
740+ <field name="relation_type_id" ref="is_competitor_of"/>
741+ <field name="dest_partner_id" ref="base.res_partner_19"/>
742+</record>
743+
744+<!-- Medialpole recommends Agrolait -->
745+<record id="relation_8_2_recommends" model="res.partner.relation">
746+ <field name="src_partner_id" ref="base.res_partner_8"/>
747+ <field name="relation_type_id" ref="recommends"/>
748+ <field name="dest_partner_id" ref="base.res_partner_2"/>
749+</record>
750+
751+<!-- Agrolait is a customer of Bank Wealthy -->
752+<record id="relation_2_7_customer" model="res.partner.relation">
753+ <field name="src_partner_id" ref="base.res_partner_2"/>
754+ <field name="relation_type_id" ref="is_customer_of"/>
755+ <field name="dest_partner_id" ref="base.res_partner_7"/>
756+</record>
757+
758+<!-- Vicking Direct is a customer of Bank Wealthy -->
759+<record id="relation_22_7_customer" model="res.partner.relation">
760+ <field name="src_partner_id" ref="base.res_partner_22"/>
761+ <field name="relation_type_id" ref="is_customer_of"/>
762+ <field name="dest_partner_id" ref="base.res_partner_7"/>
763+</record>
764+
765+<!-- Camptocamp is a competitor of Axelor -->
766+<record id="relation_12_13_competitor" model="res.partner.relation">
767+ <field name="src_partner_id" ref="base.res_partner_12"/>
768+ <field name="relation_type_id" ref="is_competitor_of"/>
769+ <field name="dest_partner_id" ref="base.res_partner_13"/>
770+</record>
771+
772+
773+</data>
774+</openerp>
775
776=== added file 'partner_relation/partner_relation_view.xml'
777--- partner_relation/partner_relation_view.xml 1970-01-01 00:00:00 +0000
778+++ partner_relation/partner_relation_view.xml 2014-06-11 07:18:53 +0000
779@@ -0,0 +1,118 @@
780+<?xml version="1.0" encoding="utf-8"?>
781+<!--
782+ Copyright (C) 2014 Artisanat Monastique de Provence (www.barroux.org)
783+ @author: Alexis de Lattre <alexis.delattre@akretion.com>
784+ The licence is in the file __openerp__.py
785+-->
786+
787+<openerp>
788+<data>
789+
790+<!-- Partner Relation Type -->
791+<record id="partner_relation_type_form" model="ir.ui.view">
792+ <field name="name">partner_relation_type_form</field>
793+ <field name="model">res.partner.relation.type</field>
794+ <field name="arch" type="xml">
795+ <form string="Partner Relation Types" version="7.0">
796+ <group name="main">
797+ <field name="name"/>
798+ <field name="reverse_id"/>
799+ <field name="active"/>
800+ </group>
801+ </form>
802+ </field>
803+</record>
804+
805+<record id="partner_relation_type_tree" model="ir.ui.view">
806+ <field name="name">partner_relation_type_tree</field>
807+ <field name="model">res.partner.relation.type</field>
808+ <field name="arch" type="xml">
809+ <tree string="Partner Relation Types">
810+ <field name="name"/>
811+ <field name="reverse_id"/>
812+ </tree>
813+ </field>
814+</record>
815+
816+<record id="partner_relation_type_action" model="ir.actions.act_window">
817+ <field name="name">Partner Relation Types</field>
818+ <field name="res_model">res.partner.relation.type</field>
819+ <field name="view_mode">tree,form</field>
820+</record>
821+
822+<menuitem id="partner_relation_config_menu" name="Relations"
823+ parent="base.menu_config_address_book" sequence="90"/>
824+
825+<menuitem id="partner_relation_type_menu" action="partner_relation_type_action"
826+ parent="partner_relation_config_menu" sequence="20"/>
827+
828+
829+<!-- Partner Relation -->
830+<record id="partner_relation_tree" model="ir.ui.view">
831+ <field name="name">partner_relation_tree</field>
832+ <field name="model">res.partner.relation</field>
833+ <field name="arch" type="xml">
834+ <tree string="Partner Relations" editable="bottom">
835+ <field name="src_partner_id"/>
836+ <field name="relation_type_id" widget="selection"/>
837+ <field name="dest_partner_id"/>
838+ </tree>
839+ </field>
840+</record>
841+
842+<record id="partner_relation_search" model="ir.ui.view">
843+ <field name="name">partner_relation_search</field>
844+ <field name="model">res.partner.relation</field>
845+ <field name="arch" type="xml">
846+ <search string="Search Partner Relations">
847+ <field name="src_partner_id" string="Partner"
848+ filter_domain="['|', ('src_partner_id', 'ilike', self), ('dest_partner_id', 'ilike', self)]"/>
849+ <group string="Group By..." name="groupby">
850+ <filter name="relation_type_groupby" string="Relation Type"
851+ context="{'group_by': 'relation_type_id'}"/>
852+ </group>
853+ </search>
854+ </field>
855+</record>
856+
857+<record id="partner_relation_action" model="ir.actions.act_window">
858+ <field name="name">Partner Relations</field>
859+ <field name="res_model">res.partner.relation</field>
860+ <field name="view_mode">tree</field>
861+</record>
862+
863+<menuitem id="partner_relation_menu" action="partner_relation_action"
864+ parent="partner_relation_config_menu" sequence="10"/>
865+
866+
867+<!-- Partner -->
868+<record id="view_partner_form" model="ir.ui.view">
869+ <field name="name">add.relations.on.res.partner.form</field>
870+ <field name="model">res.partner</field>
871+ <field name="inherit_id" ref="base.view_partner_form"/>
872+ <field name="arch" type="xml">
873+ <page string="Contacts" position="after">
874+ <page name="relations" string="Relations">
875+ <group string="Partner Relations" name="relations">
876+ <field name="relation_ids" nolabel="1">
877+ <!-- I can't call the tree view of
878+ res.partner.relation because 'src_partner_id' is a
879+ required field and it blocks... and I really want
880+ this field to be required for data integrity reasons
881+ -->
882+ <tree editable="bottom">
883+ <field name="relation_type_id" widget="selection"/>
884+ <field name="dest_partner_id" string="Partner"/>
885+ <button name="go_to_dest_partner" type="object"
886+ icon="terp-gtk-jump-to-ltr"
887+ string="Go to Relation Partner"/>
888+ </tree>
889+ </field>
890+ </group>
891+ </page>
892+ </page>
893+ </field>
894+</record>
895+
896+</data>
897+</openerp>
898
899=== added directory 'partner_relation/security'
900=== added file 'partner_relation/security/ir.model.access.csv'
901--- partner_relation/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
902+++ partner_relation/security/ir.model.access.csv 2014-06-11 07:18:53 +0000
903@@ -0,0 +1,5 @@
904+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
905+access_partner_relation_type_full,Full access on res.partner.relation.type to Settings grp,model_res_partner_relation_type,base.group_system,1,1,1,1
906+access_partner_relation_type_read,Read access on res.partner.relation.type to everybody,model_res_partner_relation_type,,1,0,0,0
907+access_partner_relation_full,Full access on res.partner.relation to Contact Manager grp,model_res_partner_relation,base.group_partner_manager,1,1,1,1
908+access_partner_relation_read,Read access on res.partner.relation to Employees grp,model_res_partner_relation,base.group_user,1,0,0,0
909
910=== added directory 'partner_relation/static'
911=== added directory 'partner_relation/static/src'
912=== added directory 'partner_relation/static/src/img'
913=== added file 'partner_relation/static/src/img/icon.png'
914Binary files partner_relation/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and partner_relation/static/src/img/icon.png 2014-06-11 07:18:53 +0000 differ