Browse Source

[FIX] A lot of bugfixes, logic improvements, general fiddling

pull/301/head
Ronald Portier 8 years ago
committed by Holger Brunn
parent
commit
0bdfc077f6
No known key found for this signature in database GPG Key ID: 1C9760FECA3AE18
  1. 22
      partner_relations/__openerp__.py
  2. 14
      partner_relations/data/demo.xml
  3. 2
      partner_relations/i18n/da.po
  4. 2
      partner_relations/i18n/de.po
  5. 2
      partner_relations/i18n/es.po
  6. 2
      partner_relations/i18n/fi.po
  7. 2
      partner_relations/i18n/fr.po
  8. 2
      partner_relations/i18n/it.po
  9. 401
      partner_relations/i18n/nl.po
  10. 297
      partner_relations/i18n/partner_relations.pot
  11. 2
      partner_relations/i18n/pt_BR.po
  12. 2
      partner_relations/i18n/sl.po
  13. 170
      partner_relations/models/res_partner.py
  14. 283
      partner_relations/models/res_partner_relation.py
  15. 494
      partner_relations/models/res_partner_relation_all.py
  16. 174
      partner_relations/models/res_partner_relation_type.py
  17. 154
      partner_relations/models/res_partner_relation_type_selection.py
  18. 473
      partner_relations/tests/test_partner_relations.py
  19. 2
      partner_relations/views/menu.xml
  20. 34
      partner_relations/views/res_partner.xml
  21. 97
      partner_relations/views/res_partner_relation.xml
  22. 86
      partner_relations/views/res_partner_relation_all.xml
  23. 12
      partner_relations/views/res_partner_relation_type.xml

22
partner_relations/__openerp__.py

