diff --git a/partner_relations/__init__.py b/partner_relations/__init__.py index 057c357a1..91d6405f3 100644 --- a/partner_relations/__init__.py +++ b/partner_relations/__init__.py @@ -1,22 +1,5 @@ # -*- 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 . -# -############################################################################## -from . import model +# © 2013-2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import models from . import tests diff --git a/partner_relations/__openerp__.py b/partner_relations/__openerp__.py index 48936fb10..a08746048 100644 --- a/partner_relations/__openerp__.py +++ b/partner_relations/__openerp__.py @@ -1,23 +1,6 @@ # -*- 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 . -# -############################################################################## +# © 2013-2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Partner relations", "version": "8.0.1.1.1", @@ -31,15 +14,11 @@ "demo": [ "data/demo.xml", ], - "test": [ - "test/test_allow.yml", - ], "data": [ - "view/res_partner_relation_all.xml", - 'view/res_partner_relation.xml', - 'view/res_partner.xml', - 'view/res_partner_relation_type.xml', - 'view/menu.xml', + "views/res_partner_relation_all.xml", + 'views/res_partner.xml', + 'views/res_partner_relation_type.xml', + 'views/menu.xml', 'security/ir.model.access.csv', ], "auto_install": False, diff --git a/partner_relations/data/demo.xml b/partner_relations/data/demo.xml index d177cecd2..2710b08c5 100644 --- a/partner_relations/data/demo.xml +++ b/partner_relations/data/demo.xml @@ -2,20 +2,21 @@ - Is assistant of - Has assistant + is assistant of + has assistant p p - Is competitor of - Is competitor of + is competitor of + is competitor of c c + - Has worked for - Has former employee + works for + has employee p c diff --git a/partner_relations/i18n/da.po b/partner_relations/i18n/da.po index 1045749bb..9ddb7d1b8 100644 --- a/partner_relations/i18n/da.po +++ b/partner_relations/i18n/da.po @@ -124,7 +124,7 @@ msgid "Has former employee" msgstr "" #. module: partner_relations -#: field:res.partner,search_relation_id:0 +#: field:res.partner,search_relation_type_id:0 msgid "Has relation of type" msgstr "" diff --git a/partner_relations/i18n/de.po b/partner_relations/i18n/de.po index 133446d03..45ac46e96 100644 --- a/partner_relations/i18n/de.po +++ b/partner_relations/i18n/de.po @@ -124,7 +124,7 @@ msgid "Has former employee" msgstr "Hat ehemaligen Mitarbeiter" #. module: partner_relations -#: field:res.partner,search_relation_id:0 +#: field:res.partner,search_relation_type_id:0 msgid "Has relation of type" msgstr "Hat Beziehungsart" diff --git a/partner_relations/i18n/es.po b/partner_relations/i18n/es.po index f1248c972..df981f1c7 100644 --- a/partner_relations/i18n/es.po +++ b/partner_relations/i18n/es.po @@ -125,7 +125,7 @@ msgid "Has former employee" msgstr "Tiene ex-empleado" #. module: partner_relations -#: field:res.partner,search_relation_id:0 +#: field:res.partner,search_relation_type_id:0 msgid "Has relation of type" msgstr "Tiene una relación de tipo" diff --git a/partner_relations/i18n/fi.po b/partner_relations/i18n/fi.po index a47c0c4b5..e180d1e6c 100644 --- a/partner_relations/i18n/fi.po +++ b/partner_relations/i18n/fi.po @@ -123,7 +123,7 @@ msgid "Has former employee" msgstr "" #. module: partner_relations -#: field:res.partner,search_relation_id:0 +#: field:res.partner,search_relation_type_id:0 msgid "Has relation of type" msgstr "" diff --git a/partner_relations/i18n/fr.po b/partner_relations/i18n/fr.po index 89e5e8e71..58479aa0a 100644 --- a/partner_relations/i18n/fr.po +++ b/partner_relations/i18n/fr.po @@ -126,7 +126,7 @@ msgid "Has former employee" msgstr "" #. module: partner_relations -#: field:res.partner,search_relation_id:0 +#: field:res.partner,search_relation_type_id:0 msgid "Has relation of type" msgstr "A une relation de type" diff --git a/partner_relations/i18n/it.po b/partner_relations/i18n/it.po index 9fdc25d33..cf3e7f1de 100644 --- a/partner_relations/i18n/it.po +++ b/partner_relations/i18n/it.po @@ -125,7 +125,7 @@ msgid "Has former employee" msgstr "" #. module: partner_relations -#: field:res.partner,search_relation_id:0 +#: field:res.partner,search_relation_type_id:0 msgid "Has relation of type" msgstr "" diff --git a/partner_relations/i18n/nl.po b/partner_relations/i18n/nl.po index 0deb6508f..159989791 100644 --- a/partner_relations/i18n/nl.po +++ b/partner_relations/i18n/nl.po @@ -1,63 +1,75 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * partner_relations -# +# # Translators: +# OCA Transbot , 2016. +# msgid "" msgstr "" "Project-Id-Version: partner-contact (8.0)\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-07 07:18+0000\n" -"PO-Revision-Date: 2016-05-06 15:15+0000\n" +"POT-Creation-Date: 2016-09-02 12:39+0000\n" +"PO-Revision-Date: 2016-09-02 15:03+0200\n" "Last-Translator: OCA Transbot \n" -"Language-Team: Dutch (http://www.transifex.com/oca/OCA-partner-contact-8-0/language/nl/)\n" +"Language-Team: Dutch (http://www.transifex.com/oca/OCA-partner-contact-8-0/" +"language/nl/)\n" +"Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Gtranslator 2.91.6\n" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:178 +#, python-format +msgid "%s partner incompatible with relation type." +msgstr "%s relatie is onverenigbaar met gekozen connectietype." #. module: partner_relations -#: model:ir.actions.act_window,help:partner_relations.action_res_partner_relation #: model:ir.actions.act_window,help:partner_relations.action_res_partner_relation_all msgid "" "

\n" -" Record and track your partners' relations. Relations may be linked to other partners with a type either directly or inversely.\n" +" Record and track your partners' relations. Relations " +"may\n" +" be linked to other partners with a type either directly\n" +" or inversely.\n" "

\n" " " msgstr "" +"

\n" +"Onderhoud de connecties tussen uw relaties. Relaties mogen gekoppeld\n" +"worden zowel via een normale connectie, als met een omgekeerde\n" +"connectie.

