From dcaeadd1ae7a5f76a78b50da4947fffa547556e4 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 28 Jul 2014 10:03:22 +0200 Subject: [PATCH] [ADD] partner_relations --- partner_relations/__init__.py | 21 ++ partner_relations/__openerp__.py | 94 +++++ partner_relations/i18n/nl.po | 268 ++++++++++++++ partner_relations/i18n/partner_relations.pot | 277 +++++++++++++++ partner_relations/model/__init__.py | 25 ++ partner_relations/model/res_partner.py | 238 +++++++++++++ .../model/res_partner_relation.py | 330 ++++++++++++++++++ .../model/res_partner_relation_all.py | 88 +++++ .../model/res_partner_relation_type.py | 48 +++ .../res_partner_relation_type_selection.py | 167 +++++++++ .../security/ir.model.access.csv | 7 + partner_relations/static/src/img/icon.png | Bin 0 -> 12960 bytes partner_relations/view/menu.xml | 15 + partner_relations/view/res_partner.xml | 96 +++++ .../view/res_partner_relation.xml | 33 ++ .../view/res_partner_relation_all.xml | 34 ++ .../view/res_partner_relation_type.xml | 44 +++ 17 files changed, 1785 insertions(+) create mode 100644 partner_relations/__init__.py create mode 100644 partner_relations/__openerp__.py create mode 100644 partner_relations/i18n/nl.po create mode 100644 partner_relations/i18n/partner_relations.pot create mode 100644 partner_relations/model/__init__.py create mode 100644 partner_relations/model/res_partner.py create mode 100644 partner_relations/model/res_partner_relation.py create mode 100644 partner_relations/model/res_partner_relation_all.py create mode 100644 partner_relations/model/res_partner_relation_type.py create mode 100644 partner_relations/model/res_partner_relation_type_selection.py create mode 100644 partner_relations/security/ir.model.access.csv create mode 100644 partner_relations/static/src/img/icon.png create mode 100644 partner_relations/view/menu.xml create mode 100644 partner_relations/view/res_partner.xml create mode 100644 partner_relations/view/res_partner_relation.xml create mode 100644 partner_relations/view/res_partner_relation_all.xml create mode 100644 partner_relations/view/res_partner_relation_type.xml diff --git a/partner_relations/__init__.py b/partner_relations/__init__.py new file mode 100644 index 000000000..4d083ea93 --- /dev/null +++ b/partner_relations/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2013 Therp BV (). +# +# 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 . +# +############################################################################## +import model diff --git a/partner_relations/__openerp__.py b/partner_relations/__openerp__.py new file mode 100644 index 000000000..2fccd144c --- /dev/null +++ b/partner_relations/__openerp__.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2013 Therp BV (). +# +# 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 . +# +############################################################################## +{ + "name": "Partner relations", + "version": "1.1", + "author": "Therp BV", + "complexity": "normal", + "description": """ +Introduction +------------ + +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": [ + 'base', + 'web_m2x_options', + 'web_tree_many2one_clickable', + ], + "data": [ + "view/res_partner_relation_all.xml", + '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': [], + }, +} diff --git a/partner_relations/i18n/nl.po b/partner_relations/i18n/nl.po new file mode 100644 index 000000000..30f9f8b47 --- /dev/null +++ b/partner_relations/i18n/nl.po @@ -0,0 +1,268 @@ +# 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 koppeling" + +#. module: partner_relations +#: field:res.partner,search_relation_partner_id:0 +msgid "Has relation with" +msgstr "Heeft koppeling 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" + +#. module: partner_relations +#: field:res.partner,search_relation_partner_category_id:0 +msgid "Has relation with a partner in category" +msgstr "Heeft koppeling met relatie van de categorie" + +#. module: partner_relations +#: model:ir.actions.act_window,name:partner_relations.action_show_partner_relations +msgid "Show partner's relations" +msgstr "Toon relatiekoppelingen" + +#. module: partner_relations +#: constraint:res.partner.relation:0 +msgid "The same relation can't be created twice." +msgstr "Dezelfde koppeling kan niet dubbel aan worden gemaakt." diff --git a/partner_relations/i18n/partner_relations.pot b/partner_relations/i18n/partner_relations.pot new file mode 100644 index 000000000..fb9a8249a --- /dev/null +++ b/partner_relations/i18n/partner_relations.pot @@ -0,0 +1,277 @@ +# 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-07-01 08:52+0000\n" +"PO-Revision-Date: 2014-07-01 08:52+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,search_relation_partner_category_id:0 +msgid "Has relation with a partner in category" +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.all: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 +#: view:res.partner.relation.all:0 +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 +#: field:res.partner.relation.all,type_selection_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 +#: model:ir.actions.act_window,name:partner_relations.action_show_partner_relations +msgid "Show partner's relations" +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 +#: sql_constraint:res.partner.relation:0 +msgid "The same relation can't be created twice." +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 "" + +#. module: partner_relations +#: model:ir.model,name:partner_relations.model_ir_translation +msgid "ir.translation" +msgstr "" + diff --git a/partner_relations/model/__init__.py b/partner_relations/model/__init__.py new file mode 100644 index 000000000..eb5702410 --- /dev/null +++ b/partner_relations/model/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2013 Therp BV (). +# +# 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 . +# +############################################################################## +import res_partner +import res_partner_relation +import res_partner_relation_type +import res_partner_relation_type_selection +from . import res_partner_relation_all diff --git a/partner_relations/model/res_partner.py b/partner_relations/model/res_partner.py new file mode 100644 index 000000000..760bfce7b --- /dev/null +++ b/partner_relations/model/res_partner.py @@ -0,0 +1,238 @@ +# -*- coding: utf-8 -*- +'''Extend res.partner model''' +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2013 Therp BV (). +# +# 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 . +# +############################################################################## +import time +from openerp.osv import orm, fields +from openerp.osv.expression import is_leaf +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)''' + + ' order by ' + self.pool['res.partner.relation']._order, + (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: + result.append( + ( + 'relation_all_ids.other_partner_id', + arg[1], + arg[2], + )) + + return result + + def _search_related_partner_category_id( + self, cr, uid, dummy_obj, name, args, context=None): + result = [] + for arg in args: + if isinstance(arg, tuple) and arg[0] == name: + result.append( + ( + 'relation_all_ids.other_partner_id.category_id', + arg[1], + arg[2], + )) + + return result + + _columns = { + 'relation_ids': fields.function( + lambda self, *args, **kwargs: self._get_relation_ids( + *args, **kwargs), + fnct_inv=_set_relation_ids, + type='one2many', obj='res.partner.relation', + string='Relations', + selectable=False, + ), + 'relation_all_ids': fields.one2many( + 'res.partner.relation.all', 'this_partner_id', + string='All relations with current partner', + auto_join=True, + selectable=False, + ), + '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' + ), + 'search_relation_partner_category_id': fields.function( + lambda self, cr, uid, ids, *args: dict([ + (i, False) for i in ids]), + fnct_search=_search_related_partner_category_id, + string='Has relation with a partner in category', + type='many2one', obj='res.partner.category' + ), + } + + 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 is_leaf(arg) 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 is_leaf(arg) 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 diff --git a/partner_relations/model/res_partner_relation.py b/partner_relations/model/res_partner_relation.py new file mode 100644 index 000000000..515f64a6b --- /dev/null +++ b/partner_relations/model/res_partner_relation.py @@ -0,0 +1,330 @@ +# -*- coding: utf-8 -*- +'''Define model res.partner.relation''' +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2013 Therp BV (). +# +# 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 . +# +############################################################################## +from openerp.osv.orm import Model +from openerp.osv import fields +from openerp.tools.translate import _ + + +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' + _order = 'active desc, date_start desc, date_end desc' + + 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, ondelete='cascade'), + 'right_partner_id': fields.many2one( + 'res.partner', string='Right partner', required=True, + auto_join=True, ondelete='cascade'), + '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 + + def _check_relation_uniqueness(self, cr, uid, ids, context=None): + '''Forbid multiple active relations of the same type between the same + partners''' + for this in self.browse(cr, uid, ids, context=context): + if not this.active: + continue + if self.search( + cr, uid, + [ + ('type_id', '=', this.type_id.id), + ('active', '=', True), + ('id', '!=', this.id), + ('left_partner_id', '=', this.left_partner_id.id), + ('right_partner_id', '=', this.right_partner_id.id), + ], + context=context): + 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'] + ), + ( + _check_relation_uniqueness, + "The same relation can't be created twice.", + ['left_partner_id', 'right_partner_id', 'active'] + ) + ] + + def get_action_related_partners(self, cr, uid, ids, context=None): + '''return a window action showing a list of partners taking part in the + relations names by ids. Context key 'partner_relations_show_side' + determines if we show 'left' side, 'right' side or 'all' (default) + partners. + If active_model is res.partner.relation.all, left=this and + right=other''' + if context is None: + context = {} + + field_names = {} + + if context.get('active_model', self._name) == self._name: + field_names = { + 'left': ['left'], + 'right': ['right'], + 'all': ['left', 'right'] + } + elif context.get('active_model') == 'res.partner.relation.all': + field_names = { + 'left': ['this'], + 'right': ['other'], + 'all': ['this', 'other'] + } + else: + assert False, 'Unknown active_model!' + + partner_ids = [] + field_names = field_names[ + context.get('partner_relations_show_side', 'all')] + field_names = ['%s_partner_id' % n for n in field_names] + + for relation in self.pool[context.get('active_model')].read( + cr, uid, ids, context=context, load='_classic_write'): + for name in field_names: + partner_ids.append(relation[name]) + + return { + 'name': _('Related partners'), + 'type': 'ir.actions.act_window', + 'res_model': 'res.partner', + 'domain': [('id', 'in', partner_ids)], + 'views': [(False, 'tree'), (False, 'form')], + 'view_type': 'form' + } diff --git a/partner_relations/model/res_partner_relation_all.py b/partner_relations/model/res_partner_relation_all.py new file mode 100644 index 000000000..7baf084dd --- /dev/null +++ b/partner_relations/model/res_partner_relation_all.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2014 Therp BV (). +# +# 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 . +# +############################################################################## +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, + type_id * 10 as type_selection_id + 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, + type_id * 10 + 1 + 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'), + 'type_selection_id': fields.many2one( + 'res.partner.relation.type.selection', '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'), + } + + def name_get(self, cr, uid, ids, context=None): + return dict([ + (this.id, '%s %s %s' % ( + this.this_partner_id.name, + this.type_selection_id.name_get()[0][1], + this.other_partner_id.name, + )) + for this in self.browse(cr, uid, ids, context=context)]) diff --git a/partner_relations/model/res_partner_relation_type.py b/partner_relations/model/res_partner_relation_type.py new file mode 100644 index 000000000..23e320b9d --- /dev/null +++ b/partner_relations/model/res_partner_relation_type.py @@ -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 (). +# +# 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 . +# +############################################################################## +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'), + } diff --git a/partner_relations/model/res_partner_relation_type_selection.py b/partner_relations/model/res_partner_relation_type_selection.py new file mode 100644 index 000000000..7d071f565 --- /dev/null +++ b/partner_relations/model/res_partner_relation_type_selection.py @@ -0,0 +1,167 @@ +# -*- coding: UTF-8 -*- +''' +Created on 23 may 2014 + +@author: Ronald Portier, Therp + +rportier@therp.nl +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' + + def name_get(self, cr, uid, ids, context=None): + 'translate name using translations from res.partner.relation.type' + result = super(ResPartnerRelationTypeSelection, self).name_get( + cr, uid, ids, context=context) + ir_translation = self.pool['ir.translation'] + return [ + (i, ir_translation._get_source( + cr, uid, + 'res.partner.relation.type,name_inverse' + if self.get_type_from_selection_id(cr, uid, i)[1] + else 'res.partner.relation.type,name', + 'model', context.get('lang'), name)) + for i, name in result] + + def name_search(self, cr, uid, name='', args=None, operator='ilike', + context=None, limit=100): + 'search for translated names in res.partner.relation.type' + res_partner_relation_type = self.pool['res.partner.relation.type'] + relation_ids = res_partner_relation_type.search( + cr, uid, [('name', operator, name)], + context=context) + inverse_relation_ids = res_partner_relation_type.search( + cr, uid, [('name_inverse', operator, name)], + context=context) + all_ids = self.search( + cr, uid, + [ + ('id', 'in', + map(lambda x: x * 10, relation_ids) + + map(lambda x: x * 10 + 1, inverse_relation_ids)), + ] + (args or []), + context=context, limit=limit) + return self.name_get(cr, uid, all_ids, context=context) diff --git a/partner_relations/security/ir.model.access.csv b/partner_relations/security/ir.model.access.csv new file mode 100644 index 000000000..840b35875 --- /dev/null +++ b/partner_relations/security/ir.model.access.csv @@ -0,0 +1,7 @@ +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_all,access_res_partner_relation,model_res_partner_relation_all,,1,0,0,0 +read_res_partner_relation_type,access_res_partner_relation_type,model_res_partner_relation_type,,1,0,0,0 +read_res_partner_relation_type_selection,access_res_partner_relation_type,model_res_partner_relation_type_selection,,1,0,0,0 +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 diff --git a/partner_relations/static/src/img/icon.png b/partner_relations/static/src/img/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c6863a3e4852ea743449d6a17547b5ddc16c821c GIT binary patch literal 12960 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 ?Im#$t6v{tsveBQHiC6tSq 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 WM@fu$C{uy}0000 + + + + + diff --git a/partner_relations/view/res_partner.xml b/partner_relations/view/res_partner.xml new file mode 100644 index 000000000..c1426cc9d --- /dev/null +++ b/partner_relations/view/res_partner.xml @@ -0,0 +1,96 @@ + + + + + res.partner + + + + + + + + + + + + + + res.partner + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/partner_relations/view/res_partner_relation.xml b/partner_relations/view/res_partner_relation.xml new file mode 100644 index 000000000..2055bc959 --- /dev/null +++ b/partner_relations/view/res_partner_relation.xml @@ -0,0 +1,33 @@ + + + + res.partner.relation + +

+ + + + + +
+ + + + + code + ir.actions.server + + action = self.get_action_related_partners(cr, uid, context.get('active_ids', []), dict(context or {}, partner_relations_show_side='right')) + True + Show partners + + + Show partners + action + client_action_multi + res.partner.relation.all + + + + + diff --git a/partner_relations/view/res_partner_relation_all.xml b/partner_relations/view/res_partner_relation_all.xml new file mode 100644 index 000000000..b05e28d42 --- /dev/null +++ b/partner_relations/view/res_partner_relation_all.xml @@ -0,0 +1,34 @@ + + + + + res.partner.relation.all + + + + + + + + + + + + res.partner.relation.all + +
+ + + + + + + + + + +
+
+
+
+
diff --git a/partner_relations/view/res_partner_relation_type.xml b/partner_relations/view/res_partner_relation_type.xml new file mode 100644 index 000000000..d0a3c0746 --- /dev/null +++ b/partner_relations/view/res_partner_relation_type.xml @@ -0,0 +1,44 @@ + + + + res.partner.relation.type + tree + + + + + + + + + + + res.partner.relation.type + form + +
+ + + + + + + + + + + + + + +
+
+
+
+