← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~therp-nl/partner-contact-management/7.0_partner_relations into lp:partner-contact-management

 

Holger Brunn (Therp) has proposed merging lp:~therp-nl/partner-contact-management/7.0_partner_relations into lp:partner-contact-management.

Requested reviews:
  Partner and Contact Core Editors (partner-contact-core-editors)

For more details, see:
https://code.launchpad.net/~therp-nl/partner-contact-management/7.0_partner_relations/+merge/223734

Introdution
-----------

This addon aims to provide generic means to model relations between partners.

Examples would be 'is sibling of' or 'is friend of', but also 'has contract X
with' or 'is assistant of'. This way, you can enode your knowledge about your
partners directly in your partner list.

Usage
-----

Before being able to use relations, you'll have define some first. Do that in
Sales / Configuration / Address Book / Partner relations. Here, you need to
name both sides of the relation: To have an assistant-relation, you would name
one side 'is assistant of' and the other side 'has assistant'. This relation
only makes sense between people, so you would choose 'Person' for both partner
types. For the relation 'is a competitor of', both sides would be companies,
while the relation 'has worked for' should have persons on the left side and
companies on the right side. If you leave this field empty, the relation is
applicable to all types of partners.

If you use categories to further specify the type of partners, you could for
example enforce that the 'is member of' relation can only have companies with
label 'Organization' on the left side.

Now open a partner and choose relations as appropriate in the 'Relations' tab.

Searching partners with relations
---------------------------------

Searching for relations is integrated transparently into the partner search
form. To find all assistants in your database, fill in 'is assistant of' and
autocomplete will propose to search for partners having this relation. Now if
you want to find Anna's assistant, you fill in 'Anna' and one of the proposals
is to search for partners having a relation with Anna. This results in Anna's
assistant(s), as you searched for assistants before.