" #. module: partner_relations -#: field:res.partner.relation,active:0 field:res.partner.relation.all,active:0 +#: field:res.partner.relation.all,active:0 msgid "Active" -msgstr "" +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 "" +msgstr "Alle connecties tussen relaties (gewoon en omgekeerd)." #. module: partner_relations #: model:ir.model,name:partner_relations.model_res_partner_relation_type_selection msgid "All relation types" -msgstr "" +msgstr "Alle connectietypes" #. module: partner_relations #: field:res.partner,relation_all_ids:0 msgid "All relations with current partner" -msgstr "" +msgstr "Alle connecties vanuit de huidige relatie" #. module: partner_relations -#: field:res.partner.relation,allow_self:0 -#: field:res.partner.relation.type,allow_self:0 -msgid "Allow both sides to be the same" -msgstr "" - -#. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation_type.py:68 +#: code:addons/partner_relations/models/res_partner_relation_type.py:13 +#: selection:res.partner.relation.type,handle_invalid_onchange:0 #, python-format -msgid "Company" -msgstr "Bedrijf" +msgid "Allow existing relations that do not fit changed conditions" +msgstr "" +"Sta bestaande connecties toe die niet voldoen aan de gewijzigde criteria" #. module: partner_relations #: field:res.partner.relation,create_uid:0 @@ -71,26 +83,27 @@ msgstr "Aangemaakt door" msgid "Created on" msgstr "Aangemaakt op" -#. 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 "" +msgstr "Categorie van het huidige record" #. module: partner_relations #: field:res.partner.relation.type.selection,contact_type_this:0 msgid "Current record's partner type" -msgstr "" +msgstr "Type van de huidige relatie" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_type.py:17 +#: selection:res.partner.relation.type,handle_invalid_onchange:0 +#, python-format +msgid "Delete relations that do not fit changed conditions" +msgstr "Verwijder connecties die niet voldoen aan de gewijzigde condities" #. module: partner_relations #: field:res.partner.relation,right_partner_id:0 msgid "Destination Partner" -msgstr "" +msgstr "Doel relatie" #. module: partner_relations #: field:res.partner.relation,display_name:0 @@ -98,49 +111,71 @@ msgstr "" #: field:res.partner.relation.type,display_name:0 #: field:res.partner.relation.type.selection,display_name:0 msgid "Display Name" +msgstr "Te tonen naam" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_type.py:11 +#: selection:res.partner.relation.type,handle_invalid_onchange:0 +#, python-format +msgid "Do not allow change that will result in invalid relations" +msgstr "Sta geen wijziging toe die zal resulteren in ongeldige connecties" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_type.py:15 +#: selection:res.partner.relation.type,handle_invalid_onchange:0 +#, python-format +msgid "End relations per today, if they do not fit changed conditions" msgstr "" +"Beëindig wijzigingen per vandaag indien ze niet voldoen aan de gewijzigde " +"condities" #. module: partner_relations #: field:res.partner.relation,date_end:0 #: field:res.partner.relation.all,date_end:0 msgid "Ending date" -msgstr "" +msgstr "Einddatum" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:184 +#: code:addons/partner_relations/models/res_partner_relation_all.py:259 +#, python-format +msgid "Error!" +msgstr "Fout!" #. module: partner_relations -#: view:res.partner.relation:partner_relations.search_res_partner_relation #: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all msgid "Group By" -msgstr "" +msgstr "Groepeer op" #. module: partner_relations #: model:res.partner.relation.type,name_inverse:partner_relations.rel_type_assistant msgid "Has assistant" -msgstr "" +msgstr "Heeft assistent" #. module: partner_relations #: model:res.partner.relation.type,name_inverse:partner_relations.rel_type_has_worked_for msgid "Has former employee" -msgstr "" +msgstr "Heeft voormalig werknemer" #. module: partner_relations -#: field:res.partner,search_relation_id:0 +#: field:res.partner,search_relation_type_id:0 msgid "Has relation of type" -msgstr "" +msgstr "heeft connectie van type" #. module: partner_relations #: field:res.partner,search_relation_partner_id:0 msgid "Has relation with" -msgstr "" +msgstr "Heeft connectie met" #. module: partner_relations #: field:res.partner,search_relation_partner_category_id:0 msgid "Has relation with a partner in category" -msgstr "" +msgstr "Heeft connectie met een relatie in de categorie" #. module: partner_relations #: model:res.partner.relation.type,name:partner_relations.rel_type_has_worked_for msgid "Has worked for" -msgstr "" +msgstr "Heeft gewerkt voor" #. module: partner_relations #: field:res.partner.relation,id:0 field:res.partner.relation.all,id:0 @@ -149,27 +184,41 @@ msgstr "" msgid "ID" msgstr "ID" +#. module: partner_relations +#: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all +msgid "Include past records" +msgstr "Inclusief beëindigde connecties" + +#. module: partner_relations +#: field:res.partner.relation.type,handle_invalid_onchange:0 +msgid "Invalid relation handling" +msgstr "Afhandeling van ongeldige connecties" + #. module: partner_relations #: field:res.partner.relation.type,name_inverse:0 msgid "Inverse name" -msgstr "" +msgstr "Omgekeerde 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 "" +#: help:res.partner.relation.type.selection,is_inverse:0 +msgid "Inverse relations are from right to left partner." +msgstr "Omgekeerde connecties zijn vanaf de rechter relatie naar de linker." #. module: partner_relations #: model:res.partner.relation.type,name:partner_relations.rel_type_assistant msgid "Is assistant of" -msgstr "" +msgstr "Is assistent van" #. module: partner_relations #: model:res.partner.relation.type,name:partner_relations.rel_type_competitor #: model:res.partner.relation.type,name_inverse:partner_relations.rel_type_competitor msgid "Is competitor of" -msgstr "" +msgstr "Is concurrent van" + +#. module: partner_relations +#: field:res.partner.relation.type.selection,is_inverse:0 +msgid "Is reverse type?" +msgstr "Is omgekeerd type?" #. module: partner_relations #: field:res.partner.relation,__last_update:0 @@ -177,7 +226,7 @@ msgstr "" #: field:res.partner.relation.type,__last_update:0 #: field:res.partner.relation.type.selection,__last_update:0 msgid "Last Modified on" -msgstr "" +msgstr "Laatst bijgewerkt op" #. module: partner_relations #: field:res.partner.relation,write_uid:0 @@ -191,240 +240,310 @@ msgstr "Laatst bijgewerkt door" msgid "Last Updated on" msgstr "Laatst bijgewerkt op" -#. module: partner_relations -#: view:res.partner.relation:partner_relations.search_res_partner_relation -msgid "Left Partner" -msgstr "" - -#. module: partner_relations -#: field:res.partner.relation,left_contact_type:0 -msgid "Left Partner Type" -msgstr "" - #. module: partner_relations #: field:res.partner.relation.type,partner_category_left:0 msgid "Left partner category" -msgstr "" +msgstr "Categorie linkerrelatie" + +#. module: partner_relations +#: selection:res.partner.relation.all,record_type:0 +msgid "Left partner to right partner" +msgstr "Van linker naar rechterrelatie" #. module: partner_relations #: field:res.partner.relation.type,contact_type_left:0 msgid "Left partner type" -msgstr "" +msgstr "Type linkerrelatie" #. module: partner_relations #: view:res.partner.relation.type:partner_relations.form_res_partner_relation_type msgid "Left side of relation" -msgstr "" +msgstr "Linkerkant van relatie" + +#. module: partner_relations +#: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all +msgid "Left to right" +msgstr "Links naar rechts" #. module: partner_relations #: field:res.partner.relation.type,name:0 #: field:res.partner.relation.type.selection,name:0 msgid "Name" -msgstr "" +msgstr "Naam" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:181 +#, python-format +msgid "No %s partner available for relation type." +msgstr "Geen %s relatie beschikbaar voor dit connectietype." + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:256 +#, python-format +msgid "No relation type available for selected partners." +msgstr "Geen connectietype beschikbaar voor verbinden van deze relaties." + +#. module: partner_relations +#: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all +#: field:res.partner.relation.all,this_partner_id:0 +msgid "One Partner" +msgstr "De ene relatie" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_type.py:82 +#, python-format +msgid "Organisation" +msgstr "Organisatie" #. module: partner_relations #: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all #: field:res.partner.relation.all,other_partner_id:0 msgid "Other Partner" -msgstr "" +msgstr "De andere relatie" #. module: partner_relations #: field:res.partner.relation.type.selection,partner_category_other:0 msgid "Other record's category" -msgstr "" +msgstr "Categorie andere relatie" #. module: partner_relations #: field:res.partner.relation.type.selection,contact_type_other:0 msgid "Other record's partner type" -msgstr "" +msgstr "Type andere relatie" #. module: partner_relations #: model:ir.model,name:partner_relations.model_res_partner -#: field:res.partner.relation,any_partner_id:0 -#: field:res.partner.relation,partner_id_display:0 +#: field:res.partner.relation.all,any_partner_id:0 msgid "Partner" -msgstr "Partner" - -#. module: partner_relations -#: view:res.partner.relation:partner_relations.form_res_partner_relation -msgid "Partner Relation" -msgstr "" +msgstr "Relatie" #. module: partner_relations #: model:ir.model,name:partner_relations.model_res_partner_relation_type msgid "Partner Relation Type" -msgstr "" +msgstr "Type connectie" #. module: partner_relations -#: view:res.partner.relation:partner_relations.tree_res_partner_relation #: view:res.partner.relation.all:partner_relations.tree_res_partner_relation_all msgid "Partner Relations" -msgstr "" +msgstr "Connecties" #. 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 Types" -msgstr "" - -#. module: partner_relations -#: field:res.partner.relation.all,contact_type:0 -msgid "Partner Type" -msgstr "" +msgstr "Connectietypes" #. module: partner_relations #: model:ir.model,name:partner_relations.model_res_partner_relation -#: view:res.partner.relation.all:partner_relations.form_res_partner_relation_all -#: view:res.partner.relation.type:partner_relations.form_res_partner_relation_type -#: view:res.partner.relation.type:partner_relations.tree_res_partner_relation_type msgid "Partner relation" -msgstr "" +msgstr "Connectie" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation.py:301 +#: code:addons/partner_relations/models/res_partner_relation.py:115 #, python-format msgid "Partners cannot have a relation with themselves." -msgstr "" +msgstr "Relaties kunnen geen connectie met zichzelf hebben" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation_type.py:69 +#: code:addons/partner_relations/models/res_partner_relation_type.py:83 #, python-format msgid "Person" -msgstr "" +msgstr "Persoon" + +#. module: partner_relations +#: view:res.partner.relation.type:partner_relations.form_res_partner_relation_type +msgid "Properties" +msgstr "Eigenschappen" #. module: partner_relations #: field:res.partner.relation.all,record_type:0 msgid "Record Type" -msgstr "" +msgstr "Recordtype" #. module: partner_relations -#: field:res.partner.relation.type.selection,record_type:0 -msgid "Record type" -msgstr "" +#: help:res.partner.relation.all,active:0 +msgid "Records with date_end in the past are inactive" +msgstr "Connecties met een datum in het verleden zijn inactief" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation.py:370 -#, python-format -msgid "Related partners" -msgstr "" +#: field:res.partner.relation.type,allow_self:0 +#: field:res.partner.relation.type.selection,allow_self:0 +msgid "Reflexive" +msgstr "Wederkerig" #. module: partner_relations #: field:res.partner.relation.all,relation_id:0 msgid "Relation" -msgstr "" +msgstr "Connectie" #. module: partner_relations #: field:res.partner,relation_count:0 msgid "Relation Count" -msgstr "" +msgstr "Aantal connecties" #. module: partner_relations -#: field:res.partner.relation.all,type_id:0 #: field:res.partner.relation.all,type_selection_id:0 msgid "Relation Type" -msgstr "" +msgstr "Type connectie" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:252 +#, python-format +msgid "Relation type incompatible with selected partner(s)." +msgstr "Type connectie komt niet overeen met geselecteerde relatie(s)." #. module: partner_relations #: field:res.partner,search_relation_date:0 msgid "Relation valid" -msgstr "" +msgstr "Geldige connectie" #. module: partner_relations -#: model:ir.actions.act_window,name:partner_relations.action_res_partner_relation #: model:ir.actions.act_window,name:partner_relations.action_res_partner_relation_all #: model:ir.ui.menu,name:partner_relations.menu_res_partner_relation_sales #: view:res.partner:partner_relations.view_partner_form -#: field:res.partner,relation_ids:0 msgid "Relations" -msgstr "" +msgstr "Connecties" #. module: partner_relations -#: view:res.partner.relation:partner_relations.search_res_partner_relation #: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all msgid "Relationship Type" -msgstr "" - -#. module: partner_relations -#: view:res.partner.relation:partner_relations.search_res_partner_relation -msgid "Right Partner" -msgstr "" - -#. module: partner_relations -#: field:res.partner.relation,right_contact_type:0 -msgid "Right Partner Type" -msgstr "" +msgstr "Type connectie" #. module: partner_relations #: field:res.partner.relation.type,partner_category_right:0 msgid "Right partner category" -msgstr "" +msgstr "Categorie rechterrelatie" + +#. module: partner_relations +#: selection:res.partner.relation.all,record_type:0 +msgid "Right partner to left partner" +msgstr "Rechter naar linkerrelatie" #. module: partner_relations #: field:res.partner.relation.type,contact_type_right:0 msgid "Right partner type" -msgstr "" +msgstr "Type rechterrelatie" #. module: partner_relations #: view:res.partner.relation.type:partner_relations.form_res_partner_relation_type msgid "Right side of relation" -msgstr "" +msgstr "Rechterkant van de connectie" + +#. module: partner_relations +#: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all +msgid "Right to left" +msgstr "Rechts naar links" #. module: partner_relations -#: view:res.partner.relation:partner_relations.search_res_partner_relation #: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all msgid "Search Relations" -msgstr "" +msgstr "Zoek connecties" #. module: partner_relations #: model:ir.actions.act_window,name:partner_relations.action_show_partner_relations msgid "Show partner's relations" -msgstr "" - -#. module: partner_relations -#: model:ir.actions.server,name:partner_relations.action_show_right_relation_partners -msgid "Show partners" -msgstr "" +msgstr "Toon connecties van relatie" #. module: partner_relations #: field:res.partner.relation,left_partner_id:0 msgid "Source Partner" -msgstr "" +msgstr "Bron relatie" #. module: partner_relations #: field:res.partner.relation,date_start:0 #: field:res.partner.relation.all,date_start:0 msgid "Starting date" -msgstr "" +msgstr "Datum ingang" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation.py:287 +#: field:res.partner.relation.type,is_symmetric:0 +#: field:res.partner.relation.type.selection,is_symmetric:0 +msgid "Symmetric" +msgstr "Symmetrisch" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation.py:101 +#, fuzzy, python-format +msgid "The %s partner does not have category %s." +msgstr "De %s relatie is niet geldig voor dit type connectie." + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation.py:95 #, python-format msgid "The %s partner is not applicable for this relation type." -msgstr "" +msgstr "De %s relatie is niet geldig voor dit type connectie." #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation.py:254 +#: code:addons/partner_relations/models/res_partner_relation.py:61 #, python-format msgid "The starting date cannot be after the ending date." +msgstr "De ingangsdatum kan niet na de einddatum liggen." + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_type.py:168 +#, python-format +msgid "" +"There are already relations not satisfying the conditions for partner type " +"or category." msgstr "" +"Er zijn al connecties die niet voldoen aan de criteria voor type relatie of " +"categorie." #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation.py:329 +#: code:addons/partner_relations/models/res_partner_relation.py:154 #, python-format msgid "There is already a similar relation with overlapping dates" +msgstr "Er is al een gelijkaardige connectie met overlappende geldigheid" + +#. module: partner_relations +#: help:res.partner.relation.type,allow_self:0 +msgid "This relation can be set up with the same partner left and right" +msgstr "Deze connectie kan een relatie met zichzelf verbinden" + +#. module: partner_relations +#: help:res.partner.relation.type,is_symmetric:0 +msgid "This relation is the same from right to left as from left to right" msgstr "" +"Deze connectie is van rechts naar links hetzelfde als van links naar rechts" #. 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 "" +msgstr "Type" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner.py:122 +#: code:addons/partner_relations/models/res_partner.py:80 +#: code:addons/partner_relations/models/res_partner.py:121 #, python-format -msgid "Unsupported search operand \"%s\"" +msgid "Unsupported search operator \"%s\"" +msgstr "Zoek operator \"%s\" wordt niet ondersteund " + +#. module: partner_relations +#: help:res.partner.relation.type,handle_invalid_onchange:0 +msgid "" +"When adding relations criteria like partner type and category are checked.\n" +"However when you change the criteria, there might be relations that do not " +"fit the new criteria.\n" +"Specify how this situation should be handled." msgstr "" +"Bij het aanmaken van connecties vinden controles plaats op type en categorie " +"van de relatie.\n" +"Echter, wanneer u de criteria verandert, dan kunnen er al connecties bestaan " +"die daar niet aan voldoen.\n" +"Geef aan hoe zo'n situatie moet worden afgehandeld. " + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:221 +#, python-format +msgid "other" +msgstr "andere" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:215 +#, python-format +msgid "this" +msgstr "deze" + +#~ msgid "Company" +#~ msgstr "Bedrijf" diff --git a/partner_relations/i18n/partner_relations.pot b/partner_relations/i18n/partner_relations.pot index 9b700725f..6bb4b07ea 100644 --- a/partner_relations/i18n/partner_relations.pot +++ b/partner_relations/i18n/partner_relations.pot @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 8.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-04-15 16:09+0000\n" -"PO-Revision-Date: 2015-04-15 16:09+0000\n" +"POT-Creation-Date: 2016-09-02 12:39+0000\n" +"PO-Revision-Date: 2016-09-02 12:39+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -16,16 +16,22 @@ msgstr "" "Plural-Forms: \n" #. module: partner_relations -#: model:ir.actions.act_window,help:partner_relations.action_res_partner_relation +#: code:addons/partner_relations/models/res_partner_relation_all.py:178 +#, python-format +msgid "%s partner incompatible with relation type." +msgstr "" + +#. module: partner_relations #: model:ir.actions.act_window,help:partner_relations.action_res_partner_relation_all msgid "

\n" -" Record and track your partners' relations. Relations may be linked to other partners with a type either directly or inversely.\n" +" Record and track your partners' relations. Relations may\n" +" be linked to other partners with a type either directly\n" +" or inversely.\n" "