@ -1,23 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# © 2013-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Partner relations",
"version": "8.0.1.1.1",
@ -33,7 +16,6 @@
],
"data": [
"views/res_partner_relation_all.xml",
'views/res_partner_relation.xml',
'views/res_partner.xml',
'views/res_partner_relation_type.xml',
'views/menu.xml',

14
partner_relations/data/demo.xml

@ -2,21 +2,21 @@
<openerp>
<data>
<record id="rel_type_assistant" model="res.partner.relation.type">
<field name="name">Is assistant of</field>
<field name="name_inverse">Has assistant</field>
<field name="name">is assistant of</field>
<field name="name_inverse">has assistant</field>
<field name="contact_type_left">p</field>
<field name="contact_type_right">p</field>
</record>
<record id="rel_type_competitor" model="res.partner.relation.type">
<field name="name">Is competitor of</field>
<field name="name_inverse">Is competitor of</field>
<field name="name">is competitor of</field>
<field name="name_inverse">is competitor of</field>
<field name="contact_type_left">c</field>
<field name="contact_type_right">c</field>
<field name="symmetric" eval="True" />
<field name="is_symmetric" eval="True" />
</record>
<record id="rel_type_has_worked_for" model="res.partner.relation.type">
<field name="name">Has worked for</field>
<field name="name_inverse">Has former employee</field>
<field name="name">works for</field>
<field name="name_inverse">has employee</field>
<field name="contact_type_left">p</field>
<field name="contact_type_right">c</field>
</record>

2
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 ""

2
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"

2
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"

2
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 ""

2
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"

2
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 ""

401
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 <transbot@odoo-community.org>, 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 <transbot@odoo-community.org>\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 ""
"<p class=\"oe_view_nocontent_create\">\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"
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
"Onderhoud de connecties tussen uw relaties. Relaties mogen gekoppeld\n"
"worden zowel via een normale connectie, als met een omgekeerde\n"
"connectie.</p>"
#. 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"

297
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 "<p class=\"oe_view_nocontent_create\">\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"
" </p>\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 ""

2
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"

2
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"

170
partner_relations/models/res_partner.py

@ -1,144 +1,124 @@
# -*- coding: utf-8 -*-
# © 2013-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
"""Support connections between partners."""
import numbers
from openerp import _, models, fields, exceptions, api
from openerp.osv.expression import is_leaf, OR, FALSE_LEAF
PADDING = 10
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(
'Relation Count',
string='Relation Count',
compute="_compute_relation_count"
)
relation_ids = fields.One2many(
'res.partner.relation', string='Relations',
compute='_compute_relation_ids',
selectable=False,
)
relation_all_ids = fields.One2many(
'res.partner.relation.all', 'this_partner_id',
comodel_name='res.partner.relation.all',
inverse_name='this_partner_id',
string='All relations with current partner',
auto_join=True, selectable=False, copy=False,
auto_join=True,
selectable=False,
copy=False,
)
search_relation_id = fields.Many2one(
'res.partner.relation.type.selection', compute=lambda self: None,
search='_search_relation_id', string='Has relation of type',
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(
'res.partner', compute=lambda self: None,
search='_search_related_partner_id', string='Has relation with',
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',
compute=lambda self: None,
search='_search_relation_date',
string='Relation valid',
)
search_relation_partner_category_id = fields.Many2one(
'res.partner.category', compute=lambda self: None,
comodel_name='res.partner.category',
compute=lambda self: None,
search='_search_related_partner_category_id',
string='Has relation with a partner in category',
)
@api.one
@api.depends("relation_ids")
@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.
"""
self.relation_count = len([r for r in self.relation_ids if r.active])
@api.multi
def _compute_relation_ids(self):
'''getter for relation_ids'''
self.env.cr.execute(
"select p.id, array_agg(r.id) "
"from res_partner p join res_partner_relation r "
"on r.left_partner_id=p.id or r.right_partner_id=p.id "
"where p.id in %s "
"group by p.id",
(tuple(self.ids),)
)
partner2relation = dict(self.env.cr.fetchall())
for this in self:
this.relation_ids += self.env['res.partner.relation'].browse(
partner2relation.get(this.id, []),
)
for rec in self:
rec.relation_count = len(rec.relation_all_ids.filtered('active'))
@api.model
def _search_relation_id(self, operator, value):
def _search_relation_type_id(self, operator, value):
"""Search partners based on their type of relations."""
result = []
if operator not in [
'=', '!=', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in'
]:
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 += self\
.env['res.partner.relation.type.selection']\
.browse(value)
relation_type_selection += type_selection_model.browse(value)
elif operator == '!=' and isinstance(value, numbers.Integral):
relation_type_selection = self\
.env['res.partner.relation.type.selection']\
.search([
('id', operator, value),
])
relation_type_selection = type_selection_model.search([
('id', operator, value),
])
else:
relation_type_selection = self\
.env['res.partner.relation.type.selection']\
.search([
('type_id.name', operator, value),
])
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:
type_id, is_inverse = relation_type.get_type_from_selection_id()
result = OR([
result,
[
'&',
('relation_all_ids.type_id', '=', type_id),
(
'relation_all_ids.record_type', 'in',
['a', 'b']
if relation_type.type_id.symmetric
else
(['b'] if is_inverse else ['a'])
)
('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
if operator != '=':
raise exceptions.ValidationError(
_('Unsupported search operator "%s"') % operator)
return [
'&',
'|',
@ -151,14 +131,19 @@ class ResPartner(models.Model):
@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
"""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'):
@ -169,7 +154,6 @@ class ResPartner(models.Model):
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):
@ -177,32 +161,10 @@ class ResPartner(models.Model):
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 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)
@api.multi
def get_partner_type(self):
"""Get partner type for relation.

283
partner_relations/models/res_partner_relation.py

@ -1,208 +1,51 @@
# -*- coding: utf-8 -*-
# © 2013-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api, exceptions, _
from openerp.osv.expression import FALSE_LEAF
from .res_partner import PADDING
"""Store relations (connections) between partners."""
from openerp import _, api, exceptions, fields, models
class ResPartnerRelation(models.Model):
'''Model res.partner.relation is used to describe all links or relations
"""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.
'''
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'
_order = 'active desc, date_start desc, date_end desc'
type_selection_id = fields.Many2one(
'res.partner.relation.type.selection',
compute='_compute_fields',
fnct_inv=lambda *args: None,
string='Type',
)
partner_id_display = fields.Many2one(
'res.partner',
compute='_compute_fields',
fnct_inv=lambda *args: None,
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='_compute_any_partner_id',
store=True,
)
right_contact_type = fields.Selection(
lambda s: s.env['res.partner.relation.type']._get_partner_types(),
'Right Partner Type',
compute='_compute_any_partner_id',
store=True,
)
any_partner_id = fields.Many2many(
'res.partner',
string='Partner',
compute='_compute_any_partner_id',
search='_search_any_partner_id'
)
left_partner_id = fields.Many2one(
'res.partner',
comodel_name='res.partner',
string='Source Partner',
required=True,
auto_join=True,
ondelete='cascade',
)
right_partner_id = fields.Many2one(
'res.partner',
comodel_name='res.partner',
string='Destination Partner',
required=True,
auto_join=True,
ondelete='cascade',
)
type_id = fields.Many2one(
'res.partner.relation.type',
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')
active = fields.Boolean('Active', default=True)
@api.multi
def _compute_fields(self):
for this in self:
on_right_partner = this._on_right_partner()
this.type_selection_id = self\
.env['res.partner.relation.type.selection']\
.browse(this.type_id.id * PADDING +
(on_right_partner and 1 or 0))
this.partner_id_display = (
this.left_partner_id
if on_right_partner
else this.right_partner_id
)
@api.onchange('type_selection_id')
def _onchange_type_selection_id(self):
'''Set domain on partner_id_display, when selection a relation type'''
result = {
'domain': {'partner_id_display': [FALSE_LEAF]},
}
if not self.type_selection_id:
return result
type_id, is_reverse = self.type_selection_id\
.get_type_from_selection_id()
self.type_id = self.env['res.partner.relation.type'].browse(type_id)
partner_domain = []
check_contact_type = self.type_id.contact_type_right
check_partner_category = self.type_id.partner_category_right
if is_reverse:
# partner_id_display is left partner
check_contact_type = self.type_id.contact_type_left
check_partner_category = self.type_id.partner_category_left
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.ids))
result['domain']['partner_id_display'] = partner_domain
return result
@api.one
@api.depends('left_partner_id', 'right_partner_id')
def _compute_any_partner_id(self):
self.left_contact_type = self.left_partner_id.get_partner_type()
self.right_contact_type = self.right_partner_id.get_partner_type()
self.any_partner_id = self.left_partner_id + self.right_partner_id
@api.model
def _search_any_partner_id(self, operator, value):
return [
'|',
('left_partner_id', operator, value),
('right_partner_id', operator, value),
]
@api.multi
def _on_right_partner(self):
'''Determine wether functions are called in a situation where the
active partner is the right partner. Default False!
'''
return set(self.mapped('right_partner_id').ids) &\
set(self.env.context.get('active_ids', []))
@api.model
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' not in vals:
return vals
type_id, is_reverse = self\
.env['res.partner.relation.type.selection']\
.browse(vals['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('this_partner_id'):
if is_reverse:
vals['right_partner_id'] = vals['this_partner_id']
else:
vals['left_partner_id'] = vals['this_partner_id']
del vals['this_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)
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
@ -220,37 +63,44 @@ class ResPartnerRelation(models.Model):
@api.one
@api.constrains('left_partner_id', 'type_id')
def _check_partner_type_left(self):
def _check_partner_left(self):
"""Check left partner for required company or person
:raises exceptions.Warning: When constraint is violated
"""
self._check_partner_type("left")
self._check_partner("left")
@api.one
@api.constrains('right_partner_id', 'type_id')
def _check_partner_type_right(self):
def _check_partner_right(self):
"""Check right partner for required company or person
:raises exceptions.Warning: When constraint is violated
"""
self._check_partner_type("right")
self._check_partner("right")
@api.one
def _check_partner_type(self, side):
"""Check partner to left or right for required company or person
def _check_partner(self, side):
"""Check partner for required company or person, and for category
: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):
partner = getattr(self, '%s_partner_id' % side)
if ((ptype == 'c' and not partner.is_company) or
(ptype == 'p' and partner.is_company)):
raise exceptions.Warning(
_('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 exceptions.Warning(
_('The %s partner does not have category %s.') %
(side, category.name)
)
@api.one
@api.constrains('left_partner_id', 'right_partner_id')
@ -260,81 +110,46 @@ class ResPartnerRelation(models.Model):
:raises exceptions.Warning: When constraint is violated
"""
if self.left_partner_id == self.right_partner_id:
if not self.allow_self:
if not (self.type_id and self.type_id.allow_self):
raise exceptions.Warning(
_('Partners cannot have a relation with themselves.')
)
@api.one
@api.constrains('left_partner_id', 'right_partner_id', 'active')
@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 exceptions.Warning: When constraint is violated
"""
if not self.active:
return
# pylint: disable=no-member
# pylint: disable=no-value-for-parameter
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)]
domain += [
'|',
('date_end', '=', False),
('date_end', '>=', self.date_start),
]
if self.date_end:
domain += ['|', ('date_start', '=', False),
('date_start', '<=', 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')
)
@api.multi
def get_action_related_partners(self):
'''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'''
field_names = {}
if self.env.context.get('active_model', self._name) == self._name:
field_names = {
'left': ['left'],
'right': ['right'],
'all': ['left', 'right']
}
elif self.env.context.get('active_model') ==\
'res.partner.relation.all':
field_names = {
'left': ['this'],
'right': ['other'],
'all': ['this', 'other']
}
else:
assert False, 'Unknown active_model!'
partners = self.env['res.partner'].browse([])
field_names = field_names[
self.env.context.get('partner_relations_show_side', 'all')
]
field_names = ['%s_partner_id' % n for n in field_names]
for relation in self.env[self.env.context.get('active_model')].browse(
self.ids):
for name in field_names:
partners += relation[name]
return {
'name': _('Related partners'),
'type': 'ir.actions.act_window',
'res_model': 'res.partner',
'domain': [('id', 'in', partners.ids)],
'views': [(False, 'tree'), (False, 'form')],
'view_type': 'form'
}

494
partner_relations/models/res_partner_relation_all.py

@ -1,23 +1,35 @@
# -*- coding: utf-8 -*-
# © 2014-2016 Therp BV <http://therp.nl>
# 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 models, fields, api
from openerp import _, api, fields, models
from openerp.tools import drop_view_if_exists
from .res_partner_relation_type_selection import\
ResPartnerRelationTypeSelection
from .res_partner import PADDING
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'
_overlays = 'res.partner.relation'
_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
"""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:
@ -28,53 +40,45 @@ class ResPartnerRelationAll(models.AbstractModel):
cr, context=context)
my_field = fields...
'''
"""
this_partner_id = fields.Many2one(
'res.partner',
string='Current Partner',
comodel_name='res.partner',
string='One Partner',
required=True,
)
other_partner_id = fields.Many2one(
'res.partner',
comodel_name='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',
comodel_name='res.partner.relation.type.selection',
string='Relation Type',
required=True,
)
relation_id = fields.Many2one(
'res.partner.relation',
'Relation',
comodel_name='res.partner.relation',
string='Relation',
readonly=True,
)
record_type = fields.Selection(
ResPartnerRelationTypeSelection._RECORD_TYPES,
'Record Type',
selection=_RECORD_TYPES,
string='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)
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='_compute_any_partner_id',
search='_search_any_partner_id'
)
def _auto_init(self, cr, context=None):
drop_view_if_exists(cr, self._table)
@ -82,59 +86,64 @@ class ResPartnerRelationAll(models.AbstractModel):
additional_view_fields = (',' + additional_view_fields)\
if additional_view_fields else ''
cr.execute(
'''create or replace view %(table)s as
select
id * %(padding)s 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)s as type_selection_id
%(additional_view_fields)s
from %(underlying_table)s
union select
id * %(padding)s + 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)s + 1
%(additional_view_fields)s
from %(underlying_table)s''',
"""\
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),
'underlying_table': AsIs('res_partner_relation'),
}
)
return super(ResPartnerRelationAll, self)._auto_init(
cr, context=context)
cr, context=context
)
@api.multi
def _get_underlying_object(self):
"""Get the record on which this record is overlaid"""
return self.env[self._overlays].browse(
i / PADDING for i in self.ids)
@api.depends('this_partner_id', 'other_partner_id')
def _compute_any_partner_id(self):
"""Compute any_partner_id, used for searching for partner, independent
wether it is the one partner or the other partner in the relation.
"""
for rec in self:
rec.any_partner_id = rec.this_partner_id + rec.other_partner_id
@api.multi
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 partner.get_partner_type()
return False
@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):
@ -149,68 +158,301 @@ class ResPartnerRelationAll(models.AbstractModel):
@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 = []
"""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 not partner_domain:
return 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:
if partner:
message = _(
'%s partner incompatible with relation type.' %
side.title()
)
else:
message = _(
'No %s partner available for relation type.' % side
)
warning = {'title': _('Error!'), 'message': message}
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:
domain.append(
('is_company', '=',
self.type_selection_id.contact_type_other == 'c'))
other_partner_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,
}
}
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 = check_partner_domain(
self.this_partner_id, this_partner_domain, _('this')
)
if warning:
result['warning'] = warning
else:
warning = check_partner_domain(
self.other_partner_id, other_partner_domain, _('other')
)
if warning:
result['warning'] = warning
return result
@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', '=',
self.this_partner_id.get_partner_type()),
'|',
('partner_category_this', '=', False),
('partner_category_this', 'in',
self.this_partner_id.category_id.ids),
],
},
}
@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):
"""Check wether type_selection_domain results in empty selection
for type_selection_id, or wrong selection if already selected.
"""
warning = {}
if not type_selection_domain:
return warning
if self.type_selection_id:
test_domain = (
[('id', '=', self.type_selection_id.id)] +
type_selection_domain
)
else:
test_domain = 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:
if self.type_selection_id:
message = _(
'Relation type incompatible with selected partner(s).'
)
else:
message = _(
'No relation type available for selected partners.'
)
warning = {'title': _('Error!'), 'message': message}
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
def check_type_selection_domain(type_selection_domain):
"""Check wether type_selection_domain results in empty selection
for type_selection_id, or wrong selection if already selected.
"""
warning = {}
if not type_selection_domain:
return warning
if self.type_selection_id:
test_domain = (
[('id', '=', self.type_selection_id.id)] +
type_selection_domain
)
else:
test_domain = 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:
if self.type_selection_id:
message = _(
'Relation type incompatible with selected partner(s).'
)
else:
message = _(
'No relation type available for selected partners.'
)
warning = {'title': _('Error!'), 'message': message}
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"""
underlying_objs = self._get_underlying_object()
vals = {
key: val
for key, val in vals.iteritems()
if not self._fields[key].readonly
}
return underlying_objs.write(vals)
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
"""Divert non-problematic creates to underlying table.
Create a res.partner.relation but return the converted id
Create a res.partner.relation but return the converted id.
"""
vals = {
key: val
for key, val in vals.iteritems()
if not self._fields[key].readonly
}
res = self.env[self._overlays].create(vals)
return self.browse(res.id * PADDING)
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"""
return self._get_underlying_object().unlink()
# pylint: disable=arguments-differ
for rec in self:
rec.relation_id.unlink()
return True

174
partner_relations/models/res_partner_relation_type.py

@ -1,7 +1,21 @@
# -*- coding: utf-8 -*-
# © 2013-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api, _
"""Define the type of relations that can exist between partners."""
from openerp import _, api, fields, models
from openerp.exceptions import ValidationError
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):
@ -11,55 +25,165 @@ class ResPartnerRelationType(models.Model):
_order = 'name'
name = fields.Char(
'Name',
string='Name',
required=True,
translate=True,
)
name_inverse = fields.Char(
'Inverse name',
string='Inverse name',
required=True,
translate=True,
)
contact_type_left = fields.Selection(
'_get_partner_types',
'Left partner type',
selection='get_partner_types',
string='Left partner type',
)
contact_type_right = fields.Selection(
'_get_partner_types',
'Right partner type',
selection='get_partner_types',
string='Right partner type',
)
partner_category_left = fields.Many2one(
'res.partner.category',
'Left partner category',
comodel_name='res.partner.category',
string='Left partner category',
)
partner_category_right = fields.Many2one(
'res.partner.category',
'Right partner category',
comodel_name='res.partner.category',
string='Right partner category',
)
allow_self = fields.Boolean(
'Reflexive',
string='Reflexive',
help='This relation can be set up with the same partner left and '
'right',
default=False,
)
symmetric = fields.Boolean(
'Symmetric',
help='This relation is the same from right to left as from left to '
'right',
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):
def get_partner_types(self):
"""A partner can be an organisation or an individual."""
# pylint: disable=no-self-use
return [
('c', _('Company')),
('c', _('Organisation')),
('p', _('Person')),
]
@api.onchange('symmetric')
def _onchange_symmetric(self):
self.update({
'name_inverse': self.name,
'contact_type_right': self.contact_type_left,
'partner_category_right': self.partner_category_left,
})
@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']
for rec in self:
handling = (
'handle_invalid_onchange' in vals and
vals['handle_invalid_onchange'] or
self.handle_invalid_onchange
)
if handling == 'ignore':
continue
# only look at relations for this type
invalid_domain = [
('type_id', '=', rec.id),
]
contact_type_left = (
'contact_type_left' in vals and vals['contact_type_left'] or
False
)
if contact_type_left == 'c':
# Valid records are companies:
invalid_domain.append(
('left_partner_id.is_company', '=', False)
)
if contact_type_left == 'p':
# Valid records are persons:
invalid_domain.append(
('left_partner_id.is_company', '=', True)
)
contact_type_right = (
'contact_type_right' in vals and vals['contact_type_right'] or
False
)
if contact_type_right == 'c':
# Valid records are companies:
invalid_domain.append(
('right_partner_id.is_company', '=', False)
)
if contact_type_right == 'p':
# Valid records are persons:
invalid_domain.append(
('right_partner_id.is_company', '=', True)
)
partner_category_left = (
'partner_category_left' in vals and
vals['partner_category_left'] or
False
)
if partner_category_left:
# records that do not have the specified category are invalid:
invalid_domain.append(
('left_partner_id.category_id', 'not in',
partner_category_left)
)
partner_category_right = (
'partner_category_right' in vals and
vals['partner_category_right'] or
False
)
if partner_category_right:
# records that do not have the specified category are invalid:
invalid_domain.append(
('right_partner_id.category_id', 'not in',
partner_category_right)
)
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)

154
partner_relations/models/res_partner_relation_type_selection.py

@ -1,17 +1,10 @@
# -*- coding: utf-8 -*-
# © 2014-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
'''
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
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..
@ -19,25 +12,20 @@ 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 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
from .res_partner import PADDING
PADDING = 10
class ResPartnerRelationTypeSelection(models.Model):
'''Virtual relation types'''
"""Virtual relation types"""
_name = 'res.partner.relation.type.selection'
_description = 'All relation types'
_auto = False # Do not try to create table in _auto_init(..)
@ -45,105 +33,95 @@ class ResPartnerRelationTypeSelection(models.Model):
_log_access = False
_order = 'name asc'
_RECORD_TYPES = [
('a', 'Type'),
('b', 'Inverse type'),
]
record_type = fields.Selection(_RECORD_TYPES, 'Record type')
type_id = fields.Many2one('res.partner.relation.type', 'Type')
type_id = fields.Many2one(
comodel_name='res.partner.relation.type',
string='Type',
)
name = fields.Char('Name')
contact_type_this = fields.Selection(
ResPartnerRelationType._get_partner_types.im_func,
'Current record\'s partner type')
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(
ResPartnerRelationType._get_partner_types.im_func,
'Other record\'s partner type')
selection=ResPartnerRelationType.get_partner_types.im_func,
string='Other record\'s partner type',
)
partner_category_this = fields.Many2one(
'res.partner.category', 'Current record\'s category')
comodel_name='res.partner.category',
string='Current record\'s category',
)
partner_category_other = fields.Many2one(
'res.partner.category', 'Other record\'s category')
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,
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
"""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,
cast('b' as char(1)),
name_inverse,
True,
contact_type_right,
contact_type_left,
partner_category_right,
partner_category_left
from %(underlying_table)s''',
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):
"""translate name using translations from res.partner.relation.type"""
ir_translation = self.env['ir.translation']
"""Get name or name_inverse from underlying model."""
return [
(
this.id,
ir_translation._get_source(
'res.partner.relation.type,name_inverse'
if this.get_type_from_selection_id()[1]
else 'res.partner.relation.type,name',
('model',),
self.env.context.get('lang'),
this.name,
this.type_id.id
)
)
(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 translated names in res.partner.relation.type"""
res_partner_relation_type = self.env['res.partner.relation.type']
relations = res_partner_relation_type.search([
('name', operator, name)
])
inverse_relations = res_partner_relation_type.search([
('name_inverse', operator, name),
('symmetric', '=', False),
])
"""Search for name or inverse name in underlying model."""
# pylint: disable=no-value-for-parameter
return self.search(
[
(
'id', 'in',
map(lambda x: x * PADDING, relations.ids) +
map(lambda x: x * PADDING + 1, inverse_relations.ids)
),
'|',
('type_id.name', operator, name),
('type_id.name_inverse', operator, name),
] + (args or []),
limit=limit
).name_get()
@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

473
partner_relations/tests/test_partner_relations.py

@ -10,190 +10,427 @@ 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.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.partner_1 = self.partner_model.create({
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_2 = self.partner_model.create({
self.partner_02_company = self.partner_model.create({
'name': 'Test Company',
'is_company': True,
'ref': 'PR02',
})
self.type_company2person = self.type_model.create({
'name': 'mixed',
'name_inverse': 'mixed_inverse',
'contact_type_left': 'c',
'contact_type_right': 'p',
})
# 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)],
})
# Determine the two records in res.partner.type.selection that came
# into existance by creating one res.partner.relation.type:
selection_types = self.selection_model.search([
('type_id', '=', self.type_company2person.id),
])
for st in selection_types:
if st.is_inverse:
self.selection_person2company = st
else:
self.selection_company2person = st
assert self.selection_person2company, (
"Failed to create person to company selection in setup."
)
assert self.selection_company2person, (
"Failed to create company to person selection in setup."
)
# Create realion type between NGO and volunteer, and then lookup
# resulting type_selection_id's:
self.type_ngo2volunteer = self.type_model.create({
'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,
})
selection_types = self.selection_model.search([
('type_id', '=', self.type_ngo2volunteer.id),
])
for st in selection_types:
if st.is_inverse:
self.selection_volunteer2ngo = st
else:
self.selection_ngo2volunteer = st
assert self.selection_volunteer2ngo, (
"Failed to create volunteer to NGO selection in setup."
)
assert self.selection_ngo2volunteer, (
"Failed to create NGO to volunteer selection in setup."
)
self.relation_allow = self.relation_type_model.create({
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.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.relation_disallow = self.relation_type_model.create({
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
})
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.
self.relation_default = self.relation_type_model.create({
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.relation_mixed = self.relation_type_model.create({
'name': 'mixed',
'name_inverse': 'mixed_inverse',
'contact_type_left': 'c',
'contact_type_right': 'p',
})
self.relation_symmetric = self.relation_type_model.create({
'name': 'sym',
'name_inverse': 'sym',
'symmetric': True,
})
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,
'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.relation_mixed.id,
'left_partner_id': self.partner_1.id,
'right_partner_id': self.partner_2.id,
'type_id': self.type_company2person.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_02_company.id,
})
def test_searching(self):
relation = self.relation_model.create({
'type_id': self.relation_mixed.id,
'left_partner_id': self.partner_2.id,
'right_partner_id': self.partner_1.id,
"""Test searching on relations.
Interaction with the relations should always be through
res.partner.relation.all.
"""
relation = 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,
})
partners = self.env['res.partner'].search([
('search_relation_id', '=', relation.type_selection_id.id)
partners = self.partner_model.search([
('search_relation_type_id', '=', relation.type_selection_id.id)
])
self.assertTrue(self.partner_2 in partners)
partners = self.env['res.partner'].search([
('search_relation_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_1 in partners)
partners = self.env['res.partner'].search([
('search_relation_id', '=', self.relation_mixed.name)
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_1 in partners)
self.assertTrue(self.partner_2 in partners)
partners = self.env['res.partner'].search([
('search_relation_id', '=', 'unknown relation')
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)
partners = self.env['res.partner'].search([
('search_relation_partner_id', '=', self.partner_2.id),
partners = self.partner_model.search([
('search_relation_partner_id', '=', self.partner_02_company.id),
])
self.assertTrue(self.partner_1 in partners)
partners = self.env['res.partner'].search([
self.assertTrue(self.partner_01_person in partners)
partners = self.partner_model.search([
('search_relation_date', '=', fields.Date.today()),
])
self.assertTrue(self.partner_1 in partners)
self.assertTrue(self.partner_2 in partners)
def test_ui_functions(self):
relation = self.relation_model.create({
'type_id': self.relation_mixed.id,
'left_partner_id': self.partner_2.id,
'right_partner_id': self.partner_1.id,
})
self.assertEqual(relation.type_selection_id.type_id, relation.type_id)
relation = relation.with_context(
active_id=self.partner_1.id,
active_ids=self.partner_1.ids,
active_model='res.partner.relation',
)
relation.read()
domain = relation._onchange_type_selection_id()['domain']
self.assertTrue(
('is_company', '=', True) in domain['partner_id_display']
)
relation.write({
'type_selection_id': relation.type_selection_id.id,
})
action = relation.get_action_related_partners()
self.assertTrue(self.partner_1.id in action['domain'][0][2])
self.assertTrue(self.partner_01_person in partners)
self.assertTrue(self.partner_02_company in partners)
def test_relation_all(self):
relation_all_record = self.env['res.partner.relation.all']\
.with_context(
active_id=self.partner_2.id,
active_ids=self.partner_2.ids,
"""Test interactions through res.partner.relation.all."""
# Check wether we can create connection from company to person,
# taking the particular company from the active records:
relation_all_record = 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_1.id,
'type_selection_id': self.relation_mixed.id * 10,
'other_partner_id': self.partner_01_person.id,
'type_selection_id': self.selection_company2person.id,
})
# Check wether display name is what we should expect:
self.assertEqual(
relation_all_record.display_name, '%s %s %s' % (
self.partner_2.name,
'mixed',
self.partner_1.name,
self.partner_02_company.name,
self.selection_company2person.name,
self.partner_01_person.name,
)
)
# Check wether the inverse record is present and looks like expected:
inverse_relation = self.relation_all_model.search([
('this_partner_id', '=', self.partner_01_person.id),
('other_partner_id', '=', self.partner_02_company.id),
])
self.assertEqual(len(inverse_relation), 1)
self.assertEqual(
inverse_relation.type_selection_id.name,
self.selection_person2company.name
)
# Check wether on_change_type_selection works as expected:
domain = relation_all_record.onchange_type_selection_id()['domain']
self.assertTrue(
('is_company', '=', False) in domain['other_partner_id'])
domain = relation_all_record.onchange_this_partner_id()['domain']
('is_company', '=', False) in domain['other_partner_id']
)
domain = relation_all_record.onchange_partner_id()['domain']
self.assertTrue(
('contact_type_this', '=', 'c') in domain['type_selection_id'])
('contact_type_this', '=', 'c') in domain['type_selection_id']
)
relation_all_record.write({
'type_id': self.relation_mixed.id,
'type_id': self.type_company2person.id,
})
# Check wether underlying record is removed when record is removed:
relation = relation_all_record.relation_id
relation_all_record.unlink()
self.assertFalse(relation.exists())
def test_symmetric(self):
relation = self.relation_model.create({
'type_id': self.relation_symmetric.id,
'left_partner_id': self.partner_2.id,
'right_partner_id': self.partner_1.id,
"""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.env['res.partner'].search([
('search_relation_id', '=', relation.type_selection_id.id)
partners = self.partner_model.search([
('search_relation_type_id', '=', relation.type_selection_id.id)
])
self.assertTrue(self.partner_1 in partners)
self.assertTrue(self.partner_2 in partners)
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,
})
# Creating a relation with a type referring to a certain category
# should only allow partners for that category.
relation_all_record = 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,
})
# Check wether on_change_type_selection works as expected:
domain = relation_all_record.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']
)
def test_relation_type_change(self):
"""Test change in relation type conditions."""
# First create a relation type having no particular conditions.
type_school2student = self.type_model.create({
'name': 'school has student',
'name_inverse': 'studies at school',
})
selection_types = self.selection_model.search([
('type_id', '=', type_school2student.id),
])
for st in selection_types:
if st.is_inverse:
student2school = st
else:
school2student = st
self.assertTrue(school2student)
self.assertTrue(student2school)
# 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({
'type_selection_id': school2student.id,
'this_partner_id': partner_school.id,
'other_partner_id': partner_bart.id,
})
self.assertTrue(relation_school2bart)
relation_school2lisa = self.relation_all_model.create({
'type_selection_id': school2student.id,
'this_partner_id': partner_school.id,
'other_partner_id': partner_lisa.id,
})
self.assertTrue(relation_school2lisa)
relation_bart2lisa = self.relation_all_model.create({
'type_selection_id': school2student.id,
'this_partner_id': partner_bart.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)],
})
type_school2student.write({
'handle_invalid_onchange': 'end',
'contact_type_left': 'c',
})
self.assertEqual(
relation_bart2lisa.date_end,
fields.Date.today()
)
type_school2student.write({
'handle_invalid_onchange': 'delete',
'contact_type_left': 'c',
})
self.assertFalse(relation_bart2lisa.exists())

2
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"
/>
<act_window

34
partner_relations/views/res_partner.xml

@ -10,7 +10,7 @@
<data>
<field name="parent_id" position="after">
<field name="search_relation_partner_id" />
<field name="search_relation_id" />
<field name="search_relation_type_id" />
<field name="search_relation_date" />
<field name="search_relation_partner_category_id" />
</field>
@ -24,19 +24,25 @@
<field name="model">res.partner</field>
<field type="xml" name="arch">
<xpath expr="//div[@name='buttons']" position="inside">
<button class="oe_inline oe_stat_button"
type="action"
context="{
'search_default_this_partner_id': active_id,
'default_left_partner_id': active_id,
'active_model': 'res.partner',
'active_id': id,
'active_ids': [id],
'active_test': False,
}"
name="%(action_res_partner_relation_all)d"
icon="fa-users">
<field string="Relations" name="relation_count" widget="statinfo"/>
<button
class="oe_inline oe_stat_button"
type="action"
context="{
'search_default_this_partner_id': active_id,
'default_this_partner_id': active_id,
'active_model': 'res.partner',
'active_id': id,
'active_ids': [id],
'active_test': False,
}"
name="%(action_res_partner_relation_all)d"
icon="fa-users"
>
<field
string="Relations"
name="relation_count"
widget="statinfo"
/>
</button>
</xpath>
</field>

97
partner_relations/views/res_partner_relation.xml

@ -1,97 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="form_res_partner_relation" model="ir.ui.view">
<field name="model">res.partner.relation</field>
<field type="xml" name="arch">
<form string="Partner Relation">
<sheet>
<field name="left_partner_id" />
<field name="type_id" />
<field name="right_partner_id" />
</sheet>
</form>
</field>
</record>
<record id="tree_res_partner_relation" model="ir.ui.view">
<field name="model">res.partner.relation</field>
<field name="arch" type="xml">
<tree
string="Partner Relations"
colors="gray:date_end and date_end &lt; current_date or not active;blue:date_start &gt; current_date"
editable="top"
>
<field
name="left_partner_id"
options="{'create': false, 'create_edit': false}"
/>
<field
name="type_selection_id"
required="True"
options="{'create': false, 'create_edit': false}"/>
<field
name="right_partner_id"
options="{'create': false, 'create_edit': false}"
/>
<field name="date_start" />
<field name="date_end" />
<field name="active" />
</tree>
</field>
</record>
<record id="search_res_partner_relation" model="ir.ui.view">
<field name="model">res.partner.relation</field>
<field name="arch" type="xml">
<search string="Search Relations">
<field name="any_partner_id" widget="many2one"/>
<field name="left_partner_id"/>
<field name="right_partner_id"/>
<field name="type_id"/>
<field name="active"/>
<group expand="0" string="Group By">
<filter string="Left Partner" context="{'group_by': 'left_partner_id'}"/>
<filter string="Right Partner" context="{'group_by': 'right_partner_id'}"/>
<filter string="Relationship Type" context="{'group_by': 'type_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_res_partner_relation" model="ir.actions.act_window">
<field name="name">Relations</field>
<field name="res_model">res.partner.relation</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="tree_res_partner_relation"/>
<field name="search_view_id" ref="search_res_partner_relation"/>
<field name="domain">[('active', '=', True)]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Record and track your partners' relations. Relations may be linked to other partners with a type either directly or inversely.
</p>
</field>
</record>
<record id="action_show_right_relation_partners" model="ir.actions.server">
<field name="sequence" eval="5"/>
<field name="state">code</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_res_partner_relation"/>
<field name="code">action = self.get_action_related_partners(cr, uid, context.get('active_ids', []), dict(context or {}, partner_relations_show_side='right'))</field>
<field name="condition">True</field>
<field name="name">Show partners</field>
</record>
<record id="action_show_right_relation_partners_value" model="ir.values">
<field name="name">Show partners</field>
<field name="key">action</field>
<field name="key2">client_action_multi</field>
<field name="model">res.partner.relation.all</field>
<field name="value" eval="'ir.actions.server,%d' % ref('partner_relations.action_show_right_relation_partners')" />
</record>
</data>
</openerp>

86
partner_relations/views/res_partner_relation_all.xml

@ -7,77 +7,87 @@
<field name="arch" type="xml">
<tree
string="Partner Relations"
colors="gray:(date_end and date_end &lt; current_date) or not active;blue:date_start &gt; current_date"
colors="gray:not active; blue:date_start &gt; current_date"
editable="top"
>
>
<field
name="this_partner_id"
invisible="1"
/>
required="True"
options="{'no_create': True}"
/>
<field
name="type_selection_id"
required="True"
options="{'create': false, 'create_edit': false}"
/>
options="{'no_create': True}"
/>
<field
name="other_partner_id"
attrs="{
'readonly': [('type_selection_id', '=', False)],
}"
options="{'create': false, 'create_edit': false}"
/>
required="True"
options="{'no_create': True}"
/>
<field name="date_start" />
<field name="date_end" />
<field name="active" />
<field name="active" invisible="1" />
</tree>
</field>
</record>
<record id="form_res_partner_relation_all" model="ir.ui.view">
<field name="model">res.partner.relation.all</field>
<field name="arch" type="xml">
<form string="Partner relation">
<group>
<field name="this_partner_id" />
<field name="type_selection_id" />
<field name="other_partner_id" />
</group>
<group>
<field name="date_start" />
<field name="date_end" />
<field name="active" />
</group>
</form>
</field>
</record>
<record id="search_res_partner_relation_all" model="ir.ui.view">
<field name="model">res.partner.relation.all</field>
<field name="arch" type="xml">
<search string="Search Relations">
<field name="any_partner_id" widget="many2one"/>
<field name="this_partner_id"/>
<field name="other_partner_id"/>
<field name="type_id"/>
<field name="active"/>
<field name="type_selection_id"/>
<filter
string="Left to right"
domain="[('record_type', '=', 'a')]"
/>
<filter
string="Right to left"
domain="[('record_type', '=', 'b')]"
/>
<filter
string="Include past records"
context="{'active_test': False}"
/>
<group expand="0" string="Group By">
<filter string="Other Partner" context="{'group_by': 'other_partner_id'}"/>
<filter string="Relationship Type" context="{'group_by': 'type_id'}"/>
<filter
string="One Partner"
context="{'group_by': 'this_partner_id'}"
/>
<filter
string="Relationship Type"
context="{'group_by': 'type_selection_id'}"
/>
<filter
string="Other Partner"
context="{'group_by': 'other_partner_id'}"
/>
</group>
</search>
</field>
</record>
<record id="action_res_partner_relation_all" model="ir.actions.act_window">
<record
id="action_res_partner_relation_all"
model="ir.actions.act_window"
>
<field name="name">Relations</field>
<field name="res_model">res.partner.relation.all</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="tree_res_partner_relation_all"/>
<field name="search_view_id" ref="search_res_partner_relation_all"/>
<field name="domain">[('active', '=', True)]</field>
<field
name="search_view_id"
ref="search_res_partner_relation_all"
/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Record and track your partners' relations. Relations may be linked to other partners with a type either directly or inversely.
Record and track your partners' relations. Relations may
be linked to other partners with a type either directly
or inversely.
</p>
</field>
</record>

12
partner_relations/views/res_partner_relation_type.xml

@ -9,10 +9,11 @@
<field name="contact_type_left" />
<field name="contact_type_right" />
<field name="allow_self" />
<field name="symmetric" />
<field name="is_symmetric" />
</tree>
</field>
</record>
<record id="form_res_partner_relation_type" model="ir.ui.view">
<field name="model">res.partner.relation.type</field>
<field type="xml" name="arch">
@ -22,7 +23,7 @@
<group
string="Left side of relation"
name="left"
>
>
<field name="name" />
<field name="contact_type_left" />
<field name="partner_category_left" />
@ -30,8 +31,8 @@
<group
string="Right side of relation"
name="right"
attrs="{'invisible': [('symmetric', '=', True)]}"
>
attrs="{'invisible': [('is_symmetric', '=', True)]}"
>
<field name="name_inverse" />
<field name="contact_type_right" />
<field name="partner_category_right" />
@ -39,7 +40,8 @@
</group>
<group name="properties" string="Properties">
<field name="allow_self" />
<field name="symmetric" />
<field name="is_symmetric" />
<field name="handle_invalid_onchange" />
</group>
</sheet>
</form>

Loading…
Cancel
Save