By default, only active, not expired relations are shown. If you need to find
partners that had some relation at a certain date, fill in that date in the
search box and one of the proposals is to search for relations valid at that
date.
-- 
https://code.launchpad.net/~therp-nl/partner-contact-management/7.0_partner_relations/+merge/223734
Your team Partner and Contact Core Editors is requested to review the proposed merge of lp:~therp-nl/partner-contact-management/7.0_partner_relations into lp:partner-contact-management.
=== added directory 'partner_relations'
=== added file 'partner_relations/__init__.py'
--- partner_relations/__init__.py	1970-01-01 00:00:00 +0000
+++ partner_relations/__init__.py	2014-06-19 13:13:46 +0000
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright (C) 2013 Therp BV (<http://therp.nl>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+import model

=== added file 'partner_relations/__openerp__.py'
--- partner_relations/__openerp__.py	1970-01-01 00:00:00 +0000
+++ partner_relations/__openerp__.py	2014-06-19 13:13:46 +0000
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright (C) 2013 Therp BV (<http://therp.nl>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+{
+    "name": "Partner relations",
+    "version": "1.1",
+    "author": "Therp BV",
+    "complexity": "normal",
+    "description": """
+Introdution
+-----------
+
+This addon aims to provide generic means to model relations between partners.
+
+Examples would be 'is sibling of' or 'is friend of', but also 'has contract X
+with' or 'is assistant of'. This way, you can enode your knowledge about your
+partners directly in your partner list.
+
+Usage
+-----
+
+Before being able to use relations, you'll have define some first. Do that in
+Sales / Configuration / Address Book / Partner relations. Here, you need to
+name both sides of the relation: To have an assistant-relation, you would name
+one side 'is assistant of' and the other side 'has assistant'. This relation
+only makes sense between people, so you would choose 'Person' for both partner
+types. For the relation 'is a competitor of', both sides would be companies,
+while the relation 'has worked for' should have persons on the left side and
+companies on the right side. If you leave this field empty, the relation is
+applicable to all types of partners.
+
+If you use categories to further specify the type of partners, you could for
+example enforce that the 'is member of' relation can only have companies with
+label 'Organization' on the left side.
+
+Now open a partner and choose relations as appropriate in the 'Relations' tab.
+
+Searching partners with relations
+---------------------------------
+
+Searching for relations is integrated transparently into the partner search
+form. To find all assistants in your database, fill in 'is assistant of' and
+autocomplete will propose to search for partners having this relation. Now if
+you want to find Anna's assistant, you fill in 'Anna' and one of the proposals
+is to search for partners having a relation with Anna. This results in Anna's
+assistant(s), as you searched for assistants before.
+
+By default, only active, not expired relations are shown. If you need to find
+partners that had some relation at a certain date, fill in that date in the
+search box and one of the proposals is to search for relations valid at that
+date.""",
+    "category": "Customer Relationship Management",
+    "depends": [
+        'web_m2x_options', 
+    ],
+    "data": [
+        'view/res_partner.xml',
+        'view/res_partner_relation.xml',
+        'view/res_partner_relation_type.xml',
+        'view/menu.xml',
+        'security/ir.model.access.csv',
+    ],
+    "js": [
+    ],
+    "css": [
+    ],
+    "qweb": [
+    ],
+    "auto_install": False,
+    "installable": True,
+    "external_dependencies": {
+        'python': [],
+    },
+}

=== added directory 'partner_relations/data'
=== added directory 'partner_relations/i18n'
=== added file 'partner_relations/i18n/nl.po'
--- partner_relations/i18n/nl.po	1970-01-01 00:00:00 +0000
+++ partner_relations/i18n/nl.po	2014-06-19 13:13:46 +0000
@@ -0,0 +1,254 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* partner_relations
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-06-19 13:02+0000\n"
+"PO-Revision-Date: 2014-06-19 13:02+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: partner_relations
+#: field:res.partner.relation,active:0
+#: field:res.partner.relation.all,active:0
+msgid "Active"
+msgstr "Actief"
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner_relation_all
+msgid "All (non-inverse + inverse) relations between partners"
+msgstr "Alle (non-inverse + inverse) koppelingen tussen relaties"
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner_relation_type_selection
+msgid "All relation types"
+msgstr "Alle relaties"
+
+#. module: partner_relations
+#: field:res.partner,relation_all_ids:0
+msgid "All relations with current partner"
+msgstr "Alle koppelingen met huidige relatie"
+
+#. module: partner_relations
+#: field:res.partner.relation.all,this_partner_id:0
+msgid "Current partner"
+msgstr "Huidige relatie"
+
+#. module: partner_relations
+#: field:res.partner.relation.type.selection,partner_category_this:0
+#: field:res.partner.relation.type.selection,search_partner_category_this:0
+msgid "Current record's category"
+msgstr "Categorie van huidige record"
+
+#. module: partner_relations
+#: field:res.partner.relation.type.selection,contact_type_this:0
+msgid "Current record's partner type"
+msgstr "Relatietype van huidige record"
+
+#. module: partner_relations
+#: field:res.partner.relation,date_end:0
+#: field:res.partner.relation.all,date_end:0
+msgid "Ending date"
+msgstr "Einddatum"
+
+#. module: partner_relations
+#: field:res.partner,search_relation_id:0
+msgid "Has relation of type"
+msgstr "Heeft relatie"
+
+#. module: partner_relations
+#: field:res.partner,search_relation_partner_id:0
+msgid "Has relation with"
+msgstr "Heeft relatie met"
+
+#. module: partner_relations
+#: field:res.partner.relation.type,name_inverse:0
+msgid "Inverse name"
+msgstr "Inverse naam"
+
+#. module: partner_relations
+#: selection:res.partner.relation.all,record_type:0
+#: selection:res.partner.relation.type.selection,record_type:0
+msgid "Inverse type"
+msgstr "Inverse type"
+
+#. module: partner_relations
+#: field:res.partner.relation,left_partner_id:0
+msgid "Left partner"
+msgstr "Linker relatie"
+
+#. module: partner_relations
+#: field:res.partner.relation.type,partner_category_left:0
+msgid "Left partner category"
+msgstr "Linker relatielabel"
+
+#. module: partner_relations
+#: field:res.partner.relation.type,contact_type_left:0
+msgid "Left partner type"
+msgstr "Linker relatietype"
+
+#. module: partner_relations
+#: view:res.partner.relation.type:0
+msgid "Left side of relation"
+msgstr "Linkerkant van de koppeling"
+
+#. module: partner_relations
+#: field:res.partner.relation.type,name:0
+#: field:res.partner.relation.type.selection,name:0
+msgid "Name"
+msgstr "Naam"
+
+#. module: partner_relations
+#: field:res.partner.relation.all,other_partner_id:0
+msgid "Other partner"
+msgstr "Andere relatie"
+
+#. module: partner_relations
+#: field:res.partner.relation.type.selection,partner_category_other:0
+msgid "Other record's category"
+msgstr "Categorie andere record"
+
+#. module: partner_relations
+#: field:res.partner.relation.type.selection,contact_type_other:0
+msgid "Other record's partner type"
+msgstr "Type andere record"
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner_relation_type
+#: model:ir.model,name:partner_relations.model_res_partner_relation_type_inverse
+msgid "Parter relation type"
+msgstr "Type relatiekoppeling"
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner
+#: field:res.partner.relation,partner_id_display:0
+msgid "Partner"
+msgstr "Relatie"
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner_category
+msgid "Partner Categories"
+msgstr "Relatie Categorieën"
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner_relation
+#: view:res.partner.relation:0
+#: view:res.partner.relation.type:0
+msgid "Partner relation"
+msgstr "Relatiekoppeling"
+
+#. module: partner_relations
+#: model:ir.actions.act_window,name:partner_relations.action_res_partner_relation_type
+#: model:ir.ui.menu,name:partner_relations.menu_res_partner_relation_type
+msgid "Partner relations"
+msgstr "Relatiekoppelingen"
+
+#. module: partner_relations
+#: constraint:res.partner.relation:0
+msgid "Partners cannot have a relation with themselves."
+msgstr "Relaties kunnen niet aan zichzelf gekoppeld worden."
+
+#. module: partner_relations
+#: field:res.partner.relation.all,record_type:0
+#: field:res.partner.relation.type.selection,record_type:0
+msgid "Record type"
+msgstr "Record type"
+
+#. module: partner_relations
+#: field:res.partner.relation.all,relation_id:0
+msgid "Relation"
+msgstr "Koppeling"
+
+#. module: partner_relations
+#: field:res.partner.relation,is_relation_expired:0
+msgid "Relation is expired"
+msgstr "Koppeling is afgelopen"
+
+#. module: partner_relations
+#: field:res.partner.relation,is_relation_future:0
+msgid "Relation is in the future"
+msgstr "Koppeling is in de toekomst"
+
+#. module: partner_relations
+#: field:res.partner.relation.all,type_id:0
+msgid "Relation type"
+msgstr "Koppelingstype"
+
+#. module: partner_relations
+#: field:res.partner,search_relation_date:0
+msgid "Relation valid"
+msgstr "Datum koppeling"
+
+#. module: partner_relations
+#: view:res.partner:0
+#: field:res.partner,relation_ids:0
+msgid "Relations"
+msgstr "Koppelingen"
+
+#. module: partner_relations
+#: field:res.partner.relation,right_partner_id:0
+msgid "Right partner"
+msgstr "Rechter relatie"
+
+#. module: partner_relations
+#: field:res.partner.relation.type,partner_category_right:0
+msgid "Right partner category"
+msgstr "Rechter relatielabel"
+
+#. module: partner_relations
+#: field:res.partner.relation.type,contact_type_right:0
+msgid "Right partner type"
+msgstr "Rechter relatietype"
+
+#. module: partner_relations
+#: view:res.partner.relation.type:0
+msgid "Right side of relation"
+msgstr "Rechterkant van de koppeling"
+
+#. module: partner_relations
+#: field:res.partner.relation,date_start:0
+#: field:res.partner.relation.all,date_start:0
+msgid "Starting date"
+msgstr "Begindatum"
+
+#. module: partner_relations
+#: constraint:res.partner.relation:0
+msgid "The left partner is not applicable for this relation type."
+msgstr "De linker relatie is niet geldig voor dit type koppeling."
+
+#. module: partner_relations
+#: constraint:res.partner.relation:0
+msgid "The right partner is not applicable for this relation type."
+msgstr "De rechter relatie is niet geldig voor dit type koppeling."
+
+#. module: partner_relations
+#: constraint:res.partner.relation:0
+msgid "The starting date cannot be after the ending date."
+msgstr "De begindatum mag niet na de einddatum liggen."
+
+#. module: partner_relations
+#: field:res.partner.relation,type_id:0
+#: field:res.partner.relation,type_selection_id:0
+#: selection:res.partner.relation.all,record_type:0
+#: selection:res.partner.relation.type.selection,record_type:0
+#: field:res.partner.relation.type.selection,type_id:0
+msgid "Type"
+msgstr "Type"
+
+#. module: partner_relations
+#: field:res.partner.category,only_for_organisation:0
+msgid "Valid for organisation only"
+msgstr "Alleen geldig voor organisaties"
+
+#. module: partner_relations
+#: field:res.partner.category,only_for_person:0
+msgid "Valid for person only"
+msgstr "Alleen geldig voor personen"
+

=== added file 'partner_relations/i18n/partner_relations.pot'
--- partner_relations/i18n/partner_relations.pot	1970-01-01 00:00:00 +0000
+++ partner_relations/i18n/partner_relations.pot	2014-06-19 13:13:46 +0000
@@ -0,0 +1,254 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* partner_relations
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-06-19 13:01+0000\n"
+"PO-Revision-Date: 2014-06-19 13:01+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: partner_relations
+#: field:res.partner.relation,active:0
+#: field:res.partner.relation.all,active:0
+msgid "Active"
+msgstr ""
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner_relation_all
+msgid "All (non-inverse + inverse) relations between partners"
+msgstr ""
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner_relation_type_selection
+msgid "All relation types"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner,relation_all_ids:0
+msgid "All relations with current partner"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.all,this_partner_id:0
+msgid "Current partner"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.type.selection,partner_category_this:0
+#: field:res.partner.relation.type.selection,search_partner_category_this:0
+msgid "Current record's category"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.type.selection,contact_type_this:0
+msgid "Current record's partner type"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation,date_end:0
+#: field:res.partner.relation.all,date_end:0
+msgid "Ending date"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner,search_relation_id:0
+msgid "Has relation of type"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner,search_relation_partner_id:0
+msgid "Has relation with"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.type,name_inverse:0
+msgid "Inverse name"
+msgstr ""
+
+#. module: partner_relations
+#: selection:res.partner.relation.all,record_type:0
+#: selection:res.partner.relation.type.selection,record_type:0
+msgid "Inverse type"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation,left_partner_id:0
+msgid "Left partner"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.type,partner_category_left:0
+msgid "Left partner category"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.type,contact_type_left:0
+msgid "Left partner type"
+msgstr ""
+
+#. module: partner_relations
+#: view:res.partner.relation.type:0
+msgid "Left side of relation"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.type,name:0
+#: field:res.partner.relation.type.selection,name:0
+msgid "Name"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.all,other_partner_id:0
+msgid "Other partner"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.type.selection,partner_category_other:0
+msgid "Other record's category"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.type.selection,contact_type_other:0
+msgid "Other record's partner type"
+msgstr ""
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner_relation_type
+#: model:ir.model,name:partner_relations.model_res_partner_relation_type_inverse
+msgid "Parter relation type"
+msgstr ""
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner
+#: field:res.partner.relation,partner_id_display:0
+msgid "Partner"
+msgstr ""
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner_category
+msgid "Partner Categories"
+msgstr ""
+
+#. module: partner_relations
+#: model:ir.model,name:partner_relations.model_res_partner_relation
+#: view:res.partner.relation:0
+#: view:res.partner.relation.type:0
+msgid "Partner relation"
+msgstr ""
+
+#. module: partner_relations
+#: model:ir.actions.act_window,name:partner_relations.action_res_partner_relation_type
+#: model:ir.ui.menu,name:partner_relations.menu_res_partner_relation_type
+msgid "Partner relations"
+msgstr ""
+
+#. module: partner_relations
+#: constraint:res.partner.relation:0
+msgid "Partners cannot have a relation with themselves."
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.all,record_type:0
+#: field:res.partner.relation.type.selection,record_type:0
+msgid "Record type"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.all,relation_id:0
+msgid "Relation"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation,is_relation_expired:0
+msgid "Relation is expired"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation,is_relation_future:0
+msgid "Relation is in the future"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.all,type_id:0
+msgid "Relation type"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner,search_relation_date:0
+msgid "Relation valid"
+msgstr ""
+
+#. module: partner_relations
+#: view:res.partner:0
+#: field:res.partner,relation_ids:0
+msgid "Relations"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation,right_partner_id:0
+msgid "Right partner"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.type,partner_category_right:0
+msgid "Right partner category"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation.type,contact_type_right:0
+msgid "Right partner type"
+msgstr ""
+
+#. module: partner_relations
+#: view:res.partner.relation.type:0
+msgid "Right side of relation"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation,date_start:0
+#: field:res.partner.relation.all,date_start:0
+msgid "Starting date"
+msgstr ""
+
+#. module: partner_relations
+#: constraint:res.partner.relation:0
+msgid "The left partner is not applicable for this relation type."
+msgstr ""
+
+#. module: partner_relations
+#: constraint:res.partner.relation:0
+msgid "The right partner is not applicable for this relation type."
+msgstr ""
+
+#. module: partner_relations
+#: constraint:res.partner.relation:0
+msgid "The starting date cannot be after the ending date."
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.relation,type_id:0
+#: field:res.partner.relation,type_selection_id:0
+#: selection:res.partner.relation.all,record_type:0
+#: selection:res.partner.relation.type.selection,record_type:0
+#: field:res.partner.relation.type.selection,type_id:0
+msgid "Type"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.category,only_for_organisation:0
+msgid "Valid for organisation only"
+msgstr ""
+
+#. module: partner_relations
+#: field:res.partner.category,only_for_person:0
+msgid "Valid for person only"
+msgstr ""
+

=== added directory 'partner_relations/model'
=== added file 'partner_relations/model/__init__.py'
--- partner_relations/model/__init__.py	1970-01-01 00:00:00 +0000
+++ partner_relations/model/__init__.py	2014-06-19 13:13:46 +0000
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright (C) 2013 Therp BV (<http://therp.nl>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+import res_partner
+import res_partner_relation
+import res_partner_relation_type
+import res_partner_relation_type_selection
+from . import res_partner_relation_all

=== added file 'partner_relations/model/res_partner.py'
--- partner_relations/model/res_partner.py	1970-01-01 00:00:00 +0000
+++ partner_relations/model/res_partner.py	2014-06-19 13:13:46 +0000
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+'''Extend res.partner model'''
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright (C) 2013 Therp BV (<http://therp.nl>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+import time
+from openerp.osv import orm
+from openerp.osv import fields
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
+
+
+class ResPartner(orm.Model):
+    _inherit = 'res.partner'
+
+    def _get_relation_ids(
+            self, cr, uid, ids, dummy_name, dummy_arg, context=None):
+        if context is None:
+            context = {}
+
+        #TODO: do a permission test on returned ids
+        cr.execute(
+            '''select id, left_partner_id, right_partner_id
+            from res_partner_relation
+            where left_partner_id in %s or right_partner_id in %s''' +
+            (' and active' if context.get('active_test', True) else ''),
+            (tuple(ids), tuple(ids))
+        )
+        result = dict([(i, []) for i in ids])
+        for row in cr.fetchall():
+            if row[1] in result:
+                result[row[1]].append(row[0])
+            if row[2] in result:
+                result[row[2]].append(row[0])
+        return result
+
+    def _set_relation_ids(
+            self, cr, uid, ids, dummy_name, field_value, dummy_arg,
+            context=None):
+        if context is None:
+            context = {}
+        relation_obj = self.pool.get('res.partner.relation')
+        context2 = self._update_context(context, ids)
+        for value in field_value:
+            if value[0] == 0:
+                relation_obj.create(cr, uid, value[2], context=context2)
+            if value[0] == 1:
+                relation_obj.write(
+                    cr, uid, value[1], value[2], context=context2)
+            if value[0] == 2:
+                relation_obj.unlink(cr, uid, value[1], context=context2)
+
+    def _search_relation_id(
+            self, cr, uid, dummy_obj, name, args, context=None):
+        result = []
+        for arg in args:
+            if isinstance(arg, tuple) and arg[0] == name:
+                if arg[1] != '=':
+                    continue
+
+                type_id, is_inverse = self\
+                    .pool['res.partner.relation.type.selection']\
+                    .get_type_from_selection_id(cr, uid, arg[2])
+
+                result.extend([
+                    '&',
+                    ('relation_all_ids.type_id', '=', type_id),
+                    ('relation_all_ids.record_type', '=',
+                     'b' if is_inverse else 'a')
+                ])
+
+        return result
+
+    def _search_relation_date(self, cr, uid, obj, name, args, context=None):
+        result = []
+        for arg in args:
+            if isinstance(arg, tuple) and arg[0] == name:
+                #TODO: handle {<,>}{,=}
+                if arg[1] != '=':
+                    continue
+
+                result.extend([
+                    '&',
+                    '|',
+                    ('relation_all_ids.date_start', '=', False),
+                    ('relation_all_ids.date_start', '<=', arg[2]),
+                    '|',
+                    ('relation_all_ids.date_end', '=', False),
+                    ('relation_all_ids.date_end', '>=', arg[2]),
+                ])
+
+        return result
+
+    def _search_related_partner_id(
+            self, cr, uid, dummy_obj, name, args, context=None):
+        result = []
+        for arg in args:
+            if isinstance(arg, tuple) and arg[0] == name:
+                if arg[1] != '=':
+                    continue
+
+                result.append(
+                    (
+                        'relation_all_ids.other_partner_id',
+                        '=',
+                        arg[2],
+                    ))
+
+        return result
+
+    _columns = {
+        'relation_ids': fields.function(
+            _get_relation_ids,
+            fnct_inv=_set_relation_ids,
+            type='one2many', obj='res.partner.relation',
+            string='Relations'
+        ),
+        'relation_all_ids': fields.one2many(
+            'res.partner.relation.all', 'this_partner_id',
+            string='All relations with current partner',
+            auto_join=True,
+        ),
+        'search_relation_id': fields.function(
+            lambda self, cr, uid, ids, *args: dict([
+                (i, False) for i in ids]),
+            fnct_search=_search_relation_id,
+            string='Has relation of type',
+            type='many2one', obj='res.partner.relation.type.selection'
+        ),
+        'search_relation_partner_id': fields.function(
+            lambda self, cr, uid, ids, *args: dict([
+                (i, False) for i in ids]),
+            fnct_search=_search_related_partner_id,
+            string='Has relation with',
+            type='many2one', obj='res.partner'
+        ),
+        'search_relation_date': fields.function(
+            lambda self, cr, uid, ids, *args: dict([
+                (i, False) for i in ids]),
+            fnct_search=_search_relation_date,
+            string='Relation valid', type='date'
+        ),
+    }
+
+    def copy_data(self, cr, uid, id, default=None, context=None):
+        if default is None:
+            default = {}
+        default.setdefault('relation_ids', [])
+        default.setdefault('relation_all_ids', [])
+        return super(ResPartner, self).copy_data(cr, uid, id, default=default,
+                                                 context=context)
+
+    def search(self, cr, uid, args, offset=0, limit=None, order=None,
+               context=None, count=False):
+        if context is None:
+            context = {}
+        #inject searching for current relation date if we search for relation
+        #properties and no explicit date was given
+        date_args = []
+        for arg in args:
+            if isinstance(arg, list) and arg[0].startswith('search_relation'):
+                if arg[0] == 'search_relation_date':
+                    date_args = []
+                    break
+                if not date_args:
+                    date_args = [
+                        ('search_relation_date', '=', time.strftime(
+                            DEFAULT_SERVER_DATE_FORMAT))]
+
+        #because of auto_join, we have to do the active test by hand
+        active_args = []
+        if context.get('active_test', True):
+            for arg in args:
+                if isinstance(arg, list) and\
+                        arg[0].startswith('search_relation'):
+                    active_args = [('relation_all_ids.active', '=', True)]
+                    break
+
+        return super(ResPartner, self).search(
+            cr, uid, args + date_args + active_args, offset=offset,
+            limit=limit, order=order, context=context, count=count)
+
+    def read(
+            self, cr, uid, ids, fields=None, context=None,
+            load='_classic_read'):
+        return super(ResPartner, self).read(
+            cr, uid, ids, fields=fields,
+            context=self._update_context(context, ids))
+
+    def write(self, cr, uid, ids, vals, context=None):
+        return super(ResPartner, self).write(
+            cr, uid, ids, vals, context=self._update_context(context, ids))
+
+    def _update_context(self, context, ids):
+        if context is None:
+            context = {}
+        ids = ids if isinstance(ids, list) else [ids] if ids else []
+        result = context.copy()
+        result.setdefault('active_id', ids[0] if ids else None)
+        result.setdefault('active_ids', ids)
+        result.setdefault('active_model', self._name)
+        return result

=== added file 'partner_relations/model/res_partner_relation.py'
--- partner_relations/model/res_partner_relation.py	1970-01-01 00:00:00 +0000
+++ partner_relations/model/res_partner_relation.py	2014-06-19 13:13:46 +0000
@@ -0,0 +1,257 @@
+# -*- coding: utf-8 -*-
+'''Define model res.partner.relation'''
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright (C) 2013 Therp BV (<http://therp.nl>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from openerp.osv.orm import Model
+from openerp.osv import fields
+
+
+class ResPartnerRelation(Model):
+    '''Model res.partner.relation is used to describe all links or relations
+    between partners in the database.
+
+    In many parts of the code we have to know whether the active partner is
+    the left partner, or the right partner. If the active partner is the
+    right partner we have to show the inverse name.
+
+    Because the active partner is crucial for the working of partner
+    relationships, we make sure on the res.partner model that the partner id
+    is set in the context where needed.
+    '''
+    _name = 'res.partner.relation'
+    _description = 'Partner relation'
+
+    def _on_right_partner(self, cr, uid, right_partner_id, context=None):
+        '''Determine wether functions are called in a situation where the
+        active partner is the right partner. Default False!
+        '''
+        if (context and 'active_ids' in context
+                and right_partner_id in context.get('active_ids', [])):
+            return True
+        return False
+
+    def _correct_vals(self, cr, uid, vals, context=None):
+        '''Fill type and left and right partner id, according to wether
+        we have a normal relation type or an inverse relation type'''
+        vals = vals.copy()
+        # If type_selection_id ends in 1, it is a reverse relation type
+        if 'type_selection_id' in vals:
+            prts_model = self.pool['res.partner.relation.type.selection']
+            type_selection_id = vals['type_selection_id']
+            (type_id, is_reverse) = (
+                prts_model.get_type_from_selection_id(
+                    cr, uid, type_selection_id))
+            vals['type_id'] = type_id
+            if context.get('active_id'):
+                if is_reverse:
+                    vals['right_partner_id'] = context['active_id']
+                else:
+                    vals['left_partner_id'] = context['active_id']
+            if vals.get('partner_id_display'):
+                if is_reverse:
+                    vals['left_partner_id'] = vals['partner_id_display']
+                else:
+                    vals['right_partner_id'] = vals['partner_id_display']
+        return vals
+
+    def _get_computed_fields(
+            self, cr, uid, ids, field_names, arg, context=None):
+        '''Return a dictionary of dictionaries, with for every partner for
+        ids, the computed values.'''
+        def get_values(this, dummy_field_names, dummy_arg, context=None):
+            '''Get computed values for record'''
+            values = {}
+            on_right_partner = self._on_right_partner(
+                cr, uid, this.right_partner_id.id, context=context)
+            # type_selection_id
+            values['type_selection_id'] = (
+                ((this.type_id.id) * 10) + (on_right_partner and 1 or 0))
+            # partner_id_display
+            values['partner_id_display'] = (
+                on_right_partner and this.left_partner_id.id
+                or this.right_partner_id.id
+            )
+            # is_relation_expired
+            today = fields.date.context_today(self, cr, uid, context=context)
+            values['is_relation_expired'] = (
+                this.date_end and (this.date_end < today))
+            # is_relation_future
+            values['is_relation_future'] = this.date_start > today
+            return values
+
+        return dict([
+            (this.id, get_values(this, field_names, arg, context=context))
+            for this in self.browse(cr, uid, ids, context=context)
+        ])
+
+    def write(self, cr, uid, ids, vals, context=None):
+        '''Override write to correct values, before being stored.'''
+        vals = self._correct_vals(cr, uid, vals, context=context)
+        return super(ResPartnerRelation, self).write(
+            cr, uid, ids, vals, context=context)
+
+    def create(self, cr, uid, vals, context=None):
+        '''Override create to correct values, before being stored.'''
+        vals = self._correct_vals(cr, uid, vals, context=context)
+        return super(ResPartnerRelation, self).create(
+            cr, uid, vals, context=context)
+
+    def on_change_type_selection_id(
+            self, cr, uid, dummy_ids, type_selection_id, context=None):
+        '''Set domain on partner_id_display, when selection a relation type'''
+        result = {
+            'domain': {'partner_id_display': []},
+            'value': {'type_id': False}
+        }
+        if not type_selection_id:
+            return result
+        prts_model = self.pool['res.partner.relation.type.selection']
+        type_model = self.pool['res.partner.relation.type']
+        (type_id, is_reverse) = (
+            prts_model.get_type_from_selection_id(
+                cr, uid, type_selection_id)
+        )
+        result['value']['type_id'] = type_id
+        type_obj = type_model.browse(cr, uid, type_id, context=context)
+        partner_domain = []
+        check_contact_type = type_obj.contact_type_right
+        check_partner_category = (
+            type_obj.partner_category_right and
+            type_obj.partner_category_right.id
+        )
+        if is_reverse:
+            # partner_id_display is left partner
+            check_contact_type = type_obj.contact_type_left
+            check_partner_category = (
+                type_obj.partner_category_left and
+                type_obj.partner_category_left.id
+            )
+        if check_contact_type == 'c':
+            partner_domain.append(('is_company', '=', True))
+        if check_contact_type == 'p':
+            partner_domain.append(('is_company', '=', False))
+        if check_partner_category:
+            partner_domain.append(
+                ('category_id', 'child_of', check_partner_category))
+        result['domain']['partner_id_display'] = partner_domain
+        return result
+
+    _columns = {
+        'left_partner_id': fields.many2one(
+            'res.partner', string='Left partner', required=True,
+            auto_join=True),
+        'right_partner_id': fields.many2one(
+            'res.partner', string='Right partner', required=True,
+            auto_join=True),
+        'type_id': fields.many2one(
+            'res.partner.relation.type', string='Type', required=True,
+            auto_join=True),
+        'date_start': fields.date('Starting date'),
+        'date_end': fields.date('Ending date'),
+        'type_selection_id': fields.function(
+            _get_computed_fields,
+            multi="computed_fields",
+            fnct_inv=lambda *args: None,
+            type='many2one', obj='res.partner.relation.type.selection',
+            string='Type',
+        ),
+        'partner_id_display': fields.function(
+            _get_computed_fields,
+            multi="computed_fields",
+            fnct_inv=lambda *args: None,
+            type='many2one', obj='res.partner',
+            string='Partner'
+        ),
+        'is_relation_expired': fields.function(
+            _get_computed_fields,
+            multi="computed_fields",
+            type='boolean',
+            method=True,
+            string='Relation is expired',
+        ),
+        'is_relation_future': fields.function(
+            _get_computed_fields,
+            multi="computed_fields",
+            type='boolean',
+            method=True,
+            string='Relation is in the future',
+        ),
+        'active': fields.boolean('Active'),
+    }
+
+    _defaults = {
+        'active': True,
+    }
+
+    def _check_dates(self, cr, uid, ids, context=None):
+        '''End date should not be before start date, if noth filled'''
+        for line in self.browse(cr, uid, ids, context=context):
+            if line.date_start and line.date_end:
+                if line.date_start > line.date_end:
+                    return False
+        return True
+
+    def _check_partner_type_left(self, cr, uid, ids, context=None):
+        '''Check left partner for required company or person'''
+        for this in self.browse(cr, uid, ids, context=context):
+            ptype = this.type_id.contact_type_left
+            company = this.left_partner_id.is_company
+            if (ptype == 'c' and not company) or (ptype == 'p' and company):
+                return False
+        return True
+
+    def _check_partner_type_right(self, cr, uid, ids, context=None):
+        '''Check right partner for required company or person'''
+        for this in self.browse(cr, uid, ids, context=context):
+            ptype = this.type_id.contact_type_right
+            company = this.right_partner_id.is_company
+            if (ptype == 'c' and not company) or (ptype == 'p' and company):
+                return False
+        return True
+
+    def _check_not_with_self(self, cr, uid, ids, context=None):
+        '''Not allowed to link partner to same partner'''
+        for this in self.browse(cr, uid, ids, context=context):
+            if this.left_partner_id == this.right_partner_id:
+                return False
+        return True
+
+    _constraints = [
+        (
+            _check_dates,
+            'The starting date cannot be after the ending date.',
+            ['date_start', 'date_end']
+        ),
+        (
+            _check_partner_type_left,
+            'The left partner is not applicable for this relation type.',
+            ['left_partner_id', 'type_id']
+        ),
+        (
+            _check_partner_type_right,
+            'The right partner is not applicable for this relation type.',
+            ['right_partner_id', 'type_id']
+        ),
+        (
+            _check_not_with_self,
+            'Partners cannot have a relation with themselves.',
+            ['left_partner_id', 'right_partner_id']
+        ),
+    ]

=== added file 'partner_relations/model/res_partner_relation_all.py'
--- partner_relations/model/res_partner_relation_all.py	1970-01-01 00:00:00 +0000
+++ partner_relations/model/res_partner_relation_all.py	2014-06-19 13:13:46 +0000
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright (C) 2014 Therp BV (<http://therp.nl>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from openerp.osv.orm import Model
+from openerp.osv import fields
+from openerp.tools import drop_view_if_exists
+from res_partner_relation_type_selection import ResPartnerRelationTypeSelection
+
+
+class ResPartnerRelationAll(Model):
+    _auto = False
+    _log_access = False
+    _name = 'res.partner.relation.all'
+    _description = 'All (non-inverse + inverse) relations between partners'
+
+    def _auto_init(self, cr, context=None):
+        drop_view_if_exists(cr, self._table)
+        cr.execute(
+            '''create or replace view %s as
+            select
+                id * 10 as id,
+                id as relation_id,
+                type_id,
+                cast('a' as char(1)) as record_type,
+                left_partner_id as this_partner_id,
+                right_partner_id as other_partner_id,
+                date_start,
+                date_end,
+                active
+            from res_partner_relation
+            union select
+                id * 10 + 1,
+                id,
+                type_id,
+                cast('b' as char(1)),
+                right_partner_id,
+                left_partner_id,
+                date_start,
+                date_end,
+                active
+             from res_partner_relation''' % self._table)
+
+        return super(ResPartnerRelationAll, self)._auto_init(
+            cr, context=context)
+
+    _columns = {
+        'record_type': fields.selection(
+            ResPartnerRelationTypeSelection._RECORD_TYPES, 'Record type'),
+        'relation_id': fields.many2one(
+            'res.partner.relation', 'Relation'),
+        'type_id': fields.many2one(
+            'res.partner.relation.type', 'Relation type'),
+        'this_partner_id': fields.many2one('res.partner', 'Current partner'),
+        'other_partner_id': fields.many2one('res.partner', 'Other partner'),
+        'date_start': fields.date('Starting date'),
+        'date_end': fields.date('Ending date'),
+        'active': fields.boolean('Active'),
+    }

=== added file 'partner_relations/model/res_partner_relation_type.py'
--- partner_relations/model/res_partner_relation_type.py	1970-01-01 00:00:00 +0000
+++ partner_relations/model/res_partner_relation_type.py	2014-06-19 13:13:46 +0000
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+'''Define model res.partner.relation.type'''
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright (C) 2013 Therp BV (<http://therp.nl>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from openerp.osv.orm import Model
+from openerp.osv import fields
+
+
+class ResPartnerRelationType(Model):
+    '''Model that defines relation types that might exist between partners'''
+    _name = 'res.partner.relation.type'
+    _description = 'Parter relation type'
+    _order = 'name'
+
+    def _get_partner_types(self, cr, uid, context=None):
+        return (('c', 'Company'), ('p', 'Person'),)
+
+    _columns = {
+        'name': fields.char(
+            'Name', size=128, required=True, translate=True),
+        'name_inverse': fields.char(
+            'Inverse name', size=128, required=True, translate=True),
+        'contact_type_left': fields.selection(
+            _get_partner_types, 'Left partner type'),
+        'contact_type_right': fields.selection(
+            _get_partner_types, 'Right partner type'),
+        'partner_category_left': fields.many2one(
+            'res.partner.category', 'Left partner category'),
+        'partner_category_right': fields.many2one(
+            'res.partner.category', 'Right partner category'),
+        }

=== added file 'partner_relations/model/res_partner_relation_type_selection.py'
--- partner_relations/model/res_partner_relation_type_selection.py	1970-01-01 00:00:00 +0000
+++ partner_relations/model/res_partner_relation_type_selection.py	2014-06-19 13:13:46 +0000
@@ -0,0 +1,133 @@
+# -*- coding: UTF-8 -*-
+'''
+Created on 23 may 2014
+
+@author: Ronald Portier, Therp
+
+rportier@xxxxxxxx
+http://www.therp.nl
+
+For the model defined here _auto is set to False to prevent creating a
+database file. All i/o operations are overridden to use a sql SELECT that
+takes data from res_partner_connection_type where each type is included in the
+result set twice, so it appears that the connection type and the inverse
+type are separate records..
+
+The original function _auto_init is still called because this function
+normally (if _auto == True) not only creates the db tables, but it also takes
+care of registering all fields in ir_model_fields. This is needed to make
+the field labels translatable.
+
+example content for last lines of _statement:
+select id, record_type,
+  customer_id, customer_name, customer_city, customer_zip, customer_street,
+  caller_id, caller_name, caller_phone, caller_fax, caller_email
+from FULL_LIST as ResPartnerRelationTypeSelection where record_type = 'c'
+ORDER BY ResPartnerRelationTypeSelection.customer_name asc,
+ResPartnerRelationTypeSelection.caller_name asc;
+
+'''
+from openerp.osv import fields
+from openerp.osv import orm
+from openerp.tools import drop_view_if_exists
+from openerp.addons.partner_relations.model.res_partner_relation_type\
+    import ResPartnerRelationType
+
+
+class ResPartnerRelationTypeSelection(orm.Model):
+    '''Virtual relation types'''
+
+    _RECORD_TYPES = [
+        ('a', 'Type'),
+        ('b', 'Inverse type'),
+    ]
+
+    _auto = False  # Do not try to create table in _auto_init(..)
+    _log_access = False
+
+    def get_type_from_selection_id(self, cr, uid, selection_id):
+        '''Selection id ic computed from id of underlying type and the
+        kind of record. This function does the inverse computation to give
+        back the original type id, and about the record type.'''
+        type_id = selection_id / 10
+        is_reverse = (selection_id % 10) > 0
+        return (type_id, is_reverse)
+
+    def _auto_init(self, cr, context=None):
+        drop_view_if_exists(cr, self._table)
+        #TODO: we lose field value's translations here.
+        #probably we need to patch ir_translation.get_source for that
+        #to get res_partner_relation_type's translations
+        cr.execute(
+            '''create or replace view %s as
+            select
+                id * 10 as id,
+                id as type_id,
+                cast('a' as char(1)) as record_type,
+                name as name,
+                contact_type_left as contact_type_this,
+                contact_type_right as contact_type_other,
+                partner_category_left as partner_category_this,
+                partner_category_right as partner_category_other
+            from res_partner_relation_type
+            union select
+                id * 10 + 1,
+                id,
+                cast('b' as char(1)),
+                name_inverse,
+                contact_type_right,
+                contact_type_left,
+                partner_category_right,
+                partner_category_left
+             from res_partner_relation_type''' % self._table)
+
+        return super(ResPartnerRelationTypeSelection, self)._auto_init(
+            cr, context=context)
+
+    def _search_partner_category_this(self, cr, uid, obj, field_name, args,
+                                      context=None):
+        category_ids = []
+
+        for arg in args:
+            if isinstance(arg, tuple) and arg[0] == field_name\
+                    and (arg[1] == '=' or arg[1] == 'in'):
+                #TODO don't we have an api function to eval that?
+                for delta in arg[2]:
+                    if delta[0] == 6:
+                        category_ids.extend(delta[2])
+
+        if category_ids:
+            return [
+                '|',
+                ('partner_category_this', '=', False),
+                ('partner_category_this', 'in', category_ids),
+            ]
+        else:
+            return [('partner_category_this', '=', False)]
+
+    _name = 'res.partner.relation.type.selection'
+    _description = 'All relation types'
+    _foreign_keys = []
+    _columns = {
+        'record_type': fields.selection(_RECORD_TYPES, 'Record type', size=16),
+        'type_id': fields.integer('Type'),
+        'name': fields.char('Name', size=64),
+        'contact_type_this': fields.selection(
+            ResPartnerRelationType._get_partner_types.im_func,
+            'Current record\'s partner type'),
+        'contact_type_other': fields.selection(
+            ResPartnerRelationType._get_partner_types.im_func,
+            'Other record\'s partner type'),
+        'partner_category_this': fields.many2one(
+            'res.partner.category', 'Current record\'s category'),
+        'partner_category_other': fields.many2one(
+            'res.partner.category', 'Other record\'s category'),
+        #search field to handle many2many deltas from the client
+        'search_partner_category_this': fields.function(
+            lambda self, cr, uid, ids, context=None: dict(
+                [(i, False) for i in ids]),
+            fnct_search=_search_partner_category_this,
+            type='many2many', obj='res.partner.category',
+            string='Current record\'s category'),
+    }
+    _order = 'name asc'

=== added directory 'partner_relations/security'
=== added file 'partner_relations/security/ir.model.access.csv'
--- partner_relations/security/ir.model.access.csv	1970-01-01 00:00:00 +0000
+++ partner_relations/security/ir.model.access.csv	2014-06-19 13:13:46 +0000
@@ -0,0 +1,6 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+read_res_partner_relation,access_res_partner_relation,model_res_partner_relation,,1,0,0,0
+read_res_partner_relation_type,access_res_partner_relation_type,model_res_partner_relation_type,,1,0,0,0
+crud_res_partner_relation_type_inverse,access_res_partner_relation_type_inverse,model_res_partner_relation_type_inverse,,1,1,1,1
+crud_res_partner_relation,access_res_partner_relation,model_res_partner_relation,base.group_partner_manager,1,1,1,1
+crud_res_partner_relation_type,access_res_partner_relation_type,model_res_partner_relation_type,base.group_sale_manager,1,1,1,1

=== added directory 'partner_relations/static'
=== added directory 'partner_relations/static/src'
=== added directory 'partner_relations/static/src/img'
=== added file 'partner_relations/static/src/img/icon.png'
Binary files partner_relations/static/src/img/icon.png	1970-01-01 00:00:00 +0000 and partner_relations/static/src/img/icon.png	2014-06-19 13:13:46 +0000 differ
=== added directory 'partner_relations/view'
=== added file 'partner_relations/view/menu.xml'
--- partner_relations/view/menu.xml	1970-01-01 00:00:00 +0000
+++ partner_relations/view/menu.xml	2014-06-19 13:13:46 +0000
@@ -0,0 +1,15 @@
+<openerp>
+    <data>
+        <act_window
+            id="action_res_partner_relation_type"
+            res_model="res.partner.relation.type"
+            view_mode="tree,form"
+            name="Partner relations"
+        />
+        <menuitem
+            id="menu_res_partner_relation_type"
+            parent="base.menu_config_address_book"
+            action="action_res_partner_relation_type"
+        />
+    </data>
+</openerp>

=== added file 'partner_relations/view/res_partner.xml'
--- partner_relations/view/res_partner.xml	1970-01-01 00:00:00 +0000
+++ partner_relations/view/res_partner.xml	2014-06-19 13:13:46 +0000
@@ -0,0 +1,82 @@
+<openerp>
+    <data>
+        <record id="view_res_partner_filter" model="ir.ui.view">
+            <field name="inherit_id" ref="base.view_res_partner_filter" />
+            <field name="model">res.partner</field>
+            <field type="xml" name="arch">
+                <data>
+                    <field name="parent_id" position="after">
+                        <field name="search_relation_partner_id" />
+                        <field name="search_relation_id" />
+                        <field name="search_relation_date" />
+                    </field>
+                </data>
+            </field>
+        </record>
+        <record id="view_partner_form" model="ir.ui.view">
+            <field name="inherit_id" ref="base.view_partner_form" />
+            <field name="model">res.partner</field>
+            <field type="xml" name="arch">
+                <data>
+                    <xpath expr="//sheet/notebook" position="inside">
+                        <page string="Relations">
+                            <field
+                                name="relation_ids"
+                            >
+                                <tree
+                                    editable="top"
+                                    colors="gray:is_relation_expired==True;blue:is_relation_future==True"
+                                >
+                                    <field
+                                        name="type_selection_id"
+                                        required="True"
+                                        context="{
+                                            'parent_model': 'res.partner',
+                                            'parent_id': parent.id,
+                                        }"
+                                        domain="[
+                                            '|',
+                                                ('contact_type_this', '=', parent.is_company and 'c' or 'p'),
+                                                ('contact_type_this', '=', False),
+                                            ('search_partner_category_this', '=', parent.category_id),
+
+                                        ]
+                                        "
+                                        options="{'create': false, 'create_edit': false}"
+                                        on_change="on_change_type_selection_id(type_selection_id)"
+                                    /> 
+                                    <field name="type_id" invisible="True" />
+                                    <field
+                                        name="partner_id_display"
+                                        required="True"
+                                        attrs="{
+                                            'readonly': [('type_selection_id','=',False)],
+                                        }"
+                                        options="{'create': false, 'create_edit': false}"
+                                    />
+                                    <field
+                                        name="date_start"
+                                    />
+                                    <field
+                                        name="date_end"
+                                    />
+                                    <field
+                                        name="active"
+                                    />
+                                    <field
+                                        name="is_relation_expired"
+                                        invisible="True"
+                                    />
+                                    <field
+                                        name="is_relation_future"
+                                        invisible="True"
+                                    />
+                                </tree>
+                            </field>
+                        </page>
+                    </xpath>
+                </data>
+            </field>
+        </record>
+    </data>
+</openerp>

=== added file 'partner_relations/view/res_partner_relation.xml'
--- partner_relations/view/res_partner_relation.xml	1970-01-01 00:00:00 +0000
+++ partner_relations/view/res_partner_relation.xml	2014-06-19 13:13:46 +0000
@@ -0,0 +1,16 @@
+<openerp>
+    <data>
+        <record id="form_res_partner_relation" model="ir.ui.view">
+            <field name="model">res.partner.relation</field>
+            <field type="xml" name="arch">
+                <form version="7.0" string="Partner relation">
+                    <sheet>
+                        <field name="left_partner_id" />
+                        <field name="type_id" />
+                        <field name="right_partner_id" />
+                    </sheet>
+                </form>
+            </field>
+        </record>
+    </data>
+</openerp>

=== added file 'partner_relations/view/res_partner_relation_type.xml'
--- partner_relations/view/res_partner_relation_type.xml	1970-01-01 00:00:00 +0000
+++ partner_relations/view/res_partner_relation_type.xml	2014-06-19 13:13:46 +0000
@@ -0,0 +1,44 @@
+<openerp>
+    <data>
+        <record id="tree_res_partner_relation_type" model="ir.ui.view">
+            <field name="model">res.partner.relation.type</field>
+            <field name="type">tree</field>
+            <field type="xml" name="arch">
+                <tree version="7.0" string="Partner relation">
+                    <field name="name" />
+                    <field name="name_inverse" />
+                    <field name="contact_type_left" />
+                    <field name="contact_type_right" />
+                </tree>
+            </field>
+        </record>
+        <record id="form_res_partner_relation_type" model="ir.ui.view">
+            <field name="model">res.partner.relation.type</field>
+            <field name="type">form</field>
+            <field type="xml" name="arch">
+                <form version="7.0" string="Partner relation">
+                    <sheet>
+                        <group>
+                            <group
+                                colspan="2" col="2"
+                                string="Left side of relation"
+                            >
+                                <field name="name" />
+                                <field name="contact_type_left" />
+                                <field name="partner_category_left" />
+                            </group>
+                            <group
+                                colspan="2" col="2"
+                                string="Right side of relation"
+                            >
+                                <field name="name_inverse" />
+                                <field name="contact_type_right" />
+                                <field name="partner_category_right" />
+                            </group>
+                        </group>
+                    </sheet>
+                </form>
+            </field>
+        </record>
+    </data>
+</openerp>


Follow ups