\n" " " msgstr "" #. module: partner_relations -#: field:res.partner.relation,active:0 #: field:res.partner.relation.all,active:0 msgid "Active" msgstr "" @@ -46,9 +52,10 @@ msgid "All relations with current partner" msgstr "" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation_type.py:64 +#: code:addons/partner_relations/models/res_partner_relation_type.py:13 +#: selection:res.partner.relation.type,handle_invalid_onchange:0 #, python-format -msgid "Company" +msgid "Allow existing relations that do not fit changed conditions" msgstr "" #. module: partner_relations @@ -63,14 +70,8 @@ msgstr "" msgid "Created on" 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 "" @@ -79,11 +80,40 @@ msgstr "" msgid "Current record's partner type" msgstr "" +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_type.py:17 +#: selection:res.partner.relation.type,handle_invalid_onchange:0 +#, python-format +msgid "Delete relations that do not fit changed conditions" +msgstr "" + #. module: partner_relations #: field:res.partner.relation,right_partner_id:0 msgid "Destination Partner" msgstr "" +#. module: partner_relations +#: field:res.partner.relation,display_name:0 +#: field:res.partner.relation.all,display_name:0 +#: field:res.partner.relation.type,display_name:0 +#: field:res.partner.relation.type.selection,display_name:0 +msgid "Display Name" +msgstr "" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_type.py:11 +#: selection:res.partner.relation.type,handle_invalid_onchange:0 +#, python-format +msgid "Do not allow change that will result in invalid relations" +msgstr "" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_type.py:15 +#: selection:res.partner.relation.type,handle_invalid_onchange:0 +#, python-format +msgid "End relations per today, if they do not fit changed conditions" +msgstr "" + #. module: partner_relations #: field:res.partner.relation,date_end:0 #: field:res.partner.relation.all,date_end:0 @@ -91,13 +121,29 @@ msgid "Ending date" msgstr "" #. module: partner_relations -#: view:res.partner.relation:partner_relations.search_res_partner_relation +#: code:addons/partner_relations/models/res_partner_relation_all.py:184 +#: code:addons/partner_relations/models/res_partner_relation_all.py:259 +#, python-format +msgid "Error!" +msgstr "" + +#. module: partner_relations #: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all msgid "Group By" msgstr "" #. module: partner_relations -#: field:res.partner,search_relation_id:0 +#: model:res.partner.relation.type,name_inverse:partner_relations.rel_type_assistant +msgid "Has assistant" +msgstr "" + +#. module: partner_relations +#: model:res.partner.relation.type,name_inverse:partner_relations.rel_type_has_worked_for +msgid "Has former employee" +msgstr "" + +#. module: partner_relations +#: field:res.partner,search_relation_type_id:0 msgid "Has relation of type" msgstr "" @@ -111,6 +157,11 @@ msgstr "" msgid "Has relation with a partner in category" msgstr "" +#. module: partner_relations +#: model:res.partner.relation.type,name:partner_relations.rel_type_has_worked_for +msgid "Has worked for" +msgstr "" + #. module: partner_relations #: field:res.partner.relation,id:0 #: field:res.partner.relation.all,id:0 @@ -119,15 +170,48 @@ msgstr "" msgid "ID" msgstr "" +#. module: partner_relations +#: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all +msgid "Include past records" +msgstr "" + +#. module: partner_relations +#: field:res.partner.relation.type,handle_invalid_onchange:0 +msgid "Invalid relation handling" +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" +#: help:res.partner.relation.type.selection,is_inverse:0 +msgid "Inverse relations are from right to left partner." +msgstr "" + +#. module: partner_relations +#: model:res.partner.relation.type,name:partner_relations.rel_type_assistant +msgid "Is assistant of" +msgstr "" + +#. module: partner_relations +#: model:res.partner.relation.type,name:partner_relations.rel_type_competitor +#: model:res.partner.relation.type,name_inverse:partner_relations.rel_type_competitor +msgid "Is competitor of" +msgstr "" + +#. module: partner_relations +#: field:res.partner.relation.type.selection,is_inverse:0 +msgid "Is reverse type?" +msgstr "" + +#. module: partner_relations +#: field:res.partner.relation,__last_update:0 +#: field:res.partner.relation.all,__last_update:0 +#: field:res.partner.relation.type,__last_update:0 +#: field:res.partner.relation.type.selection,__last_update:0 +msgid "Last Modified on" msgstr "" #. module: partner_relations @@ -143,18 +227,13 @@ msgid "Last Updated on" msgstr "" #. module: partner_relations -#: view:res.partner.relation:partner_relations.search_res_partner_relation -msgid "Left Partner" -msgstr "" - -#. module: partner_relations -#: field:res.partner.relation,left_contact_type:0 -msgid "Left Partner Type" +#: field:res.partner.relation.type,partner_category_left:0 +msgid "Left partner category" msgstr "" #. module: partner_relations -#: field:res.partner.relation.type,partner_category_left:0 -msgid "Left partner category" +#: selection:res.partner.relation.all,record_type:0 +msgid "Left partner to right partner" msgstr "" #. module: partner_relations @@ -167,12 +246,41 @@ msgstr "" msgid "Left side of relation" msgstr "" +#. module: partner_relations +#: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all +msgid "Left to right" +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 +#: code:addons/partner_relations/models/res_partner_relation_all.py:181 +#, python-format +msgid "No %s partner available for relation type." +msgstr "" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:256 +#, python-format +msgid "No relation type available for selected partners." +msgstr "" + +#. module: partner_relations +#: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all +#: field:res.partner.relation.all,this_partner_id:0 +msgid "One Partner" +msgstr "" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_type.py:82 +#, python-format +msgid "Organisation" +msgstr "" + #. module: partner_relations #: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all #: field:res.partner.relation.all,other_partner_id:0 @@ -191,22 +299,16 @@ msgstr "" #. module: partner_relations #: model:ir.model,name:partner_relations.model_res_partner -#: field:res.partner.relation,partner_id_display:0 +#: field:res.partner.relation.all,any_partner_id:0 msgid "Partner" msgstr "" -#. module: partner_relations -#: view:res.partner.relation:partner_relations.form_res_partner_relation -msgid "Partner Relation" -msgstr "" - #. module: partner_relations #: model:ir.model,name:partner_relations.model_res_partner_relation_type msgid "Partner Relation Type" msgstr "" #. module: partner_relations -#: view:res.partner.relation:partner_relations.tree_res_partner_relation #: view:res.partner.relation.all:partner_relations.tree_res_partner_relation_all msgid "Partner Relations" msgstr "" @@ -217,45 +319,42 @@ msgstr "" msgid "Partner Relations Types" msgstr "" -#. module: partner_relations -#: field:res.partner.relation.all,contact_type:0 -msgid "Partner Type" -msgstr "" - #. module: partner_relations #: model:ir.model,name:partner_relations.model_res_partner_relation -#: view:res.partner.relation.all:partner_relations.form_res_partner_relation_all -#: view:res.partner.relation.type:partner_relations.form_res_partner_relation_type -#: view:res.partner.relation.type:partner_relations.tree_res_partner_relation_type msgid "Partner relation" msgstr "" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation.py:291 +#: code:addons/partner_relations/models/res_partner_relation.py:115 #, python-format msgid "Partners cannot have a relation with themselves." msgstr "" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation_type.py:65 +#: code:addons/partner_relations/models/res_partner_relation_type.py:83 #, python-format msgid "Person" msgstr "" +#. module: partner_relations +#: view:res.partner.relation.type:partner_relations.form_res_partner_relation_type +msgid "Properties" +msgstr "" + #. module: partner_relations #: field:res.partner.relation.all,record_type:0 msgid "Record Type" msgstr "" #. module: partner_relations -#: field:res.partner.relation.type.selection,record_type:0 -msgid "Record type" +#: help:res.partner.relation.all,active:0 +msgid "Records with date_end in the past are inactive" msgstr "" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation.py:360 -#, python-format -msgid "Related partners" +#: field:res.partner.relation.type,allow_self:0 +#: field:res.partner.relation.type.selection,allow_self:0 +msgid "Reflexive" msgstr "" #. module: partner_relations @@ -264,44 +363,46 @@ msgid "Relation" msgstr "" #. module: partner_relations -#: field:res.partner.relation.all,type_id:0 +#: field:res.partner,relation_count:0 +msgid "Relation Count" +msgstr "" + +#. module: partner_relations #: field:res.partner.relation.all,type_selection_id:0 msgid "Relation Type" msgstr "" +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:252 +#, python-format +msgid "Relation type incompatible with selected partner(s)." +msgstr "" + #. module: partner_relations #: field:res.partner,search_relation_date:0 msgid "Relation valid" msgstr "" #. module: partner_relations -#: model:ir.actions.act_window,name:partner_relations.action_res_partner_relation #: model:ir.actions.act_window,name:partner_relations.action_res_partner_relation_all #: model:ir.ui.menu,name:partner_relations.menu_res_partner_relation_sales #: view:res.partner:partner_relations.view_partner_form -#: field:res.partner,relation_ids:0 msgid "Relations" msgstr "" #. module: partner_relations -#: view:res.partner.relation:partner_relations.search_res_partner_relation #: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all msgid "Relationship Type" msgstr "" #. module: partner_relations -#: view:res.partner.relation:partner_relations.search_res_partner_relation -msgid "Right Partner" -msgstr "" - -#. module: partner_relations -#: field:res.partner.relation,right_contact_type:0 -msgid "Right Partner Type" +#: field:res.partner.relation.type,partner_category_right:0 +msgid "Right partner category" msgstr "" #. module: partner_relations -#: field:res.partner.relation.type,partner_category_right:0 -msgid "Right partner category" +#: selection:res.partner.relation.all,record_type:0 +msgid "Right partner to left partner" msgstr "" #. module: partner_relations @@ -315,19 +416,18 @@ msgid "Right side of relation" msgstr "" #. module: partner_relations -#: view:res.partner.relation:partner_relations.search_res_partner_relation #: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all -msgid "Search Relations" +msgid "Right to left" msgstr "" #. module: partner_relations -#: model:ir.actions.act_window,name:partner_relations.action_show_partner_relations -msgid "Show partner's relations" +#: view:res.partner.relation.all:partner_relations.search_res_partner_relation_all +msgid "Search Relations" msgstr "" #. module: partner_relations -#: model:ir.actions.server,name:partner_relations.action_show_right_relation_partners -msgid "Show partners" +#: model:ir.actions.act_window,name:partner_relations.action_show_partner_relations +msgid "Show partner's relations" msgstr "" #. module: partner_relations @@ -342,35 +442,80 @@ msgid "Starting date" msgstr "" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation.py:278 +#: field:res.partner.relation.type,is_symmetric:0 +#: field:res.partner.relation.type.selection,is_symmetric:0 +msgid "Symmetric" +msgstr "" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation.py:101 +#, python-format +msgid "The %s partner does not have category %s." +msgstr "" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation.py:95 #, python-format msgid "The %s partner is not applicable for this relation type." msgstr "" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation.py:245 +#: code:addons/partner_relations/models/res_partner_relation.py:61 #, python-format msgid "The starting date cannot be after the ending date." msgstr "" #. module: partner_relations -#: code:addons/partner_relations/model/res_partner_relation.py:319 +#: code:addons/partner_relations/models/res_partner_relation_type.py:168 +#, python-format +msgid "There are already relations not satisfying the conditions for partner type or category." +msgstr "" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation.py:154 #, python-format msgid "There is already a similar relation with overlapping dates" msgstr "" +#. module: partner_relations +#: help:res.partner.relation.type,allow_self:0 +msgid "This relation can be set up with the same partner left and right" +msgstr "" + +#. module: partner_relations +#: help:res.partner.relation.type,is_symmetric:0 +msgid "This relation is the same from right to left as from left to right" +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 -#: code:addons/partner_relations/model/res_partner.py:109 +#: code:addons/partner_relations/models/res_partner.py:80 +#: code:addons/partner_relations/models/res_partner.py:121 +#, python-format +msgid "Unsupported search operator \"%s\"" +msgstr "" + +#. module: partner_relations +#: help:res.partner.relation.type,handle_invalid_onchange:0 +msgid "When adding relations criteria like partner type and category are checked.\n" +"However when you change the criteria, there might be relations that do not fit the new criteria.\n" +"Specify how this situation should be handled." +msgstr "" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:221 +#, python-format +msgid "other" +msgstr "" + +#. module: partner_relations +#: code:addons/partner_relations/models/res_partner_relation_all.py:215 #, python-format -msgid "Unsupported search operand \"%s\"" +msgid "this" msgstr "" diff --git a/partner_relations/i18n/pt_BR.po b/partner_relations/i18n/pt_BR.po index f1b3c44a5..4f78272a3 100644 --- a/partner_relations/i18n/pt_BR.po +++ b/partner_relations/i18n/pt_BR.po @@ -125,7 +125,7 @@ msgid "Has former employee" msgstr "Tem ex-empregado" #. module: partner_relations -#: field:res.partner,search_relation_id:0 +#: field:res.partner,search_relation_type_id:0 msgid "Has relation of type" msgstr "Tem relação do tipo" diff --git a/partner_relations/i18n/sl.po b/partner_relations/i18n/sl.po index 39f3694ac..0f4fc9b64 100644 --- a/partner_relations/i18n/sl.po +++ b/partner_relations/i18n/sl.po @@ -124,7 +124,7 @@ msgid "Has former employee" msgstr "Ima bivšega zaposlenega" #. module: partner_relations -#: field:res.partner,search_relation_id:0 +#: field:res.partner,search_relation_type_id:0 msgid "Has relation of type" msgstr "Ima odnos tipa" diff --git a/partner_relations/model/__init__.py b/partner_relations/model/__init__.py deleted file mode 100644 index a493a9d0e..000000000 --- a/partner_relations/model/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- 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 . -# -############################################################################## - -from . import res_partner -from . import res_partner_relation -from . import res_partner_relation_type -from . import res_partner_relation_all -from . import res_partner_relation_type_selection diff --git a/partner_relations/model/res_partner.py b/partner_relations/model/res_partner.py deleted file mode 100644 index 514b8101a..000000000 --- a/partner_relations/model/res_partner.py +++ /dev/null @@ -1,339 +0,0 @@ -# -*- 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 import osv, models, fields, exceptions, api -from openerp.osv.expression import is_leaf, AND, OR, FALSE_LEAF -from openerp.tools import DEFAULT_SERVER_DATE_FORMAT -from openerp.tools.translate import _ - -PADDING = 10 - - -def get_partner_type(partner): - """Get partner type for relation. - - :param partner: a res.partner either a company or not - :return: 'c' for company or 'p' for person - :rtype: str - """ - return 'c' if partner.is_company else 'p' - - -class ResPartner(models.Model): - _inherit = 'res.partner' - - relation_count = fields.Integer( - 'Relation Count', - compute="_count_relations" - ) - - @api.one - @api.depends("relation_ids") - def _count_relations(self): - """Count the number of relations this partner has for Smart Button - - Don't count inactive relations. - """ - self.relation_count = len([r for r in self.relation_ids if r.active]) - - def _get_relation_ids_select(self, cr, uid, ids, field_name, arg, - context=None): - '''return the partners' relations as tuple - (id, left_partner_id, right_partner_id)''' - 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)) - ) - return cr.fetchall() - - def _get_relation_ids( - self, cr, uid, ids, field_name, arg, context=None): - '''getter for relation_ids''' - if context is None: - context = {} - result = dict([(i, []) for i in ids]) - # TODO: do a permission test on returned ids - for row in self._get_relation_ids_select( - cr, uid, ids, field_name, arg, context=context): - 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): - '''setter for relation_ids''' - if context is None: - context = {} - relation_obj = self.pool.get('res.partner.relation') - context2 = self.with_partner_relations_context( - cr, uid, ids, context=context).env.context - for value in field_value: - if value[0] == 0: - relation_obj.create(cr, uid, value[2], context=context2) - if value[0] == 1: - # if we write partner_id_display, we also need to pass - # type_selection_id in order to have this write end up on - # the correct field - if 'partner_id_display' in value[2] and 'type_selection_id'\ - not in value[2]: - relation_data = relation_obj.read( - cr, uid, [value[1]], ['type_selection_id'], - context=context)[0] - value[2]['type_selection_id'] =\ - relation_data['type_selection_id'] - 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] not in ['=', '!=', 'like', 'not like', 'ilike', - 'not ilike', 'in', 'not in']: - raise exceptions.ValidationError( - _('Unsupported search operand "%s"') % arg[1]) - - relation_type_selection_ids = [] - relation_type_selection = self\ - .pool['res.partner.relation.type.selection'] - - if arg[1] == '=' and isinstance(arg[2], (long, int)): - relation_type_selection_ids.append(arg[2]) - elif arg[1] == '!=' and isinstance(arg[2], (long, int)): - type_id, is_inverse = ( - relation_type_selection.browse(cr, uid, arg[2], - context=context) - .get_type_from_selection_id() - ) - result = OR([ - result, - [ - ('relation_all_ids.type_id', '!=', type_id), - ] - ]) - continue - else: - relation_type_selection_ids = relation_type_selection\ - .search( - cr, uid, - [ - ('type_id.name', arg[1], arg[2]), - ('record_type', '=', 'a'), - ], - context=context) - relation_type_selection_ids.extend( - relation_type_selection.search( - cr, uid, - [ - ('type_id.name_inverse', arg[1], arg[2]), - ('record_type', '=', 'b'), - ], - context=context)) - - if not relation_type_selection_ids: - result = AND([result, [FALSE_LEAF]]) - - for relation_type_selection_id in relation_type_selection_ids: - type_id, is_inverse = ( - relation_type_selection.browse( - cr, uid, relation_type_selection_id, - context=context - ).get_type_from_selection_id() - ) - - result = OR([ - result, - [ - '&', - ('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': osv.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': osv.fields.one2many( - 'res.partner.relation.all', 'this_partner_id', - string='All relations with current partner', - auto_join=True, - selectable=False, - ), - 'search_relation_id': osv.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': osv.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': osv.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': osv.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) - - @api.v7 - def read(self, cr, user, ids, fields=None, context=None, - load='_classic_read'): - return super(ResPartner, self).read( - cr, user, ids, fields=fields, context=context, load=load) - - @api.v8 - def read(self, fields=None, load='_classic_read'): - return super(ResPartner, self.with_partner_relations_context())\ - .read(fields=fields, load=load) - - @api.multi - def write(self, vals): - return super(ResPartner, self.with_partner_relations_context())\ - .write(vals) - - @api.multi - def with_partner_relations_context(self): - context = dict(self.env.context) - if context.get('active_model', self._name) == self._name: - existing = self.exists() - context.setdefault( - 'active_id', existing.ids[0] if existing.ids else None) - context.setdefault('active_ids', existing.ids) - context.setdefault('active_model', self._name) - return self.with_context(context) diff --git a/partner_relations/model/res_partner_relation.py b/partner_relations/model/res_partner_relation.py deleted file mode 100644 index 448b02889..000000000 --- a/partner_relations/model/res_partner_relation.py +++ /dev/null @@ -1,376 +0,0 @@ -# -*- 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 import osv, models, fields, api, exceptions, _ - -from .res_partner import get_partner_type - - -class ResPartnerRelation(models.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 _search_any_partner_id(self, operator, value): - return [ - '|', - ('left_partner_id', operator, value), - ('right_partner_id', operator, value), - ] - - 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(self, dummy_field_names, dummy_arg, context=None): - '''Get computed values for record''' - values = {} - on_right_partner = self._on_right_partner(self.right_partner_id.id) - # type_selection_id - values['type_selection_id'] = ( - ((self.type_id.id) * 10) + (on_right_partner and 1 or 0)) - # partner_id_display - values['partner_id_display'] = ( - self.left_partner_id.id - if on_right_partner - else self.right_partner_id.id - ) - return values - - return dict([ - (i.id, get_values(i, field_names, arg, context=context)) - for i in self.browse(cr, uid, ids, context=context) - ]) - - _columns = { - 'type_selection_id': osv.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': osv.fields.function( - _get_computed_fields, - multi="computed_fields", - fnct_inv=lambda *args: None, - type='many2one', obj='res.partner', - string='Partner' - ), - } - - allow_self = fields.Boolean(related='type_id.allow_self') - left_contact_type = fields.Selection( - lambda s: s.env['res.partner.relation.type']._get_partner_types(), - 'Left Partner Type', - compute='_get_partner_type_any', - store=True, - ) - - right_contact_type = fields.Selection( - lambda s: s.env['res.partner.relation.type']._get_partner_types(), - 'Right Partner Type', - compute='_get_partner_type_any', - store=True, - ) - - any_partner_id = fields.Many2many( - 'res.partner', - string='Partner', - compute='_get_partner_type_any', - search='_search_any_partner_id' - ) - - left_partner_id = fields.Many2one( - 'res.partner', - string='Source Partner', - required=True, - auto_join=True, - ondelete='cascade', - ) - - right_partner_id = fields.Many2one( - 'res.partner', - string='Destination 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') - active = fields.Boolean('Active', default=True) - - @api.one - @api.depends('left_partner_id', 'right_partner_id') - def _get_partner_type_any(self): - self.left_contact_type = get_partner_type(self.left_partner_id) - self.right_contact_type = get_partner_type(self.right_partner_id) - - self.any_partner_id = self.left_partner_id + self.right_partner_id - - 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, vals): - """Fill type and left and right partner id, according to whether - 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.env['res.partner.relation.type.selection'] - type_selection_id = vals['type_selection_id'] - (type_id, is_reverse) = ( - prts_model.browse(type_selection_id). - get_type_from_selection_id() - ) - vals['type_id'] = type_id - if self._context.get('active_id'): - if is_reverse: - vals['right_partner_id'] = self._context['active_id'] - else: - vals['left_partner_id'] = self._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'] - if vals.get('other_partner_id'): - if is_reverse: - vals['left_partner_id'] = vals['other_partner_id'] - else: - vals['right_partner_id'] = vals['other_partner_id'] - del vals['other_partner_id'] - if vals.get('contact_type'): - del vals['contact_type'] - return vals - - @api.multi - def write(self, vals): - """Override write to correct values, before being stored.""" - vals = self._correct_vals(vals) - return super(ResPartnerRelation, self).write(vals) - - @api.model - def create(self, vals): - """Override create to correct values, before being stored.""" - vals = self._correct_vals(vals) - return super(ResPartnerRelation, self).create(vals) - - 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 - - @api.one - @api.constrains('date_start', 'date_end') - def _check_dates(self): - """End date should not be before start date, if not filled - - :raises exceptions.Warning: When constraint is violated - """ - if (self.date_start and self.date_end and - self.date_start > self.date_end): - raise exceptions.Warning( - _('The starting date cannot be after the ending date.') - ) - - @api.one - @api.constrains('left_partner_id', 'type_id') - def _check_partner_type_left(self): - """Check left partner for required company or person - - :raises exceptions.Warning: When constraint is violated - """ - self._check_partner_type("left") - - @api.one - @api.constrains('right_partner_id', 'type_id') - def _check_partner_type_right(self): - """Check right partner for required company or person - - :raises exceptions.Warning: When constraint is violated - """ - self._check_partner_type("right") - - @api.one - def _check_partner_type(self, side): - """Check partner to left or right for required company or person - - :param str side: left or right - :raises exceptions.Warning: When constraint is violated - """ - assert side in ['left', 'right'] - ptype = getattr(self.type_id, "contact_type_%s" % side) - company = getattr(self, '%s_partner_id' % side).is_company - if (ptype == 'c' and not company) or (ptype == 'p' and company): - raise exceptions.Warning( - _('The %s partner is not applicable for this relation type.') % - side - ) - - @api.one - @api.constrains('left_partner_id', 'right_partner_id') - def _check_not_with_self(self): - """Not allowed to link partner to same partner - - :raises exceptions.Warning: When constraint is violated - """ - if self.left_partner_id == self.right_partner_id: - if not self.allow_self: - raise exceptions.Warning( - _('Partners cannot have a relation with themselves.') - ) - - @api.one - @api.constrains('left_partner_id', 'right_partner_id', 'active') - def _check_relation_uniqueness(self): - """Forbid multiple active relations of the same type between the same - partners - - :raises exceptions.Warning: When constraint is violated - """ - if not self.active: - return - domain = [ - ('type_id', '=', self.type_id.id), - ('active', '=', True), - ('id', '!=', self.id), - ('left_partner_id', '=', self.left_partner_id.id), - ('right_partner_id', '=', self.right_partner_id.id), - ] - if self.date_start: - domain += ['|', ('date_end', '=', False), - ('date_end', '>=', self.date_start)] - if self.date_end: - domain += ['|', ('date_start', '=', False), - ('date_start', '<=', self.date_end)] - if self.search(domain): - raise exceptions.Warning( - _('There is already a similar relation with overlapping dates') - ) - - 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 deleted file mode 100644 index 59bc12bc7..000000000 --- a/partner_relations/model/res_partner_relation_all.py +++ /dev/null @@ -1,237 +0,0 @@ -# -*- 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 import models, fields, api -from openerp.tools import drop_view_if_exists -from .res_partner_relation_type_selection import \ - ResPartnerRelationTypeSelection -from .res_partner import get_partner_type, PADDING - - -class ResPartnerRelationAll(models.AbstractModel): - _auto = False - _log_access = False - _name = 'res.partner.relation.all' - _overlays = 'res.partner.relation' - _description = 'All (non-inverse + inverse) relations between partners' - - _additional_view_fields = [] - '''append to this list if you added fields to res_partner_relation that - you need in this model and related fields are not adequate (ie for sorting) - You must use the same name as in res_partner_relation. - Don't overwrite this list in your declaration but append in _auto_init: - - def _auto_init(self, cr, context=None): - self._additional_view_fields.append('my_field') - return super(ResPartnerRelationAll, self)._auto_init( - cr, context=context) - - my_field = fields... - ''' - - this_partner_id = fields.Many2one( - 'res.partner', - string='Current Partner', - required=True, - ) - - other_partner_id = fields.Many2one( - 'res.partner', - string='Other Partner', - required=True, - ) - - type_id = fields.Many2one( - 'res.partner.relation.type', - string='Relation Type', - required=True, - ) - - type_selection_id = fields.Many2one( - 'res.partner.relation.type.selection', - string='Relation Type', - required=True, - ) - - relation_id = fields.Many2one( - 'res.partner.relation', - 'Relation', - readonly=True, - ) - - record_type = fields.Selection( - ResPartnerRelationTypeSelection._RECORD_TYPES, - 'Record Type', - readonly=True, - ) - - contact_type = fields.Selection( - lambda s: s.env['res.partner.relation.type']._get_partner_types(), - 'Partner Type', - default=lambda self: self._get_default_contact_type() - ) - - date_start = fields.Date('Starting date') - date_end = fields.Date('Ending date') - active = fields.Boolean('Active', default=True) - - def _auto_init(self, cr, context=None): - drop_view_if_exists(cr, self._table) - additional_view_fields = ','.join(self._additional_view_fields) - additional_view_fields = (',' + additional_view_fields)\ - if additional_view_fields else '' - cr.execute( - '''create or replace view %(table)s as - select - id * %(padding)d as id, - id as relation_id, - type_id, - cast('a' as char(1)) as record_type, - left_contact_type as contact_type, - left_partner_id as this_partner_id, - right_partner_id as other_partner_id, - date_start, - date_end, - active, - type_id * %(padding)d as type_selection_id - %(additional_view_fields)s - from %(underlying_table)s - union select - id * %(padding)d + 1, - id, - type_id, - cast('b' as char(1)), - right_contact_type, - right_partner_id, - left_partner_id, - date_start, - date_end, - active, - type_id * %(padding)d + 1 - %(additional_view_fields)s - from %(underlying_table)s''' % { - 'table': self._table, - 'padding': PADDING, - 'additional_view_fields': additional_view_fields, - 'underlying_table': 'res_partner_relation', - } - ) - - return super(ResPartnerRelationAll, self)._auto_init( - cr, context=context) - - def _get_underlying_object(self): - """Get the record on which this record is overlaid""" - return self.env[self._overlays].browse(self.id / PADDING) - - def _get_default_contact_type(self): - partner_id = self._context.get('default_this_partner_id') - if partner_id: - partner = self.env['res.partner'].browse(partner_id) - return get_partner_type(partner) - return False - - @api.multi - def name_get(self): - return { - 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 - } - - @api.onchange('type_selection_id') - def onchange_type_selection_id(self): - """Add domain on other_partner_id according to category_other and - contact_type_other""" - domain = [] - if self.type_selection_id.contact_type_other: - domain.append( - ('is_company', '=', - self.type_selection_id.contact_type_other == 'c')) - if self.type_selection_id.partner_category_other: - domain.append( - ('category_id', 'in', - self.type_selection_id.partner_category_other.ids)) - return { - 'domain': { - 'other_partner_id': domain, - } - } - - @api.onchange('this_partner_id') - def onchange_this_partner_id(self): - if not self.this_partner_id: - return {'domain': {'type_selection_id': []}} - return { - 'domain': { - 'type_selection_id': [ - '|', - ('contact_type_this', '=', False), - ('contact_type_this', '=', - 'c' if self.this_partner_id else 'p'), - '|', - ('partner_category_this', '=', False), - ('partner_category_this', 'in', - self.this_partner_id.category_id.ids), - ], - }, - } - - @api.one - def write(self, vals): - """divert non-problematic writes to underlying table""" - underlying_objs = self._get_underlying_object() - vals = { - key: val - for key, val in vals.iteritems() - if not self._columns[key].readonly - } - vals['type_selection_id'] = vals.get( - 'type_selection_id', - underlying_objs.type_selection_id.id - ) - return underlying_objs.write(vals) - - @api.model - def create(self, vals): - """divert non-problematic creates to underlying table - - Create a res.partner.relation but return the converted id - """ - vals = { - key: val - for key, val in vals.iteritems() - if not self._columns[key].readonly - } - vals['type_selection_id'] = vals.get( - 'type_selection_id', - False, - ) - res = self.env[self._overlays].create(vals) - return self.browse(res.id * PADDING) - - @api.one - def unlink(self): - """divert non-problematic creates to underlying table""" - return self._get_underlying_object().unlink() diff --git a/partner_relations/model/res_partner_relation_type.py b/partner_relations/model/res_partner_relation_type.py deleted file mode 100644 index 624ac4988..000000000 --- a/partner_relations/model/res_partner_relation_type.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- 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 import models, fields, api, _ - - -class ResPartnerRelationType(models.Model): - """Model that defines relation types that might exist between partners""" - _name = 'res.partner.relation.type' - _description = 'Partner Relation Type' - _order = 'name' - - 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', - ) - allow_self = fields.Boolean( - 'Allow both sides to be the same', - default=False, - ) - - @api.model - def _get_partner_types(self): - return [ - ('c', _('Company')), - ('p', _('Person')), - ] diff --git a/partner_relations/model/res_partner_relation_type_selection.py b/partner_relations/model/res_partner_relation_type_selection.py deleted file mode 100644 index 138989b3f..000000000 --- a/partner_relations/model/res_partner_relation_type_selection.py +++ /dev/null @@ -1,175 +0,0 @@ -# -*- 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 import api -from openerp.osv import fields -from openerp.osv import orm -from openerp.tools import drop_view_if_exists -from .res_partner_relation_type import ResPartnerRelationType -from .res_partner import PADDING - - -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 - - @api.multi - def get_type_from_selection_id(self): - """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 = self.id / PADDING - is_reverse = (self.id % PADDING) > 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 %(table)s as - select - id * %(padding)d 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 %(underlying_table)s - union select - id * %(padding)d + 1, - id, - cast('b' as char(1)), - name_inverse, - contact_type_right, - contact_type_left, - partner_category_right, - partner_category_left - from %(underlying_table)s''' % { - 'table': self._table, - 'padding': PADDING, - 'underlying_table': 'res_partner_relation_type', - }) - - 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.many2one( - 'res.partner.relation.type', '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 * PADDING, relation_ids) + - map(lambda x: x * PADDING + 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/models/__init__.py b/partner_relations/models/__init__.py new file mode 100644 index 000000000..8aef17344 --- /dev/null +++ b/partner_relations/models/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# © 2013-2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import res_partner +from . import res_partner_relation +from . import res_partner_relation_type +from . import res_partner_relation_all +from . import res_partner_relation_type_selection diff --git a/partner_relations/models/res_partner.py b/partner_relations/models/res_partner.py new file mode 100644 index 000000000..3545a299c --- /dev/null +++ b/partner_relations/models/res_partner.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +# © 2013-2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +"""Support connections between partners.""" +import numbers + +from openerp import _, api, exceptions, fields, models +from openerp.osv.expression import is_leaf, OR, FALSE_LEAF + + +class ResPartner(models.Model): + """Extend partner with relations and allow to search for relations + in various ways. + """ + # pylint: disable=invalid-name + # pylint: disable=no-member + _inherit = 'res.partner' + + relation_count = fields.Integer( + string='Relation Count', + compute="_compute_relation_count" + ) + relation_all_ids = fields.One2many( + comodel_name='res.partner.relation.all', + inverse_name='this_partner_id', + string='All relations with current partner', + auto_join=True, + selectable=False, + copy=False, + ) + search_relation_type_id = fields.Many2one( + comodel_name='res.partner.relation.type.selection', + compute=lambda self: None, + search='_search_relation_type_id', + string='Has relation of type', + ) + search_relation_partner_id = fields.Many2one( + comodel_name='res.partner', + compute=lambda self: None, + search='_search_related_partner_id', + string='Has relation with', + ) + search_relation_date = fields.Date( + compute=lambda self: None, + search='_search_relation_date', + string='Relation valid', + ) + search_relation_partner_category_id = fields.Many2one( + comodel_name='res.partner.category', + compute=lambda self: None, + search='_search_related_partner_category_id', + string='Has relation with a partner in category', + ) + + @api.depends("relation_all_ids") + def _compute_relation_count(self): + """Count the number of relations this partner has for Smart Button + + Don't count inactive relations. + """ + for rec in self: + rec.relation_count = len(rec.relation_all_ids.filtered('active')) + + @api.model + def _search_relation_type_id(self, operator, value): + """Search partners based on their type of relations.""" + result = [] + SUPPORTED_OPERATORS = ( + '=', + '!=', + 'like', + 'not like', + 'ilike', + 'not ilike', + 'in', + 'not in', + ) + if operator not in SUPPORTED_OPERATORS: + raise exceptions.ValidationError( + _('Unsupported search operator "%s"') % operator) + type_selection_model = self.env['res.partner.relation.type.selection'] + relation_type_selection = [] + if operator == '=' and isinstance(value, numbers.Integral): + relation_type_selection += type_selection_model.browse(value) + elif operator == '!=' and isinstance(value, numbers.Integral): + relation_type_selection = type_selection_model.search([ + ('id', operator, value), + ]) + else: + relation_type_selection = type_selection_model.search([ + '|', + ('type_id.name', operator, value), + ('type_id.name_inverse', operator, value), + ]) + if not relation_type_selection: + result = [FALSE_LEAF] + for relation_type in relation_type_selection: + result = OR([ + result, + [ + ('relation_all_ids.type_selection_id.id', '=', + relation_type.id), + ], + ]) + return result + + @api.model + def _search_related_partner_id(self, operator, value): + """Find partner based on relation with other partner.""" + # pylint: disable=no-self-use + return [ + ('relation_all_ids.other_partner_id', operator, value), + ] + + @api.model + def _search_relation_date(self, operator, value): + """Look only for relations valid at date of search.""" + # pylint: disable=no-self-use + return [ + '&', + '|', + ('relation_all_ids.date_start', '=', False), + ('relation_all_ids.date_start', '<=', value), + '|', + ('relation_all_ids.date_end', '=', False), + ('relation_all_ids.date_end', '>=', value), + ] + + @api.model + def _search_related_partner_category_id(self, operator, value): + """Search for partner related to a partner with search category.""" + # pylint: disable=no-self-use + return [ + ('relation_all_ids.other_partner_id.category_id', operator, value), + ] + + @api.model + def search(self, args, offset=0, limit=None, order=None, count=False): + """Inject searching for current relation date if we search for + relation properties and no explicit date was given. + """ + # pylint: disable=arguments-differ + # pylint: disable=no-value-for-parameter + 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', '=', fields.Date.today()), + ] + # because of auto_join, we have to do the active test by hand + active_args = [] + if self.env.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( + args + date_args + active_args, offset=offset, limit=limit, + order=order, count=count) + + @api.multi + def get_partner_type(self): + """Get partner type for relation. + :return: 'c' for company or 'p' for person + :rtype: str + """ + self.ensure_one() + return 'c' if self.is_company else 'p' diff --git a/partner_relations/models/res_partner_relation.py b/partner_relations/models/res_partner_relation.py new file mode 100644 index 000000000..0176fe9b2 --- /dev/null +++ b/partner_relations/models/res_partner_relation.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +# © 2013-2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +"""Store relations (connections) between partners.""" +from openerp import _, api, fields, models +from openerp.exceptions import ValidationError + + +class ResPartnerRelation(models.Model): + """Model res.partner.relation is used to describe all links or relations + between partners in the database. + + This model is actually only used to store the data. The model + res.partner.relation.all, based on a view that contains each record + two times, once for the normal relation, once for the inverse relation, + will be used to maintain the data. + """ + _name = 'res.partner.relation' + _description = 'Partner relation' + + left_partner_id = fields.Many2one( + comodel_name='res.partner', + string='Source Partner', + required=True, + auto_join=True, + ondelete='cascade', + ) + right_partner_id = fields.Many2one( + comodel_name='res.partner', + string='Destination Partner', + required=True, + auto_join=True, + ondelete='cascade', + ) + type_id = fields.Many2one( + comodel_name='res.partner.relation.type', + string='Type', + required=True, + auto_join=True, + ) + date_start = fields.Date('Starting date') + date_end = fields.Date('Ending date') + + @api.model + def create(self, vals): + """Override create to correct values, before being stored.""" + context = self.env.context + if 'left_partner_id' not in vals and context.get('active_id'): + vals['left_partner_id'] = context.get('active_id') + return super(ResPartnerRelation, self).create(vals) + + @api.one + @api.constrains('date_start', 'date_end') + def _check_dates(self): + """End date should not be before start date, if not filled + + :raises ValidationError: When constraint is violated + """ + if (self.date_start and self.date_end and + self.date_start > self.date_end): + raise ValidationError( + _('The starting date cannot be after the ending date.') + ) + + @api.one + @api.constrains('left_partner_id', 'type_id') + def _check_partner_left(self): + """Check left partner for required company or person + + :raises ValidationError: When constraint is violated + """ + self._check_partner("left") + + @api.one + @api.constrains('right_partner_id', 'type_id') + def _check_partner_right(self): + """Check right partner for required company or person + + :raises ValidationError: When constraint is violated + """ + self._check_partner("right") + + @api.one + def _check_partner(self, side): + """Check partner for required company or person, and for category + + :param str side: left or right + :raises ValidationError: When constraint is violated + """ + assert side in ['left', 'right'] + ptype = getattr(self.type_id, "contact_type_%s" % side) + partner = getattr(self, '%s_partner_id' % side) + if ((ptype == 'c' and not partner.is_company) or + (ptype == 'p' and partner.is_company)): + raise ValidationError( + _('The %s partner is not applicable for this relation type.') % + side + ) + category = getattr(self.type_id, "partner_category_%s" % side) + if category and category.id not in partner.category_id.ids: + raise ValidationError( + _('The %s partner does not have category %s.') % + (side, category.name) + ) + + @api.one + @api.constrains('left_partner_id', 'right_partner_id') + def _check_not_with_self(self): + """Not allowed to link partner to same partner + + :raises ValidationError: When constraint is violated + """ + if self.left_partner_id == self.right_partner_id: + if not (self.type_id and self.type_id.allow_self): + raise ValidationError( + _('Partners cannot have a relation with themselves.') + ) + + @api.one + @api.constrains( + 'left_partner_id', + 'type_id', + 'right_partner_id', + 'date_start', + 'date_end', + ) + def _check_relation_uniqueness(self): + """Forbid multiple active relations of the same type between the same + partners + + :raises ValidationError: When constraint is violated + """ + # pylint: disable=no-member + # pylint: disable=no-value-for-parameter + domain = [ + ('type_id', '=', self.type_id.id), + ('id', '!=', self.id), + ('left_partner_id', '=', self.left_partner_id.id), + ('right_partner_id', '=', self.right_partner_id.id), + ] + if self.date_start: + domain += [ + '|', + ('date_end', '=', False), + ('date_end', '>=', self.date_start), + ] + if self.date_end: + domain += [ + '|', + ('date_start', '=', False), + ('date_start', '<=', self.date_end), + ] + if self.search(domain): + raise ValidationError( + _('There is already a similar relation with overlapping dates') + ) diff --git a/partner_relations/models/res_partner_relation_all.py b/partner_relations/models/res_partner_relation_all.py new file mode 100644 index 000000000..a87b4f765 --- /dev/null +++ b/partner_relations/models/res_partner_relation_all.py @@ -0,0 +1,347 @@ +# -*- coding: utf-8 -*- +# © 2014-2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +"""Abstract model to show each relation from two sides.""" +from psycopg2.extensions import AsIs + +from openerp import _, api, fields, models +from openerp.tools import drop_view_if_exists + + +PADDING = 10 +_RECORD_TYPES = [ + ('a', 'Left partner to right partner'), + ('b', 'Right partner to left partner'), +] + + +class ResPartnerRelationAll(models.AbstractModel): + """Abstract model to show each relation from two sides.""" + _auto = False + _log_access = False + _name = 'res.partner.relation.all' + _description = 'All (non-inverse + inverse) relations between partners' + _order = ( + 'this_partner_id, type_selection_id,' + 'date_end desc, date_start desc' + ) + + _overlays = 'res.partner.relation' + + _additional_view_fields = [] + """append to this list if you added fields to res_partner_relation that + you need in this model and related fields are not adequate (ie for sorting) + You must use the same name as in res_partner_relation. + Don't overwrite this list in your declaration but append in _auto_init: + + def _auto_init(self, cr, context=None): + self._additional_view_fields.append('my_field') + return super(ResPartnerRelationAll, self)._auto_init( + cr, context=context) + + my_field = fields... + """ + + this_partner_id = fields.Many2one( + comodel_name='res.partner', + string='One Partner', + required=True, + ) + other_partner_id = fields.Many2one( + comodel_name='res.partner', + string='Other Partner', + required=True, + ) + type_selection_id = fields.Many2one( + comodel_name='res.partner.relation.type.selection', + string='Relation Type', + required=True, + ) + relation_id = fields.Many2one( + comodel_name='res.partner.relation', + string='Relation', + readonly=True, + ) + record_type = fields.Selection( + selection=_RECORD_TYPES, + string='Record Type', + readonly=True, + ) + date_start = fields.Date('Starting date') + date_end = fields.Date('Ending date') + active = fields.Boolean( + string='Active', + help="Records with date_end in the past are inactive", + ) + any_partner_id = fields.Many2many( + comodel_name='res.partner', + string='Partner', + compute=lambda self: None, + search='_search_any_partner_id' + ) + + def _auto_init(self, cr, context=None): + drop_view_if_exists(cr, self._table) + additional_view_fields = ','.join(self._additional_view_fields) + additional_view_fields = (',' + additional_view_fields)\ + if additional_view_fields else '' + cr.execute( + """\ +CREATE OR REPLACE VIEW %(table)s AS + SELECT + rel.id * %(padding)s AS id, + rel.id AS relation_id, + cast('a' AS CHAR(1)) AS record_type, + rel.left_partner_id AS this_partner_id, + rel.right_partner_id AS other_partner_id, + rel.date_start, + rel.date_end, + (rel.date_end IS NULL OR rel.date_end >= current_date) AS active, + rel.type_id * %(padding)s AS type_selection_id + %(additional_view_fields)s + FROM res_partner_relation rel + UNION SELECT + rel.id * %(padding)s + 1, + rel.id, + CAST('b' AS CHAR(1)), + rel.right_partner_id, + rel.left_partner_id, + rel.date_start, + rel.date_end, + rel.date_end IS NULL OR rel.date_end >= current_date, + CASE + WHEN typ.is_symmetric THEN rel.type_id * %(padding)s + ELSE rel.type_id * %(padding)s + 1 + END + %(additional_view_fields)s + FROM res_partner_relation rel + JOIN res_partner_relation_type typ ON (rel.type_id = typ.id) + """, + { + 'table': AsIs(self._table), + 'padding': PADDING, + 'additional_view_fields': AsIs(additional_view_fields), + } + ) + return super(ResPartnerRelationAll, self)._auto_init( + cr, context=context + ) + + @api.model + def _search_any_partner_id(self, operator, value): + """Search relation with partner, no matter on which side.""" + # pylint: disable=no-self-use + return [ + '|', + ('this_partner_id', operator, value), + ('other_partner_id', operator, value), + ] + + @api.multi + def name_get(self): + return { + this.id: '%s %s %s' % ( + this.this_partner_id.name, + this.type_selection_id.display_name, + this.other_partner_id.name, + ) + for this in self + } + + @api.onchange('type_selection_id') + def onchange_type_selection_id(self): + """Add domain on partners according to category and contact_type.""" + + def check_partner_domain(partner, partner_domain, side): + """Check wether partner_domain results in empty selection + for partner, or wrong selection of partner already selected. + """ + warning = {} + if partner: + test_domain = [('id', '=', partner.id)] + partner_domain + else: + test_domain = partner_domain + partner_model = self.env['res.partner'] + partners_found = partner_model.search(test_domain, limit=1) + if not partners_found: + warning['title'] = _('Error!') + if partner: + warning['message'] = ( + _('%s partner incompatible with relation type.') % + side.title() + ) + else: + warning['message'] = ( + _('No %s partner available for relation type.') % + side + ) + return warning + + this_partner_domain = [] + other_partner_domain = [] + if self.type_selection_id.contact_type_this: + this_partner_domain.append(( + 'is_company', '=', + self.type_selection_id.contact_type_this == 'c' + )) + if self.type_selection_id.partner_category_this: + this_partner_domain.append(( + 'category_id', 'in', + self.type_selection_id.partner_category_this.ids + )) + if self.type_selection_id.contact_type_other: + other_partner_domain.append(( + 'is_company', '=', + self.type_selection_id.contact_type_other == 'c' + )) + if self.type_selection_id.partner_category_other: + other_partner_domain.append(( + 'category_id', 'in', + self.type_selection_id.partner_category_other.ids + )) + result = {'domain': { + 'this_partner_id': this_partner_domain, + 'other_partner_id': other_partner_domain, + }} + # Check wether domain results in no choice or wrong choice of partners: + warning = {} + if this_partner_domain: + warning = check_partner_domain( + self.this_partner_id, this_partner_domain, _('this') + ) + if not warning and other_partner_domain: + warning = check_partner_domain( + self.other_partner_id, other_partner_domain, _('other') + ) + if warning: + result['warning'] = warning + return result + + @api.onchange( + 'this_partner_id', + 'other_partner_id', + ) + def onchange_partner_id(self): + """Set domain on type_selection_id based on partner(s) selected.""" + + def check_type_selection_domain(type_selection_domain): + """If type_selection_id already selected, check wether it + is compatible with the computed type_selection_domain. An empty + selection can practically only occur in a practically empty + database, and will not lead to problems. Therefore not tested. + """ + warning = {} + if not (type_selection_domain and self.type_selection_id): + return warning + test_domain = ( + [('id', '=', self.type_selection_id.id)] + + type_selection_domain + ) + type_model = self.env['res.partner.relation.type.selection'] + types_found = type_model.search(test_domain, limit=1) + if not types_found: + warning['title'] = _('Error!') + warning['message'] = _( + 'Relation type incompatible with selected partner(s).' + ) + return warning + + type_selection_domain = [] + if self.this_partner_id: + type_selection_domain += [ + '|', + ('contact_type_this', '=', False), + ('contact_type_this', '=', + self.this_partner_id.get_partner_type()), + '|', + ('partner_category_this', '=', False), + ('partner_category_this', 'in', + self.this_partner_id.category_id.ids), + ] + if self.other_partner_id: + type_selection_domain += [ + '|', + ('contact_type_other', '=', False), + ('contact_type_other', '=', + self.other_partner_id.get_partner_type()), + '|', + ('partner_category_other', '=', False), + ('partner_category_other', 'in', + self.other_partner_id.category_id.ids), + ] + result = {'domain': { + 'type_selection_id': type_selection_domain, + }} + # Check wether domain results in no choice or wrong choice for + # type_selection_id: + warning = check_type_selection_domain(type_selection_domain) + if warning: + result['warning'] = warning + return result + + @api.model + def _correct_vals(self, vals): + """Fill left and right partner from this and other partner.""" + vals = vals.copy() + if 'this_partner_id' in vals: + vals['left_partner_id'] = vals['this_partner_id'] + del vals['this_partner_id'] + if 'other_partner_id' in vals: + vals['right_partner_id'] = vals['other_partner_id'] + del vals['other_partner_id'] + if 'type_selection_id' not in vals: + return vals + selection = self.type_selection_id.browse(vals['type_selection_id']) + type_id = selection.type_id.id + is_inverse = selection.is_inverse + vals['type_id'] = type_id + del vals['type_selection_id'] + # Need to switch right and left partner if we are in reverse id: + if 'left_partner_id' in vals or 'right_partner_id' in vals: + if is_inverse: + left_partner_id = False + right_partner_id = False + if 'left_partner_id' in vals: + right_partner_id = vals['left_partner_id'] + del vals['left_partner_id'] + if 'right_partner_id' in vals: + left_partner_id = vals['right_partner_id'] + del vals['right_partner_id'] + if left_partner_id: + vals['left_partner_id'] = left_partner_id + if right_partner_id: + vals['right_partner_id'] = right_partner_id + return vals + + @api.multi + def write(self, vals): + """divert non-problematic writes to underlying table""" + vals = self._correct_vals(vals) + for rec in self: + rec.relation_id.write(vals) + return True + + @api.model + def create(self, vals): + """Divert non-problematic creates to underlying table. + + Create a res.partner.relation but return the converted id. + """ + is_inverse = False + if 'type_selection_id' in vals: + selection = self.type_selection_id.browse( + vals['type_selection_id'] + ) + is_inverse = selection.is_inverse + vals = self._correct_vals(vals) + res = self.relation_id.create(vals) + return_id = res.id * PADDING + (is_inverse and 1 or 0) + return self.browse(return_id) + + @api.multi + def unlink(self): + """divert non-problematic creates to underlying table""" + # pylint: disable=arguments-differ + for rec in self: + rec.relation_id.unlink() + return True diff --git a/partner_relations/models/res_partner_relation_type.py b/partner_relations/models/res_partner_relation_type.py new file mode 100644 index 000000000..7a8b324c6 --- /dev/null +++ b/partner_relations/models/res_partner_relation_type.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# © 2013-2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +"""Define the type of relations that can exist between partners.""" +from openerp import _, api, fields, models +from openerp.exceptions import ValidationError +from openerp.osv.expression import AND, OR + + +HANDLE_INVALID_ONCHANGE = [ + ('restrict', + _('Do not allow change that will result in invalid relations')), + ('ignore', + _('Allow existing relations that do not fit changed conditions')), + ('end', + _('End relations per today, if they do not fit changed conditions')), + ('delete', + _('Delete relations that do not fit changed conditions')), +] + + +class ResPartnerRelationType(models.Model): + """Model that defines relation types that might exist between partners""" + _name = 'res.partner.relation.type' + _description = 'Partner Relation Type' + _order = 'name' + + name = fields.Char( + string='Name', + required=True, + translate=True, + ) + name_inverse = fields.Char( + string='Inverse name', + required=True, + translate=True, + ) + contact_type_left = fields.Selection( + selection='get_partner_types', + string='Left partner type', + ) + contact_type_right = fields.Selection( + selection='get_partner_types', + string='Right partner type', + ) + partner_category_left = fields.Many2one( + comodel_name='res.partner.category', + string='Left partner category', + ) + partner_category_right = fields.Many2one( + comodel_name='res.partner.category', + string='Right partner category', + ) + allow_self = fields.Boolean( + string='Reflexive', + help='This relation can be set up with the same partner left and ' + 'right', + default=False, + ) + is_symmetric = fields.Boolean( + string='Symmetric', + old_name='symmetric', + help="This relation is the same from right to left as from left to" + " right", + default=False, + ) + handle_invalid_onchange = fields.Selection( + selection=HANDLE_INVALID_ONCHANGE, + string='Invalid relation handling', + required=True, + default='restrict', + help="When adding relations criteria like partner type and category" + " are checked.\n" + "However when you change the criteria, there might be relations" + " that do not fit the new criteria.\n" + "Specify how this situation should be handled.", + ) + + @api.model + def get_partner_types(self): + """A partner can be an organisation or an individual.""" + # pylint: disable=no-self-use + return [ + ('c', _('Organisation')), + ('p', _('Person')), + ] + + @api.onchange('is_symmetric') + def onchange_is_symmetric(self): + """Set right side to left side if symmetric.""" + if self.is_symmetric: + self.update({ + 'name_inverse': self.name, + 'contact_type_right': self.contact_type_left, + 'partner_category_right': self.partner_category_left, + }) + + @api.multi + def check_existing(self, vals): + """Check wether records exist that do not fit new criteria.""" + relation_model = self.env['res.partner.relation'] + + def get_type_condition(vals, side): + """Add if needed check for contact type.""" + fieldname1 = 'contact_type_%s' % side + fieldname2 = '%s_partner_id.is_company' % side + contact_type = fieldname1 in vals and vals[fieldname1] or False + if contact_type == 'c': + # Records that are not companies are invalid: + return [(fieldname2, '=', False)] + if contact_type == 'p': + # Records that are companies are invalid: + return [(fieldname2, '=', True)] + return [] + + def get_category_condition(vals, side): + """Add if needed check for partner category.""" + fieldname1 = 'partner_category_%s' % side + fieldname2 = '%s_partner_id.category_id' % side + category_id = fieldname1 in vals and vals[fieldname1] or False + if category_id: + # Records that do not have the specified category are invalid: + return [(fieldname2, 'not in', [category_id])] + return [] + + for rec in self: + handling = ( + 'handle_invalid_onchange' in vals and + vals['handle_invalid_onchange'] or + self.handle_invalid_onchange + ) + if handling == 'ignore': + continue + invalid_conditions = [] + for side in ['left', 'right']: + invalid_conditions = OR([ + invalid_conditions, + get_type_condition(vals, side), + ]) + invalid_conditions = OR([ + invalid_conditions, + get_category_condition(vals, side), + ]) + if not invalid_conditions: + return + # only look at relations for this type + invalid_domain = AND([ + [('type_id', '=', rec.id)], invalid_conditions + ]) + invalid_relations = relation_model.with_context( + active_test=False + ).search(invalid_domain) + if invalid_relations: + if handling == 'restrict': + raise ValidationError( + _('There are already relations not satisfying the' + ' conditions for partner type or category.') + ) + elif handling == 'delete': + invalid_relations.unlink() + else: + # Delete future records, end other ones, ignore relations + # already ended: + cutoff_date = fields.Date.today() + for relation in invalid_relations: + if relation.date_start >= cutoff_date: + relation.unlink() + elif (not relation.date_end or + relation.date_end > cutoff_date): + relation.write({'date_end': cutoff_date}) + + @api.multi + def write(self, vals): + """Handle existing relations if conditions change.""" + self.check_existing(vals) + return super(ResPartnerRelationType, self).write(vals) diff --git a/partner_relations/models/res_partner_relation_type_selection.py b/partner_relations/models/res_partner_relation_type_selection.py new file mode 100644 index 000000000..43ea80f94 --- /dev/null +++ b/partner_relations/models/res_partner_relation_type_selection.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# © 2014-2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +""" +For the model defined here _auto is set to False to prevent creating a +database file. The model is based on a SQL view based on +res_partner_relation_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. +""" +from psycopg2.extensions import AsIs + +from openerp import api, fields, models +from openerp.tools import drop_view_if_exists + +from .res_partner_relation_type import ResPartnerRelationType + + +PADDING = 10 + + +class ResPartnerRelationTypeSelection(models.Model): + """Virtual relation types""" + _name = 'res.partner.relation.type.selection' + _description = 'All relation types' + _auto = False # Do not try to create table in _auto_init(..) + _foreign_keys = [] + _log_access = False + _order = 'name asc' + + type_id = fields.Many2one( + comodel_name='res.partner.relation.type', + string='Type', + ) + name = fields.Char('Name') + contact_type_this = fields.Selection( + selection=ResPartnerRelationType.get_partner_types.im_func, + string='Current record\'s partner type', + ) + is_inverse = fields.Boolean( + string="Is reverse type?", + help="Inverse relations are from right to left partner.", + ) + contact_type_other = fields.Selection( + selection=ResPartnerRelationType.get_partner_types.im_func, + string='Other record\'s partner type', + ) + partner_category_this = fields.Many2one( + comodel_name='res.partner.category', + string='Current record\'s category', + ) + partner_category_other = fields.Many2one( + comodel_name='res.partner.category', + string='Other record\'s category', + ) + allow_self = fields.Boolean( + string='Reflexive', + ) + is_symmetric = fields.Boolean( + string='Symmetric', + ) + + def _auto_init(self, cr, context=None): + drop_view_if_exists(cr, self._table) + cr.execute( + """CREATE OR REPLACE VIEW %(table)s AS + SELECT + id * %(padding)s AS id, + id AS type_id, + name AS name, + False AS is_inverse, + 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, + allow_self, + is_symmetric + FROM %(underlying_table)s + UNION SELECT + id * %(padding)s + 1, + id, + name_inverse, + True, + contact_type_right, + contact_type_left, + partner_category_right, + partner_category_left, + allow_self, + is_symmetric + FROM %(underlying_table)s + WHERE not is_symmetric + """, + { + 'table': AsIs(self._table), + 'padding': PADDING, + 'underlying_table': AsIs('res_partner_relation_type'), + }) + return super(ResPartnerRelationTypeSelection, self)._auto_init( + cr, context=context) + + @api.multi + def name_get(self): + """Get name or name_inverse from underlying model.""" + return [ + (this.id, + this.is_inverse and this.type_id.name_inverse or + this.type_id.display_name) + for this in self + ] + + @api.model + def name_search(self, name='', args=None, operator='ilike', limit=100): + """Search for name or inverse name in underlying model.""" + # pylint: disable=no-value-for-parameter + return self.search( + [ + '|', + ('type_id.name', operator, name), + ('type_id.name_inverse', operator, name), + ] + (args or []), + limit=limit + ).name_get() diff --git a/partner_relations/test/test_allow.yml b/partner_relations/test/test_allow.yml deleted file mode 100644 index cbd89f803..000000000 --- a/partner_relations/test/test_allow.yml +++ /dev/null @@ -1,22 +0,0 @@ -- - I create a relation allowing the same partner at both ends. -- - !record {model: res.partner.relation.type, id: partner_relations.allow_self}: - name: 'Relation Allow' - name_inverse: 'Inverse Relation Allow' - contact_type_right: 'p' - contact_type_left: 'p' - allow_self: True -- - I create a partner U for testing purposes -- - !record {model: res.partner, id: partner_relations.test_U}: - name: 'unittests.U' - image: '' -- - I create relation instance U -- (allow) --> U -- - !record {model: res.partner.relation, id: partner_relations.test_allow}: - left_partner_id: partner_relations.test_U - right_partner_id: partner_relations.test_U - type_id: partner_relations.allow_self diff --git a/partner_relations/tests/__init__.py b/partner_relations/tests/__init__.py index 0c45f0703..272f6d49f 100644 --- a/partner_relations/tests/__init__.py +++ b/partner_relations/tests/__init__.py @@ -1 +1,7 @@ -from . import test_partner_relations +# -*- coding: utf-8 -*- +# Copyright 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import test_partner_relation_common +from . import test_partner_relation +from . import test_partner_relation_all +from . import test_partner_search diff --git a/partner_relations/tests/test_partner_relation.py b/partner_relations/tests/test_partner_relation.py new file mode 100644 index 000000000..b3ba3bd7b --- /dev/null +++ b/partner_relations/tests/test_partner_relation.py @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from datetime import date +from dateutil.relativedelta import relativedelta + +from openerp import fields +from openerp.exceptions import ValidationError + +from .test_partner_relation_common import TestPartnerRelationCommon + + +class TestPartnerRelation(TestPartnerRelationCommon): + + def test_selection_name_search(self): + """Test wether we can find type selection on reverse name.""" + selection_types = self.selection_model.name_search( + name=self.selection_person2company.name + ) + self.assertTrue(selection_types) + self.assertTrue( + (self.selection_person2company.id, + self.selection_person2company.name) in selection_types + ) + + def test_self_allowed(self): + """Test creation of relation to same partner when type allows.""" + type_allow = self.type_model.create({ + 'name': 'allow', + 'name_inverse': 'allow_inverse', + 'contact_type_left': 'p', + 'contact_type_right': 'p', + 'allow_self': True + }) + self.assertTrue(type_allow) + reflexive_relation = self.relation_model.create({ + 'type_id': type_allow.id, + 'left_partner_id': self.partner_01_person.id, + 'right_partner_id': self.partner_01_person.id, + }) + self.assertTrue(reflexive_relation) + + def test_self_disallowed(self): + """Test creating relation to same partner when disallowed. + + Attempt to create a relation of a partner to the same partner should + raise an error when the type of relation explicitly disallows this. + """ + type_disallow = self.type_model.create({ + 'name': 'disallow', + 'name_inverse': 'disallow_inverse', + 'contact_type_left': 'p', + 'contact_type_right': 'p', + 'allow_self': False + }) + self.assertTrue(type_disallow) + with self.assertRaises(ValidationError): + self.relation_model.create({ + 'type_id': type_disallow.id, + 'left_partner_id': self.partner_01_person.id, + 'right_partner_id': self.partner_01_person.id, + }) + + def test_self_default(self): + """Test default not to allow relation with same partner. + + Attempt to create a relation of a partner to the same partner + raise an error when the type of relation does not explicitly allow + this. + """ + type_default = self.type_model.create({ + 'name': 'default', + 'name_inverse': 'default_inverse', + 'contact_type_left': 'p', + 'contact_type_right': 'p', + }) + self.assertTrue(type_default) + with self.assertRaises(ValidationError): + self.relation_model.create({ + 'type_id': type_default.id, + 'left_partner_id': self.partner_01_person.id, + 'right_partner_id': self.partner_01_person.id, + }) + + def test_self_mixed(self): + """Test creation of relation with wrong types. + + Trying to create a relation between partners with an inappropiate + type should raise an error. + """ + with self.assertRaises(ValidationError): + self.relation_model.create({ + 'type_id': self.type_company2person.id, + 'left_partner_id': self.partner_01_person.id, + 'right_partner_id': self.partner_02_company.id, + }) + + def test_symmetric(self): + """Test creating symmetric relation.""" + # Start out with non symmetric relation: + type_symmetric = self.type_model.create({ + 'name': 'not yet symmetric', + 'name_inverse': 'the other side of not symmetric', + 'is_symmetric': False, + 'contact_type_left': False, + 'contact_type_right': 'p', + }) + # not yet symmetric relation should result in two records in + # selection: + selection_symmetric = self.selection_model.search([ + ('type_id', '=', type_symmetric.id), + ]) + self.assertEqual(len(selection_symmetric), 2) + # Now change to symmetric and test name and inverse name: + with self.env.do_in_draft(): + type_symmetric.write( + vals={ + 'name': 'sym', + 'is_symmetric': True, + } + ) + with self.env.do_in_onchange(): + type_symmetric.onchange_is_symmetric() + self.assertEqual(type_symmetric.is_symmetric, True) + self.assertEqual( + type_symmetric.name_inverse, + type_symmetric.name + ) + self.assertEqual( + type_symmetric.contact_type_right, + type_symmetric.contact_type_left + ) + # now update the database: + type_symmetric.write( + vals={ + 'name': type_symmetric.name, + 'is_symmetric': type_symmetric.is_symmetric, + 'name_inverse': type_symmetric.name_inverse, + 'contact_type_right': type_symmetric.contact_type_right, + } + ) + # symmetric relation should result in only one record in + # selection: + selection_symmetric = self.selection_model.search([ + ('type_id', '=', type_symmetric.id), + ]) + self.assertEqual(len(selection_symmetric), 1) + relation = self.relation_all_model.create({ + 'type_selection_id': selection_symmetric.id, + 'this_partner_id': self.partner_02_company.id, + 'other_partner_id': self.partner_01_person.id, + }) + partners = self.partner_model.search([ + ('search_relation_type_id', '=', relation.type_selection_id.id) + ]) + self.assertTrue(self.partner_01_person in partners) + self.assertTrue(self.partner_02_company in partners) + + def test_category_domain(self): + """Test check on category in relations.""" + # Check on left side: + with self.assertRaises(ValidationError): + self.relation_model.create({ + 'type_id': self.type_ngo2volunteer.id, + 'left_partner_id': self.partner_02_company.id, + 'right_partner_id': self.partner_04_volunteer.id, + }) + # Check on right side: + with self.assertRaises(ValidationError): + self.relation_model.create({ + 'type_id': self.type_ngo2volunteer.id, + 'left_partner_id': self.partner_03_ngo.id, + 'right_partner_id': self.partner_01_person.id, + }) + + def test_relation_type_change(self): + """Test change in relation type conditions.""" + # First create a relation type having no particular conditions. + (type_school2student, + school2student, + school2student_inverse) = ( + self._create_relation_type_selection({ + 'name': 'school has student', + 'name_inverse': 'studies at school', + }) + ) + # Second create relations based on those conditions. + partner_school = self.partner_model.create({ + 'name': 'Test School', + 'is_company': True, + 'ref': 'TS', + }) + partner_bart = self.partner_model.create({ + 'name': 'Bart Simpson', + 'is_company': False, + 'ref': 'BS', + }) + partner_lisa = self.partner_model.create({ + 'name': 'Lisa Simpson', + 'is_company': False, + 'ref': 'LS', + }) + relation_school2bart = self.relation_all_model.create({ + 'this_partner_id': partner_school.id, + 'type_selection_id': school2student.id, + 'other_partner_id': partner_bart.id, + }) + self.assertTrue(relation_school2bart) + relation_school2lisa = self.relation_all_model.create({ + 'this_partner_id': partner_school.id, + 'type_selection_id': school2student.id, + 'other_partner_id': partner_lisa.id, + }) + self.assertTrue(relation_school2lisa) + relation_bart2lisa = self.relation_all_model.create({ + 'this_partner_id': partner_bart.id, + 'type_selection_id': school2student.id, + 'other_partner_id': partner_lisa.id, + }) + self.assertTrue(relation_bart2lisa) + # Third creata a category and make it a condition for the + # relation type. + # - Test restriction + # - Test ignore + category_student = self.category_model.create({ + 'name': 'Student', + }) + with self.assertRaises(ValidationError): + type_school2student.write({ + 'partner_category_right': category_student.id, + }) + self.assertFalse(type_school2student.partner_category_right.id) + type_school2student.write({ + 'handle_invalid_onchange': 'ignore', + 'partner_category_right': category_student.id, + }) + self.assertEqual( + type_school2student.partner_category_right.id, + category_student.id + ) + # Fourth make company type a condition for left partner + # - Test ending + # - Test deletion + partner_bart.write({ + 'category_id': [(4, category_student.id)], + }) + partner_lisa.write({ + 'category_id': [(4, category_student.id)], + }) + # Future student to be deleted by end action: + partner_homer = self.partner_model.create({ + 'name': 'Homer Simpson', + 'is_company': False, + 'ref': 'HS', + 'category_id': [(4, category_student.id)], + }) + relation_lisa2homer = self.relation_all_model.create({ + 'this_partner_id': partner_lisa.id, + 'type_selection_id': school2student.id, + 'other_partner_id': partner_homer.id, + 'date_start': fields.Date.to_string( + date.today() + relativedelta(months=+6) + ), + }) + self.assertTrue(relation_lisa2homer) + type_school2student.write({ + 'handle_invalid_onchange': 'end', + 'contact_type_left': 'c', + }) + self.assertEqual( + relation_bart2lisa.date_end, + fields.Date.today() + ) + self.assertFalse(relation_lisa2homer.exists()) + type_school2student.write({ + 'handle_invalid_onchange': 'delete', + 'contact_type_left': 'c', + 'contact_type_right': 'p', + }) + self.assertFalse(relation_bart2lisa.exists()) diff --git a/partner_relations/tests/test_partner_relation_all.py b/partner_relations/tests/test_partner_relation_all.py new file mode 100644 index 000000000..e2c48f46a --- /dev/null +++ b/partner_relations/tests/test_partner_relation_all.py @@ -0,0 +1,241 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp.exceptions import ValidationError + +from .test_partner_relation_common import TestPartnerRelationCommon + + +class TestPartnerRelation(TestPartnerRelationCommon): + + def setUp(self): + super(TestPartnerRelation, self).setUp() + + # Create a new relation type which will not have valid relations: + category_nobody = self.category_model.create({ + 'name': 'Nobody', + }) + (self.type_nobody, + self.selection_nobody, + self.selection_nobody_inverse) = ( + self._create_relation_type_selection({ + 'name': 'has relation with nobody', + 'name_inverse': 'nobody has relation with', + 'contact_type_left': 'c', + 'contact_type_right': 'p', + 'partner_category_left': category_nobody.id, + 'partner_category_right': category_nobody.id, + }) + ) + + def _get_empty_relation(self): + """Get empty relation record for onchange tests.""" + # Need English, because we will compare text + return self.relation_all_model.with_context(lang='en_US').new({}) + + def test_create_with_active_id(self): + """Test creation with this_partner_id from active_id.""" + # Check wether we can create connection from company to person, + # taking the particular company from the active records: + relation = self.relation_all_model.with_context( + active_id=self.partner_02_company.id, + active_ids=self.partner_02_company.ids, + ).create({ + 'other_partner_id': self.partner_01_person.id, + 'type_selection_id': self.selection_company2person.id, + }) + self.assertTrue(relation) + self.assertEqual(relation.this_partner_id, self.partner_02_company) + # Partner should have one relation now: + self.assertEqual(self.partner_01_person.relation_count, 1) + + def test_display_name(self): + """Test display name""" + relation = self._create_company2person_relation() + self.assertEqual( + relation.display_name, '%s %s %s' % ( + relation.this_partner_id.name, + relation.type_selection_id.name, + relation.other_partner_id.name, + ) + ) + + def test__regular_write(self): + """Test write with valid data.""" + relation = self._create_company2person_relation() + relation.write({ + 'date_start': '2014-09-01', + }) + relation.invalidate_cache(ids=relation.ids) + self.assertEqual(relation.date_start, '2014-09-01') + + def test_write_incompatible_dates(self): + """Test write with date_end before date_start.""" + relation = self._create_company2person_relation() + with self.assertRaises(ValidationError): + relation.write({ + 'date_start': '2016-09-01', + 'date_end': '2016-08-01', + }) + + def test_validate_overlapping_01(self): + """Test create overlapping with no start / end dates.""" + relation = self._create_company2person_relation() + with self.assertRaises(ValidationError): + # New relation with no start / end should give error + self.relation_all_model.create({ + 'this_partner_id': relation.this_partner_id.id, + 'type_selection_id': relation.type_selection_id.id, + 'other_partner_id': relation.other_partner_id.id, + }) + + def test_validate_overlapping_02(self): + """Test create overlapping with start / end dates.""" + relation = self.relation_all_model.create({ + 'this_partner_id': self.partner_02_company.id, + 'type_selection_id': self.selection_company2person.id, + 'other_partner_id': self.partner_01_person.id, + 'date_start': '2015-09-01', + 'date_end': '2016-08-31', + }) + # New relation with overlapping start / end should give error + with self.assertRaises(ValidationError): + self.relation_all_model.create({ + 'this_partner_id': relation.this_partner_id.id, + 'type_selection_id': relation.type_selection_id.id, + 'other_partner_id': relation.other_partner_id.id, + 'date_start': '2016-08-01', + 'date_end': '2017-07-30', + }) + + def test_validate_overlapping_03(self): + """Test create not overlapping.""" + relation = self.relation_all_model.create({ + 'this_partner_id': self.partner_02_company.id, + 'type_selection_id': self.selection_company2person.id, + 'other_partner_id': self.partner_01_person.id, + 'date_start': '2015-09-01', + 'date_end': '2016-08-31', + }) + relation_another_record = self.relation_all_model.create({ + 'this_partner_id': relation.this_partner_id.id, + 'type_selection_id': relation.type_selection_id.id, + 'other_partner_id': relation.other_partner_id.id, + 'date_start': '2016-09-01', + 'date_end': '2017-08-31', + }) + self.assertTrue(relation_another_record) + + def test_inverse_record(self): + """Test creation of inverse record.""" + relation = self._create_company2person_relation() + inverse_relation = self.relation_all_model.search([ + ('this_partner_id', '=', relation.other_partner_id.id), + ('other_partner_id', '=', relation.this_partner_id.id), + ]) + self.assertEqual(len(inverse_relation), 1) + self.assertEqual( + inverse_relation.type_selection_id.name, + self.selection_person2company.name + ) + + def test_inverse_creation(self): + """Test creation of record through inverse selection.""" + relation = self.relation_all_model.create({ + 'this_partner_id': self.partner_01_person.id, + 'type_selection_id': self.selection_person2company.id, + 'other_partner_id': self.partner_02_company.id, + }) + # Check wether display name is what we should expect: + self.assertEqual( + relation.display_name, '%s %s %s' % ( + self.partner_01_person.name, + self.selection_person2company.name, + self.partner_02_company.name, + ) + ) + + def test_unlink(self): + """Unlinking derived relation should unlink base relation.""" + # Check wether underlying record is removed when record is removed: + relation = self._create_company2person_relation() + base_relation = relation.relation_id + relation.unlink() + self.assertFalse(base_relation.exists()) + + def test_on_change_type_selection(self): + """Test on_change_type_selection.""" + # 1. Test call with empty relation + relation_empty = self._get_empty_relation() + result = relation_empty.onchange_type_selection_id() + self.assertTrue('domain' in result) + self.assertFalse('warning' in result) + self.assertTrue('this_partner_id' in result['domain']) + self.assertFalse(result['domain']['this_partner_id']) + self.assertTrue('other_partner_id' in result['domain']) + self.assertFalse(result['domain']['other_partner_id']) + # 2. Test call with company 2 person relation + relation = self._create_company2person_relation() + domain = relation.onchange_type_selection_id()['domain'] + self.assertTrue( + ('is_company', '=', False) in domain['other_partner_id'] + ) + # 3. Test with relation needing categories. + relation_ngo_volunteer = self.relation_all_model.create({ + 'this_partner_id': self.partner_03_ngo.id, + 'type_selection_id': self.selection_ngo2volunteer.id, + 'other_partner_id': self.partner_04_volunteer.id, + }) + domain = relation_ngo_volunteer.onchange_type_selection_id()['domain'] + self.assertTrue( + ('category_id', 'in', [self.category_01_ngo.id]) in + domain['this_partner_id'] + ) + self.assertTrue( + ('category_id', 'in', [self.category_02_volunteer.id]) in + domain['other_partner_id'] + ) + # 4. Test with invalid or impossible combinations + relation_nobody = self._get_empty_relation() + with self.env.do_in_draft(): + relation_nobody.type_selection_id = self.selection_nobody + warning = relation_nobody.onchange_type_selection_id()['warning'] + self.assertTrue('message' in warning) + self.assertTrue('No this partner available' in warning['message']) + with self.env.do_in_draft(): + relation_nobody.this_partner_id = self.partner_02_company + warning = relation_nobody.onchange_type_selection_id()['warning'] + self.assertTrue('message' in warning) + self.assertTrue('incompatible' in warning['message']) + # Allow left partner and check message for other partner: + self.type_nobody.write({ + 'partner_category_left': False, + }) + self.selection_nobody.invalidate_cache(ids=self.selection_nobody.ids) + warning = relation_nobody.onchange_type_selection_id()['warning'] + self.assertTrue('message' in warning) + self.assertTrue('No other partner available' in warning['message']) + + def test_on_change_partner_id(self): + """Test on_change_partner_id.""" + # 1. Test call with empty relation + relation_empty = self._get_empty_relation() + result = relation_empty.onchange_partner_id() + self.assertTrue('domain' in result) + self.assertFalse('warning' in result) + self.assertTrue('type_selection_id' in result['domain']) + self.assertFalse(result['domain']['type_selection_id']) + # 2. Test call with company 2 person relation + relation = self._create_company2person_relation() + domain = relation.onchange_partner_id()['domain'] + self.assertTrue( + ('contact_type_this', '=', 'c') in domain['type_selection_id'] + ) + # 3. Test with invalid or impossible combinations + relation_nobody = self._get_empty_relation() + with self.env.do_in_draft(): + relation_nobody.this_partner_id = self.partner_02_company + relation_nobody.type_selection_id = self.selection_nobody + warning = relation_nobody.onchange_partner_id()['warning'] + self.assertTrue('message' in warning) + self.assertTrue('incompatible' in warning['message']) diff --git a/partner_relations/tests/test_partner_relation_common.py b/partner_relations/tests/test_partner_relation_common.py new file mode 100644 index 000000000..94249c6c7 --- /dev/null +++ b/partner_relations/tests/test_partner_relation_common.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp.tests import common + + +class TestPartnerRelationCommon(common.TransactionCase): + + def setUp(self): + super(TestPartnerRelationCommon, self).setUp() + + self.partner_model = self.env['res.partner'] + self.category_model = self.env['res.partner.category'] + self.type_model = self.env['res.partner.relation.type'] + self.selection_model = self.env['res.partner.relation.type.selection'] + self.relation_model = self.env['res.partner.relation'] + self.relation_all_model = self.env['res.partner.relation.all'] + self.partner_01_person = self.partner_model.create({ + 'name': 'Test User 1', + 'is_company': False, + 'ref': 'PR01', + }) + self.partner_02_company = self.partner_model.create({ + 'name': 'Test Company', + 'is_company': True, + 'ref': 'PR02', + }) + # Create partners with specific categories: + self.category_01_ngo = self.category_model.create({ + 'name': 'NGO', + }) + self.partner_03_ngo = self.partner_model.create({ + 'name': 'Test NGO', + 'is_company': True, + 'ref': 'PR03', + 'category_id': [(4, self.category_01_ngo.id)], + }) + self.category_02_volunteer = self.category_model.create({ + 'name': 'Volunteer', + }) + self.partner_04_volunteer = self.partner_model.create({ + 'name': 'Test Volunteer', + 'is_company': False, + 'ref': 'PR04', + 'category_id': [(4, self.category_02_volunteer.id)], + }) + # Create a new relation type withouth categories: + (self.type_company2person, + self.selection_company2person, + self.selection_person2company) = ( + self._create_relation_type_selection({ + 'name': 'mixed', + 'name_inverse': 'mixed_inverse', + 'contact_type_left': 'c', + 'contact_type_right': 'p', + }) + ) + # Create a new relation type with categories: + (self.type_ngo2volunteer, + self.selection_ngo2volunteer, + self.selection_volunteer2ngo) = ( + self._create_relation_type_selection({ + 'name': 'NGO has volunteer', + 'name_inverse': 'volunteer works for NGO', + 'contact_type_left': 'c', + 'contact_type_right': 'p', + 'partner_category_left': self.category_01_ngo.id, + 'partner_category_right': self.category_02_volunteer.id, + }) + ) + + def _create_relation_type_selection(self, vals): + """Create relation type and return this with selection types.""" + assert 'name' in vals, ( + "Name missing in vals to create relation type. Vals: %s." + % vals + ) + assert 'name' in vals, ( + "Name_inverse missing in vals to create relation type. Vals: %s." + % vals + ) + new_type = self.type_model.create(vals) + self.assertTrue( + new_type, + msg="No relation type created with vals %s." % vals + ) + selection_types = self.selection_model.search([ + ('type_id', '=', new_type.id), + ]) + for st in selection_types: + if st.is_inverse: + inverse_type_selection = st + else: + type_selection = st + self.assertTrue( + inverse_type_selection, + msg="Failed to find inverse type selection based on" + " relation type created with vals %s." % vals + ) + self.assertTrue( + type_selection, + msg="Failed to find type selection based on" + " relation type created with vals %s." % vals + ) + return (new_type, type_selection, inverse_type_selection) + + def _create_company2person_relation(self): + """Utility function to get a relation from company 2 partner.""" + return self.relation_all_model.create({ + 'type_selection_id': self.selection_company2person.id, + 'this_partner_id': self.partner_02_company.id, + 'other_partner_id': self.partner_01_person.id, + }) diff --git a/partner_relations/tests/test_partner_relations.py b/partner_relations/tests/test_partner_relations.py deleted file mode 100644 index 6d471fc02..000000000 --- a/partner_relations/tests/test_partner_relations.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# Author: Charbel Jacquin -# Copyright 2015 Camptocamp SA -# -# 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.tests import common -from openerp.exceptions import ValidationError - - -class TestPartnerRelation(common.TransactionCase): - - def setUp(self): - - super(TestPartnerRelation, self).setUp() - - self.partner_model = self.env['res.partner'] - self.relation_type_model = self.env['res.partner.relation.type'] - self.relation_model = self.env['res.partner.relation'] - - self.partner_1 = self.partner_model.create({ - 'name': 'Test User 1', - 'is_company': False - }) - - self.partner_2 = self.partner_model.create({ - 'name': 'Test User 2', - 'is_company': False - }) - - self.relation_allow = self.relation_type_model.create({ - 'name': 'allow', - 'name_inverse': 'allow_inverse', - 'contact_type_left': 'p', - 'contact_type_right': 'p', - 'allow_self': True - }) - - self.relation_disallow = self.relation_type_model.create({ - 'name': 'disallow', - 'name_inverse': 'disallow_inverse', - 'contact_type_left': 'p', - 'contact_type_right': 'p', - 'allow_self': False - }) - - self.relation_default = self.relation_type_model.create({ - 'name': 'default', - 'name_inverse': 'default_inverse', - 'contact_type_left': 'p', - 'contact_type_right': 'p', - }) - - def test_self_allowed(self): - - self.relation_model.create({'type_id': self.relation_allow.id, - 'left_partner_id': self.partner_1.id, - 'right_partner_id': self.partner_1.id}) - - def test_self_disallowed(self): - with self.assertRaises(ValidationError): - self.relation_model.create({'type_id': self.relation_disallow.id, - 'left_partner_id': self.partner_1.id, - 'right_partner_id': self.partner_1.id}) - - def test_self_default(self): - with self.assertRaises(ValidationError): - self.relation_model.create({'type_id': self.relation_default.id, - 'left_partner_id': self.partner_1.id, - 'right_partner_id': self.partner_1.id}) diff --git a/partner_relations/tests/test_partner_search.py b/partner_relations/tests/test_partner_search.py new file mode 100644 index 000000000..153b35b78 --- /dev/null +++ b/partner_relations/tests/test_partner_search.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 Camptocamp SA +# Copyright 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import fields +from openerp.exceptions import ValidationError + +from .test_partner_relation_common import TestPartnerRelationCommon + + +class TestPartnerSearch(TestPartnerRelationCommon): + + def test_search_relation_type(self): + """Test searching on relation type.""" + relation = self._create_company2person_relation() + partners = self.partner_model.search([ + ('search_relation_type_id', '=', relation.type_selection_id.id) + ]) + self.assertTrue(self.partner_02_company in partners) + partners = self.partner_model.search([ + ('search_relation_type_id', '!=', relation.type_selection_id.id) + ]) + self.assertTrue(self.partner_01_person in partners) + partners = self.partner_model.search([ + ('search_relation_type_id', '=', self.type_company2person.name) + ]) + self.assertTrue(self.partner_01_person in partners) + self.assertTrue(self.partner_02_company in partners) + partners = self.partner_model.search([ + ('search_relation_type_id', '=', 'unknown relation') + ]) + self.assertFalse(partners) + # Check error with invalid search operator: + with self.assertRaises(ValidationError): + partners = self.partner_model.search([ + ('search_relation_type_id', 'child_of', 'some parent') + ]) + + def test_search_relation_partner(self): + """Test searching on related partner.""" + self._create_company2person_relation() + partners = self.partner_model.search([ + ('search_relation_partner_id', '=', self.partner_02_company.id), + ]) + self.assertTrue(self.partner_01_person in partners) + + def test_search_relation_date(self): + """Test searching on relations valid on a certain date.""" + self._create_company2person_relation() + partners = self.partner_model.search([ + ('search_relation_date', '=', fields.Date.today()), + ]) + self.assertTrue(self.partner_01_person in partners) + self.assertTrue(self.partner_02_company in partners) + + def test_search_any_partner(self): + """Test searching for partner left or right.""" + self._create_company2person_relation() + both_relations = self.relation_all_model.search([ + ('any_partner_id', '=', self.partner_02_company.id), + ]) + self.assertEqual(len(both_relations), 2) + + def test_search_partner_category(self): + """Test searching for partners related to partners having category.""" + relation_ngo_volunteer = self.relation_all_model.create({ + 'this_partner_id': self.partner_03_ngo.id, + 'type_selection_id': self.selection_ngo2volunteer.id, + 'other_partner_id': self.partner_04_volunteer.id, + }) + self.assertTrue(relation_ngo_volunteer) + partners = self.partner_model.search([ + ('search_relation_partner_category_id', '=', + self.category_02_volunteer.id) + ]) + self.assertTrue(self.partner_03_ngo in partners) diff --git a/partner_relations/view/res_partner_relation.xml b/partner_relations/view/res_partner_relation.xml deleted file mode 100644 index 96bcb3982..000000000 --- a/partner_relations/view/res_partner_relation.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - res.partner.relation - -
- - - - - -
-
-
- - - res.partner.relation - - - - - - - - - - - - - - res.partner.relation - - - - - - - - - - - - - - - - - - Relations - res.partner.relation - form - tree - - - [('active', '=', True)] - -

