diff --git a/partner_relations/models/res_partner.py b/partner_relations/models/res_partner.py index 1833c1498..3545a299c 100644 --- a/partner_relations/models/res_partner.py +++ b/partner_relations/models/res_partner.py @@ -116,9 +116,6 @@ class ResPartner(models.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 [ '&', '|', diff --git a/partner_relations/models/res_partner_relation.py b/partner_relations/models/res_partner_relation.py index 2ccf0facd..0176fe9b2 100644 --- a/partner_relations/models/res_partner_relation.py +++ b/partner_relations/models/res_partner_relation.py @@ -2,7 +2,8 @@ # © 2013-2016 Therp BV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). """Store relations (connections) between partners.""" -from openerp import _, api, exceptions, fields, models +from openerp import _, api, fields, models +from openerp.exceptions import ValidationError class ResPartnerRelation(models.Model): @@ -53,11 +54,11 @@ class ResPartnerRelation(models.Model): def _check_dates(self): """End date should not be before start date, if not filled - :raises exceptions.Warning: When constraint is violated + :raises ValidationError: When constraint is violated """ if (self.date_start and self.date_end and self.date_start > self.date_end): - raise exceptions.Warning( + raise ValidationError( _('The starting date cannot be after the ending date.') ) @@ -66,7 +67,7 @@ class ResPartnerRelation(models.Model): def _check_partner_left(self): """Check left partner for required company or person - :raises exceptions.Warning: When constraint is violated + :raises ValidationError: When constraint is violated """ self._check_partner("left") @@ -75,7 +76,7 @@ class ResPartnerRelation(models.Model): def _check_partner_right(self): """Check right partner for required company or person - :raises exceptions.Warning: When constraint is violated + :raises ValidationError: When constraint is violated """ self._check_partner("right") @@ -84,20 +85,20 @@ class ResPartnerRelation(models.Model): """Check partner for required company or person, and for category :param str side: left or right - :raises exceptions.Warning: When constraint is violated + :raises ValidationError: When constraint is violated """ assert side in ['left', 'right'] ptype = getattr(self.type_id, "contact_type_%s" % side) partner = getattr(self, '%s_partner_id' % side) if ((ptype == 'c' and not partner.is_company) or (ptype == 'p' and partner.is_company)): - raise exceptions.Warning( + raise ValidationError( _('The %s partner is not applicable for this relation type.') % side ) category = getattr(self.type_id, "partner_category_%s" % side) if category and category.id not in partner.category_id.ids: - raise exceptions.Warning( + raise ValidationError( _('The %s partner does not have category %s.') % (side, category.name) ) @@ -107,11 +108,11 @@ class ResPartnerRelation(models.Model): def _check_not_with_self(self): """Not allowed to link partner to same partner - :raises exceptions.Warning: When constraint is violated + :raises ValidationError: When constraint is violated """ if self.left_partner_id == self.right_partner_id: if not (self.type_id and self.type_id.allow_self): - raise exceptions.Warning( + raise ValidationError( _('Partners cannot have a relation with themselves.') ) @@ -127,7 +128,7 @@ class ResPartnerRelation(models.Model): """Forbid multiple active relations of the same type between the same partners - :raises exceptions.Warning: When constraint is violated + :raises ValidationError: When constraint is violated """ # pylint: disable=no-member # pylint: disable=no-value-for-parameter @@ -150,6 +151,6 @@ class ResPartnerRelation(models.Model): ('date_start', '<=', self.date_end), ] if self.search(domain): - raise exceptions.Warning( + raise ValidationError( _('There is already a similar relation with overlapping dates') ) diff --git a/partner_relations/models/res_partner_relation_all.py b/partner_relations/models/res_partner_relation_all.py index 552c0efa4..a87b4f765 100644 --- a/partner_relations/models/res_partner_relation_all.py +++ b/partner_relations/models/res_partner_relation_all.py @@ -76,7 +76,7 @@ class ResPartnerRelationAll(models.AbstractModel): any_partner_id = fields.Many2many( comodel_name='res.partner', string='Partner', - compute='_compute_any_partner_id', + compute=lambda self: None, search='_search_any_partner_id' ) @@ -127,14 +127,6 @@ CREATE OR REPLACE VIEW %(table)s AS cr, context=context ) - @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.model def _search_any_partner_id(self, operator, value): """Search relation with partner, no matter on which side.""" @@ -165,8 +157,6 @@ CREATE OR REPLACE VIEW %(table)s AS 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: @@ -174,16 +164,17 @@ CREATE OR REPLACE VIEW %(table)s AS partner_model = self.env['res.partner'] partners_found = partner_model.search(test_domain, limit=1) if not partners_found: + warning['title'] = _('Error!') if partner: - message = _( - '%s partner incompatible with relation type.' % + warning['message'] = ( + _('%s partner incompatible with relation type.') % side.title() ) else: - message = _( - 'No %s partner available for relation type.' % side + warning['message'] = ( + _('No %s partner available for relation type.') % + side ) - warning = {'title': _('Error!'), 'message': message} return warning this_partner_domain = [] @@ -213,17 +204,17 @@ CREATE OR REPLACE VIEW %(table)s AS '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 = {} + if this_partner_domain: + warning = check_partner_domain( + self.this_partner_id, this_partner_domain, _('this') + ) + if not warning and other_partner_domain: warning = check_partner_domain( self.other_partner_id, other_partner_domain, _('other') ) - if warning: - result['warning'] = warning + if warning: + result['warning'] = warning return result @api.onchange( @@ -234,126 +225,25 @@ CREATE OR REPLACE VIEW %(table)s AS """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. + """If type_selection_id already selected, check wether it + is compatible with the computed type_selection_domain. An empty + selection can practically only occur in a practically empty + database, and will not lead to problems. Therefore not tested. """ warning = {} - if not type_selection_domain: + if not (type_selection_domain and self.type_selection_id): return warning - if self.type_selection_id: - test_domain = ( - [('id', '=', self.type_selection_id.id)] + - type_selection_domain - ) - else: - test_domain = type_selection_domain + test_domain = ( + [('id', '=', self.type_selection_id.id)] + + type_selection_domain + ) type_model = self.env['res.partner.relation.type.selection'] types_found = type_model.search(test_domain, limit=1) if not types_found: - 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 + warning['title'] = _('Error!') + warning['message'] = _( + 'Relation type incompatible with selected partner(s).' ) - 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 = [] @@ -362,8 +252,7 @@ CREATE OR REPLACE VIEW %(table)s AS '|', ('contact_type_this', '=', False), ('contact_type_this', '=', - self.this_partner_id.get_partner_type() - ), + self.this_partner_id.get_partner_type()), '|', ('partner_category_this', '=', False), ('partner_category_this', 'in', diff --git a/partner_relations/models/res_partner_relation_type.py b/partner_relations/models/res_partner_relation_type.py index 7ea1f94cc..7a8b324c6 100644 --- a/partner_relations/models/res_partner_relation_type.py +++ b/partner_relations/models/res_partner_relation_type.py @@ -4,6 +4,7 @@ """Define the type of relations that can exist between partners.""" from openerp import _, api, fields, models from openerp.exceptions import ValidationError +from openerp.osv.expression import AND, OR HANDLE_INVALID_ONCHANGE = [ @@ -98,6 +99,30 @@ class ResPartnerRelationType(models.Model): def check_existing(self, vals): """Check wether records exist that do not fit new criteria.""" relation_model = self.env['res.partner.relation'] + + def get_type_condition(vals, side): + """Add if needed check for contact type.""" + fieldname1 = 'contact_type_%s' % side + fieldname2 = '%s_partner_id.is_company' % side + contact_type = fieldname1 in vals and vals[fieldname1] or False + if contact_type == 'c': + # Records that are not companies are invalid: + return [(fieldname2, '=', False)] + if contact_type == 'p': + # Records that are companies are invalid: + return [(fieldname2, '=', True)] + return [] + + def get_category_condition(vals, side): + """Add if needed check for partner category.""" + fieldname1 = 'partner_category_%s' % side + fieldname2 = '%s_partner_id.category_id' % side + category_id = fieldname1 in vals and vals[fieldname1] or False + if category_id: + # Records that do not have the specified category are invalid: + return [(fieldname2, 'not in', [category_id])] + return [] + for rec in self: handling = ( 'handle_invalid_onchange' in vals and @@ -106,60 +131,22 @@ class ResPartnerRelationType(models.Model): ) if handling == 'ignore': continue + invalid_conditions = [] + for side in ['left', 'right']: + invalid_conditions = OR([ + invalid_conditions, + get_type_condition(vals, side), + ]) + invalid_conditions = OR([ + invalid_conditions, + get_category_condition(vals, side), + ]) + if not invalid_conditions: + return # only look at relations for this type - invalid_domain = [ - ('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_domain = AND([ + [('type_id', '=', rec.id)], invalid_conditions + ]) invalid_relations = relation_model.with_context( active_test=False ).search(invalid_domain) diff --git a/partner_relations/tests/__init__.py b/partner_relations/tests/__init__.py index 0c45f0703..272f6d49f 100644 --- a/partner_relations/tests/__init__.py +++ b/partner_relations/tests/__init__.py @@ -1 +1,7 @@ -from . import test_partner_relations +# -*- coding: utf-8 -*- +# Copyright 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import test_partner_relation_common +from . import test_partner_relation +from . import test_partner_relation_all +from . import test_partner_search diff --git a/partner_relations/tests/test_partner_relations.py b/partner_relations/tests/test_partner_relation.py similarity index 51% rename from partner_relations/tests/test_partner_relations.py rename to partner_relations/tests/test_partner_relation.py index 041a6d526..b3ba3bd7b 100644 --- a/partner_relations/tests/test_partner_relations.py +++ b/partner_relations/tests/test_partner_relation.py @@ -1,97 +1,26 @@ # -*- coding: utf-8 -*- -# Copyright 2015 Camptocamp SA # Copyright 2016 Therp BV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from datetime import date +from dateutil.relativedelta import relativedelta + from openerp import fields -from openerp.tests import common from openerp.exceptions import ValidationError +from .test_partner_relation_common import TestPartnerRelationCommon -class TestPartnerRelation(common.TransactionCase): - def setUp(self): - super(TestPartnerRelation, self).setUp() +class TestPartnerRelation(TestPartnerRelationCommon): - self.partner_model = self.env['res.partner'] - self.category_model = self.env['res.partner.category'] - self.type_model = self.env['res.partner.relation.type'] - self.selection_model = self.env['res.partner.relation.type.selection'] - self.relation_model = self.env['res.partner.relation'] - self.relation_all_model = self.env['res.partner.relation.all'] - self.partner_01_person = self.partner_model.create({ - 'name': 'Test User 1', - 'is_company': False, - 'ref': 'PR01', - }) - self.partner_02_company = self.partner_model.create({ - 'name': 'Test Company', - 'is_company': True, - 'ref': 'PR02', - }) - 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." + def test_selection_name_search(self): + """Test wether we can find type selection on reverse name.""" + selection_types = self.selection_model.name_search( + name=self.selection_person2company.name ) - assert self.selection_ngo2volunteer, ( - "Failed to create NGO to volunteer selection in setup." + self.assertTrue(selection_types) + self.assertTrue( + (self.selection_person2company.id, + self.selection_person2company.name) in selection_types ) def test_self_allowed(self): @@ -103,11 +32,13 @@ class TestPartnerRelation(common.TransactionCase): 'contact_type_right': 'p', 'allow_self': True }) - self.relation_model.create({ + self.assertTrue(type_allow) + reflexive_relation = self.relation_model.create({ 'type_id': type_allow.id, 'left_partner_id': self.partner_01_person.id, 'right_partner_id': self.partner_01_person.id, }) + self.assertTrue(reflexive_relation) def test_self_disallowed(self): """Test creating relation to same partner when disallowed. @@ -122,6 +53,7 @@ class TestPartnerRelation(common.TransactionCase): 'contact_type_right': 'p', 'allow_self': False }) + self.assertTrue(type_disallow) with self.assertRaises(ValidationError): self.relation_model.create({ 'type_id': type_disallow.id, @@ -142,6 +74,7 @@ class TestPartnerRelation(common.TransactionCase): 'contact_type_left': 'p', 'contact_type_right': 'p', }) + self.assertTrue(type_default) with self.assertRaises(ValidationError): self.relation_model.create({ 'type_id': type_default.id, @@ -162,90 +95,6 @@ class TestPartnerRelation(common.TransactionCase): 'right_partner_id': self.partner_02_company.id, }) - def test_searching(self): - """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.partner_model.search([ - ('search_relation_type_id', '=', relation.type_selection_id.id) - ]) - self.assertTrue(self.partner_02_company in partners) - partners = self.partner_model.search([ - ('search_relation_type_id', '!=', relation.type_selection_id.id) - ]) - self.assertTrue(self.partner_01_person in partners) - partners = self.partner_model.search([ - ('search_relation_type_id', '=', self.type_company2person.name) - ]) - self.assertTrue(self.partner_01_person in partners) - self.assertTrue(self.partner_02_company in partners) - partners = self.partner_model.search([ - ('search_relation_type_id', '=', 'unknown relation') - ]) - self.assertFalse(partners) - partners = self.partner_model.search([ - ('search_relation_partner_id', '=', self.partner_02_company.id), - ]) - self.assertTrue(self.partner_01_person in partners) - partners = self.partner_model.search([ - ('search_relation_date', '=', fields.Date.today()), - ]) - self.assertTrue(self.partner_01_person in partners) - self.assertTrue(self.partner_02_company in partners) - - def test_relation_all(self): - """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_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_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_partner_id()['domain'] - self.assertTrue( - ('contact_type_this', '=', 'c') in domain['type_selection_id'] - ) - relation_all_record.write({ - '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): """Test creating symmetric relation.""" # Start out with non symmetric relation: @@ -323,41 +172,18 @@ class TestPartnerRelation(common.TransactionCase): '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) + (type_school2student, + school2student, + school2student_inverse) = ( + self._create_relation_type_selection({ + 'name': 'school has student', + 'name_inverse': 'studies at school', + }) + ) # Second create relations based on those conditions. partner_school = self.partner_model.create({ 'name': 'Test School', @@ -375,20 +201,20 @@ class TestPartnerRelation(common.TransactionCase): 'ref': 'LS', }) relation_school2bart = self.relation_all_model.create({ - 'type_selection_id': school2student.id, 'this_partner_id': partner_school.id, + 'type_selection_id': school2student.id, 'other_partner_id': partner_bart.id, }) self.assertTrue(relation_school2bart) relation_school2lisa = self.relation_all_model.create({ - 'type_selection_id': school2student.id, 'this_partner_id': partner_school.id, + 'type_selection_id': school2student.id, 'other_partner_id': partner_lisa.id, }) self.assertTrue(relation_school2lisa) relation_bart2lisa = self.relation_all_model.create({ - 'type_selection_id': school2student.id, 'this_partner_id': partner_bart.id, + 'type_selection_id': school2student.id, 'other_partner_id': partner_lisa.id, }) self.assertTrue(relation_bart2lisa) @@ -421,6 +247,22 @@ class TestPartnerRelation(common.TransactionCase): partner_lisa.write({ 'category_id': [(4, category_student.id)], }) + # Future student to be deleted by end action: + partner_homer = self.partner_model.create({ + 'name': 'Homer Simpson', + 'is_company': False, + 'ref': 'HS', + 'category_id': [(4, category_student.id)], + }) + relation_lisa2homer = self.relation_all_model.create({ + 'this_partner_id': partner_lisa.id, + 'type_selection_id': school2student.id, + 'other_partner_id': partner_homer.id, + 'date_start': fields.Date.to_string( + date.today() + relativedelta(months=+6) + ), + }) + self.assertTrue(relation_lisa2homer) type_school2student.write({ 'handle_invalid_onchange': 'end', 'contact_type_left': 'c', @@ -429,8 +271,10 @@ class TestPartnerRelation(common.TransactionCase): relation_bart2lisa.date_end, fields.Date.today() ) + self.assertFalse(relation_lisa2homer.exists()) type_school2student.write({ 'handle_invalid_onchange': 'delete', 'contact_type_left': 'c', + 'contact_type_right': 'p', }) self.assertFalse(relation_bart2lisa.exists()) diff --git a/partner_relations/tests/test_partner_relation_all.py b/partner_relations/tests/test_partner_relation_all.py new file mode 100644 index 000000000..e2c48f46a --- /dev/null +++ b/partner_relations/tests/test_partner_relation_all.py @@ -0,0 +1,241 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp.exceptions import ValidationError + +from .test_partner_relation_common import TestPartnerRelationCommon + + +class TestPartnerRelation(TestPartnerRelationCommon): + + def setUp(self): + super(TestPartnerRelation, self).setUp() + + # Create a new relation type which will not have valid relations: + category_nobody = self.category_model.create({ + 'name': 'Nobody', + }) + (self.type_nobody, + self.selection_nobody, + self.selection_nobody_inverse) = ( + self._create_relation_type_selection({ + 'name': 'has relation with nobody', + 'name_inverse': 'nobody has relation with', + 'contact_type_left': 'c', + 'contact_type_right': 'p', + 'partner_category_left': category_nobody.id, + 'partner_category_right': category_nobody.id, + }) + ) + + def _get_empty_relation(self): + """Get empty relation record for onchange tests.""" + # Need English, because we will compare text + return self.relation_all_model.with_context(lang='en_US').new({}) + + def test_create_with_active_id(self): + """Test creation with this_partner_id from active_id.""" + # Check wether we can create connection from company to person, + # taking the particular company from the active records: + relation = self.relation_all_model.with_context( + active_id=self.partner_02_company.id, + active_ids=self.partner_02_company.ids, + ).create({ + 'other_partner_id': self.partner_01_person.id, + 'type_selection_id': self.selection_company2person.id, + }) + self.assertTrue(relation) + self.assertEqual(relation.this_partner_id, self.partner_02_company) + # Partner should have one relation now: + self.assertEqual(self.partner_01_person.relation_count, 1) + + def test_display_name(self): + """Test display name""" + relation = self._create_company2person_relation() + self.assertEqual( + relation.display_name, '%s %s %s' % ( + relation.this_partner_id.name, + relation.type_selection_id.name, + relation.other_partner_id.name, + ) + ) + + def test__regular_write(self): + """Test write with valid data.""" + relation = self._create_company2person_relation() + relation.write({ + 'date_start': '2014-09-01', + }) + relation.invalidate_cache(ids=relation.ids) + self.assertEqual(relation.date_start, '2014-09-01') + + def test_write_incompatible_dates(self): + """Test write with date_end before date_start.""" + relation = self._create_company2person_relation() + with self.assertRaises(ValidationError): + relation.write({ + 'date_start': '2016-09-01', + 'date_end': '2016-08-01', + }) + + def test_validate_overlapping_01(self): + """Test create overlapping with no start / end dates.""" + relation = self._create_company2person_relation() + with self.assertRaises(ValidationError): + # New relation with no start / end should give error + self.relation_all_model.create({ + 'this_partner_id': relation.this_partner_id.id, + 'type_selection_id': relation.type_selection_id.id, + 'other_partner_id': relation.other_partner_id.id, + }) + + def test_validate_overlapping_02(self): + """Test create overlapping with start / end dates.""" + relation = self.relation_all_model.create({ + 'this_partner_id': self.partner_02_company.id, + 'type_selection_id': self.selection_company2person.id, + 'other_partner_id': self.partner_01_person.id, + 'date_start': '2015-09-01', + 'date_end': '2016-08-31', + }) + # New relation with overlapping start / end should give error + with self.assertRaises(ValidationError): + self.relation_all_model.create({ + 'this_partner_id': relation.this_partner_id.id, + 'type_selection_id': relation.type_selection_id.id, + 'other_partner_id': relation.other_partner_id.id, + 'date_start': '2016-08-01', + 'date_end': '2017-07-30', + }) + + def test_validate_overlapping_03(self): + """Test create not overlapping.""" + relation = self.relation_all_model.create({ + 'this_partner_id': self.partner_02_company.id, + 'type_selection_id': self.selection_company2person.id, + 'other_partner_id': self.partner_01_person.id, + 'date_start': '2015-09-01', + 'date_end': '2016-08-31', + }) + relation_another_record = self.relation_all_model.create({ + 'this_partner_id': relation.this_partner_id.id, + 'type_selection_id': relation.type_selection_id.id, + 'other_partner_id': relation.other_partner_id.id, + 'date_start': '2016-09-01', + 'date_end': '2017-08-31', + }) + self.assertTrue(relation_another_record) + + def test_inverse_record(self): + """Test creation of inverse record.""" + relation = self._create_company2person_relation() + inverse_relation = self.relation_all_model.search([ + ('this_partner_id', '=', relation.other_partner_id.id), + ('other_partner_id', '=', relation.this_partner_id.id), + ]) + self.assertEqual(len(inverse_relation), 1) + self.assertEqual( + inverse_relation.type_selection_id.name, + self.selection_person2company.name + ) + + def test_inverse_creation(self): + """Test creation of record through inverse selection.""" + relation = self.relation_all_model.create({ + 'this_partner_id': self.partner_01_person.id, + 'type_selection_id': self.selection_person2company.id, + 'other_partner_id': self.partner_02_company.id, + }) + # Check wether display name is what we should expect: + self.assertEqual( + relation.display_name, '%s %s %s' % ( + self.partner_01_person.name, + self.selection_person2company.name, + self.partner_02_company.name, + ) + ) + + def test_unlink(self): + """Unlinking derived relation should unlink base relation.""" + # Check wether underlying record is removed when record is removed: + relation = self._create_company2person_relation() + base_relation = relation.relation_id + relation.unlink() + self.assertFalse(base_relation.exists()) + + def test_on_change_type_selection(self): + """Test on_change_type_selection.""" + # 1. Test call with empty relation + relation_empty = self._get_empty_relation() + result = relation_empty.onchange_type_selection_id() + self.assertTrue('domain' in result) + self.assertFalse('warning' in result) + self.assertTrue('this_partner_id' in result['domain']) + self.assertFalse(result['domain']['this_partner_id']) + self.assertTrue('other_partner_id' in result['domain']) + self.assertFalse(result['domain']['other_partner_id']) + # 2. Test call with company 2 person relation + relation = self._create_company2person_relation() + domain = relation.onchange_type_selection_id()['domain'] + self.assertTrue( + ('is_company', '=', False) in domain['other_partner_id'] + ) + # 3. Test with relation needing categories. + relation_ngo_volunteer = self.relation_all_model.create({ + 'this_partner_id': self.partner_03_ngo.id, + 'type_selection_id': self.selection_ngo2volunteer.id, + 'other_partner_id': self.partner_04_volunteer.id, + }) + domain = relation_ngo_volunteer.onchange_type_selection_id()['domain'] + self.assertTrue( + ('category_id', 'in', [self.category_01_ngo.id]) in + domain['this_partner_id'] + ) + self.assertTrue( + ('category_id', 'in', [self.category_02_volunteer.id]) in + domain['other_partner_id'] + ) + # 4. Test with invalid or impossible combinations + relation_nobody = self._get_empty_relation() + with self.env.do_in_draft(): + relation_nobody.type_selection_id = self.selection_nobody + warning = relation_nobody.onchange_type_selection_id()['warning'] + self.assertTrue('message' in warning) + self.assertTrue('No this partner available' in warning['message']) + with self.env.do_in_draft(): + relation_nobody.this_partner_id = self.partner_02_company + warning = relation_nobody.onchange_type_selection_id()['warning'] + self.assertTrue('message' in warning) + self.assertTrue('incompatible' in warning['message']) + # Allow left partner and check message for other partner: + self.type_nobody.write({ + 'partner_category_left': False, + }) + self.selection_nobody.invalidate_cache(ids=self.selection_nobody.ids) + warning = relation_nobody.onchange_type_selection_id()['warning'] + self.assertTrue('message' in warning) + self.assertTrue('No other partner available' in warning['message']) + + def test_on_change_partner_id(self): + """Test on_change_partner_id.""" + # 1. Test call with empty relation + relation_empty = self._get_empty_relation() + result = relation_empty.onchange_partner_id() + self.assertTrue('domain' in result) + self.assertFalse('warning' in result) + self.assertTrue('type_selection_id' in result['domain']) + self.assertFalse(result['domain']['type_selection_id']) + # 2. Test call with company 2 person relation + relation = self._create_company2person_relation() + domain = relation.onchange_partner_id()['domain'] + self.assertTrue( + ('contact_type_this', '=', 'c') in domain['type_selection_id'] + ) + # 3. Test with invalid or impossible combinations + relation_nobody = self._get_empty_relation() + with self.env.do_in_draft(): + relation_nobody.this_partner_id = self.partner_02_company + relation_nobody.type_selection_id = self.selection_nobody + warning = relation_nobody.onchange_partner_id()['warning'] + self.assertTrue('message' in warning) + self.assertTrue('incompatible' in warning['message']) diff --git a/partner_relations/tests/test_partner_relation_common.py b/partner_relations/tests/test_partner_relation_common.py new file mode 100644 index 000000000..94249c6c7 --- /dev/null +++ b/partner_relations/tests/test_partner_relation_common.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp.tests import common + + +class TestPartnerRelationCommon(common.TransactionCase): + + def setUp(self): + super(TestPartnerRelationCommon, self).setUp() + + self.partner_model = self.env['res.partner'] + self.category_model = self.env['res.partner.category'] + self.type_model = self.env['res.partner.relation.type'] + self.selection_model = self.env['res.partner.relation.type.selection'] + self.relation_model = self.env['res.partner.relation'] + self.relation_all_model = self.env['res.partner.relation.all'] + self.partner_01_person = self.partner_model.create({ + 'name': 'Test User 1', + 'is_company': False, + 'ref': 'PR01', + }) + self.partner_02_company = self.partner_model.create({ + 'name': 'Test Company', + 'is_company': True, + 'ref': 'PR02', + }) + # Create partners with specific categories: + self.category_01_ngo = self.category_model.create({ + 'name': 'NGO', + }) + self.partner_03_ngo = self.partner_model.create({ + 'name': 'Test NGO', + 'is_company': True, + 'ref': 'PR03', + 'category_id': [(4, self.category_01_ngo.id)], + }) + self.category_02_volunteer = self.category_model.create({ + 'name': 'Volunteer', + }) + self.partner_04_volunteer = self.partner_model.create({ + 'name': 'Test Volunteer', + 'is_company': False, + 'ref': 'PR04', + 'category_id': [(4, self.category_02_volunteer.id)], + }) + # Create a new relation type withouth categories: + (self.type_company2person, + self.selection_company2person, + self.selection_person2company) = ( + self._create_relation_type_selection({ + 'name': 'mixed', + 'name_inverse': 'mixed_inverse', + 'contact_type_left': 'c', + 'contact_type_right': 'p', + }) + ) + # Create a new relation type with categories: + (self.type_ngo2volunteer, + self.selection_ngo2volunteer, + self.selection_volunteer2ngo) = ( + self._create_relation_type_selection({ + 'name': 'NGO has volunteer', + 'name_inverse': 'volunteer works for NGO', + 'contact_type_left': 'c', + 'contact_type_right': 'p', + 'partner_category_left': self.category_01_ngo.id, + 'partner_category_right': self.category_02_volunteer.id, + }) + ) + + def _create_relation_type_selection(self, vals): + """Create relation type and return this with selection types.""" + assert 'name' in vals, ( + "Name missing in vals to create relation type. Vals: %s." + % vals + ) + assert 'name' in vals, ( + "Name_inverse missing in vals to create relation type. Vals: %s." + % vals + ) + new_type = self.type_model.create(vals) + self.assertTrue( + new_type, + msg="No relation type created with vals %s." % vals + ) + selection_types = self.selection_model.search([ + ('type_id', '=', new_type.id), + ]) + for st in selection_types: + if st.is_inverse: + inverse_type_selection = st + else: + type_selection = st + self.assertTrue( + inverse_type_selection, + msg="Failed to find inverse type selection based on" + " relation type created with vals %s." % vals + ) + self.assertTrue( + type_selection, + msg="Failed to find type selection based on" + " relation type created with vals %s." % vals + ) + return (new_type, type_selection, inverse_type_selection) + + def _create_company2person_relation(self): + """Utility function to get a relation from company 2 partner.""" + return self.relation_all_model.create({ + 'type_selection_id': self.selection_company2person.id, + 'this_partner_id': self.partner_02_company.id, + 'other_partner_id': self.partner_01_person.id, + }) diff --git a/partner_relations/tests/test_partner_search.py b/partner_relations/tests/test_partner_search.py new file mode 100644 index 000000000..153b35b78 --- /dev/null +++ b/partner_relations/tests/test_partner_search.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 Camptocamp SA +# Copyright 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import fields +from openerp.exceptions import ValidationError + +from .test_partner_relation_common import TestPartnerRelationCommon + + +class TestPartnerSearch(TestPartnerRelationCommon): + + def test_search_relation_type(self): + """Test searching on relation type.""" + relation = self._create_company2person_relation() + partners = self.partner_model.search([ + ('search_relation_type_id', '=', relation.type_selection_id.id) + ]) + self.assertTrue(self.partner_02_company in partners) + partners = self.partner_model.search([ + ('search_relation_type_id', '!=', relation.type_selection_id.id) + ]) + self.assertTrue(self.partner_01_person in partners) + partners = self.partner_model.search([ + ('search_relation_type_id', '=', self.type_company2person.name) + ]) + self.assertTrue(self.partner_01_person in partners) + self.assertTrue(self.partner_02_company in partners) + partners = self.partner_model.search([ + ('search_relation_type_id', '=', 'unknown relation') + ]) + self.assertFalse(partners) + # Check error with invalid search operator: + with self.assertRaises(ValidationError): + partners = self.partner_model.search([ + ('search_relation_type_id', 'child_of', 'some parent') + ]) + + def test_search_relation_partner(self): + """Test searching on related partner.""" + self._create_company2person_relation() + partners = self.partner_model.search([ + ('search_relation_partner_id', '=', self.partner_02_company.id), + ]) + self.assertTrue(self.partner_01_person in partners) + + def test_search_relation_date(self): + """Test searching on relations valid on a certain date.""" + self._create_company2person_relation() + partners = self.partner_model.search([ + ('search_relation_date', '=', fields.Date.today()), + ]) + self.assertTrue(self.partner_01_person in partners) + self.assertTrue(self.partner_02_company in partners) + + def test_search_any_partner(self): + """Test searching for partner left or right.""" + self._create_company2person_relation() + both_relations = self.relation_all_model.search([ + ('any_partner_id', '=', self.partner_02_company.id), + ]) + self.assertEqual(len(both_relations), 2) + + def test_search_partner_category(self): + """Test searching for partners related to partners having category.""" + relation_ngo_volunteer = self.relation_all_model.create({ + 'this_partner_id': self.partner_03_ngo.id, + 'type_selection_id': self.selection_ngo2volunteer.id, + 'other_partner_id': self.partner_04_volunteer.id, + }) + self.assertTrue(relation_ngo_volunteer) + partners = self.partner_model.search([ + ('search_relation_partner_category_id', '=', + self.category_02_volunteer.id) + ]) + self.assertTrue(self.partner_03_ngo in partners)