- Record and track your partners' relations. Relations may be linked to other partners with a type either directly or inversely. -

-
-
- - - - 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 deleted file mode 100644 index 49a820710..000000000 --- a/partner_relations/view/res_partner_relation_all.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - res.partner.relation.all - - - - - - - - - - - - - - - res.partner.relation.all - -
- - - - - - - - - - -
-
-
- - - res.partner.relation.all - - - - - - - - - - - - - - - - Relations - res.partner.relation.all - form - tree - - - [('active', '=', True)] - -

- Record and track your partners' relations. Relations may be linked to other partners with a type either directly or inversely. -

-
-
- -
-
diff --git a/partner_relations/view/menu.xml b/partner_relations/views/menu.xml similarity index 91% rename from partner_relations/view/menu.xml rename to partner_relations/views/menu.xml index 40230348a..b60842f67 100644 --- a/partner_relations/view/menu.xml +++ b/partner_relations/views/menu.xml @@ -5,7 +5,7 @@ id="menu_res_partner_relation_sales" sequence="2" parent="base.menu_sales" - action="action_res_partner_relation" + action="action_res_partner_relation_all" /> - + @@ -24,19 +24,25 @@ res.partner - diff --git a/partner_relations/views/res_partner_relation_all.xml b/partner_relations/views/res_partner_relation_all.xml new file mode 100644 index 000000000..6f529984f --- /dev/null +++ b/partner_relations/views/res_partner_relation_all.xml @@ -0,0 +1,96 @@ + + + + + + res.partner.relation.all + + + + + + + + + + + + + + res.partner.relation.all + + + + + + + + + + + + + + + + + + + + Relations + res.partner.relation.all + form + tree + + + +

+ Record and track your partners' relations. Relations may + be linked to other partners with a type either directly + or inversely. +

+
+
+ +
+
diff --git a/partner_relations/view/res_partner_relation_type.xml b/partner_relations/views/res_partner_relation_type.xml similarity index 74% rename from partner_relations/view/res_partner_relation_type.xml rename to partner_relations/views/res_partner_relation_type.xml index 415dbe154..56f883959 100644 --- a/partner_relations/view/res_partner_relation_type.xml +++ b/partner_relations/views/res_partner_relation_type.xml @@ -2,42 +2,47 @@ res.partner.relation.type - tree - + + + res.partner.relation.type - form -
+ - + name="left" + > + name="right" + attrs="{'invisible': [('is_symmetric', '=', True)]}" + > + + + + +