You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

286 lines
11 KiB

# Copyright 2013-2018 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
"""Define the type of relations that can exist between partners."""
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.osv.expression import AND, OR
HANDLE_INVALID_ONCHANGE = [
('restrict',
_('Do not allow change that will result in invalid relations')),
('ignore',
_('Allow existing relations that do not fit changed conditions')),
('end',
_('End relations per today, if they do not fit changed conditions')),
('delete',
_('Delete relations that do not fit changed conditions')),
]
class ResPartnerRelationType(models.Model):
"""Model that defines relation types that might exist between partners"""
_name = 'res.partner.relation.type'
_description = 'Partner Relation Type'
_order = 'name'
name = fields.Char(
string='Name',
required=True,
translate=True,
)
name_inverse = fields.Char(
string='Inverse name',
required=True,
translate=True,
)
contact_type_left = fields.Selection(
selection='get_partner_types',
string='Left partner type',
)
contact_type_right = fields.Selection(
selection='get_partner_types',
string='Right partner type',
)
partner_category_left = fields.Many2one(
comodel_name='res.partner.category',
string='Left partner category',
)
partner_category_right = fields.Many2one(
comodel_name='res.partner.category',
string='Right partner category',
)
allow_self = fields.Boolean(
string='Reflexive',
help='This relation can be set up with the same partner left and '
'right',
default=False,
)
is_symmetric = fields.Boolean(
string='Symmetric',
help="This relation is the same from right to left as from left to"
" right",
default=False,
)
handle_invalid_onchange = fields.Selection(
selection=HANDLE_INVALID_ONCHANGE,
string='Invalid relation handling',
required=True,
default='restrict',
help="When adding relations criteria like partner type and category"
" are checked.\n"
"However when you change the criteria, there might be relations"
" that do not fit the new criteria.\n"
"Specify how this situation should be handled.",
)
@api.model
def get_partner_types(self):
"""A partner can be an organisation or an individual."""
# pylint: disable=no-self-use
return [
('c', _('Organisation')),
('p', _('Person')),
]
@api.model
def _end_active_relations(self, relations):
"""End the relations that are active.
If a relation is current, that is, if it has a start date
in the past and end date in the future (or no end date),
the end date will be set to the current date.
If a relation has a end date in the past, then it is inactive and
will not be modified.
:param relations: a recordset of relations (not necessarily all active)
"""
today = fields.Date.today()
for relation in relations:
if relation.date_start and relation.date_start >= today:
relation.unlink()
elif not relation.date_end or relation.date_end > today:
relation.write({'date_end': today})
@api.multi
def check_existing(self, vals):
"""Check wether records exist that do not fit new criteria."""
relation_model = self.env['res.partner.relation']
def get_type_condition(vals, side):
"""Add if needed check for contact type."""
fieldname1 = 'contact_type_%s' % side
fieldname2 = '%s_partner_id.is_company' % side
contact_type = fieldname1 in vals and vals[fieldname1] or False
if contact_type == 'c':
# Records that are not companies are invalid:
return [(fieldname2, '=', False)]
if contact_type == 'p':
# Records that are companies are invalid:
return [(fieldname2, '=', True)]
return []
def get_category_condition(vals, side):
"""Add if needed check for partner category."""
fieldname1 = 'partner_category_%s' % side
fieldname2 = '%s_partner_id.category_id' % side
category_id = fieldname1 in vals and vals[fieldname1] or False
if category_id:
# Records that do not have the specified category are invalid:
return [(fieldname2, 'not in', [category_id])]
return []
for this in self:
handling = (
'handle_invalid_onchange' in vals and
vals['handle_invalid_onchange'] or
this.handle_invalid_onchange
)
if handling == 'ignore':
continue
invalid_conditions = []
for side in ['left', 'right']:
invalid_conditions = OR([
invalid_conditions,
get_type_condition(vals, side),
])
invalid_conditions = OR([
invalid_conditions,
get_category_condition(vals, side),
])
if not invalid_conditions:
return
# only look at relations for this type
invalid_domain = AND([
[('type_id', '=', this.id)], invalid_conditions
])
invalid_relations = relation_model.with_context(
active_test=False
).search(invalid_domain)
if invalid_relations:
if handling == 'restrict':
raise ValidationError(
_('There are already relations not satisfying the'
' conditions for partner type or category.')
)
elif handling == 'delete':
invalid_relations.unlink()
else:
self._end_active_relations(invalid_relations)
def _get_reflexive_relations(self):
"""Get all reflexive relations for this relation type.
:return: a recordset of res.partner.relation.
"""
self.env.cr.execute(
"""
SELECT id FROM res_partner_relation
WHERE left_partner_id = right_partner_id
AND type_id = %(relation_type_id)s
""", {
'relation_type_id': self.id,
}
)
reflexive_relation_ids = [r[0] for r in self.env.cr.fetchall()]
return self.env['res.partner.relation'].browse(reflexive_relation_ids)
def _check_no_existing_reflexive_relations(self):
"""Check that no reflexive relation exists for these relation types."""
for relation_type in self:
relations = relation_type._get_reflexive_relations()
if relations:
raise ValidationError(
_("Reflexivity could not be disabled for the relation "
"type {relation_type}. There are existing reflexive "
"relations defined for the following partners: "
"{partners}").format(
relation_type=relation_type.display_name,
partners=relations.mapped(
'left_partner_id.display_name')))
def _delete_existing_reflexive_relations(self):
"""Delete existing reflexive relations for these relation types."""
for relation_type in self:
relations = relation_type._get_reflexive_relations()
relations.unlink()
def _end_active_reflexive_relations(self):
"""End active reflexive relations for these relation types."""
for relation_type in self:
reflexive_relations = relation_type._get_reflexive_relations()
self._end_active_relations(reflexive_relations)
def _handle_deactivation_of_allow_self(self):
"""Handle the deactivation of reflexivity on these relations types."""
restrict_relation_types = self.filtered(
lambda t: t.handle_invalid_onchange == 'restrict')
restrict_relation_types._check_no_existing_reflexive_relations()
delete_relation_types = self.filtered(
lambda t: t.handle_invalid_onchange == 'delete')
delete_relation_types._delete_existing_reflexive_relations()
end_relation_types = self.filtered(
lambda t: t.handle_invalid_onchange == 'end')
end_relation_types._end_active_reflexive_relations()
@api.multi
def _update_right_vals(self, vals):
"""Make sure that on symmetric relations, right vals follow left vals.
@attention: All fields ending in `_right` will have their values
replaced by the values of the fields whose names end
in `_left`.
"""
vals['name_inverse'] = vals.get('name', self.name)
# For all left keys in model, take value for right either from
# left key in vals, or if not present, from right key in self:
left_keys = [key for key in self._fields if key.endswith('_left')]
for left_key in left_keys:
right_key = left_key.replace('_left', '_right')
vals[right_key] = vals.get(left_key, self[left_key])
if hasattr(vals[right_key], 'id'):
vals[right_key] = vals[right_key].id
@api.model
def create(self, vals):
if vals.get('is_symmetric'):
self._update_right_vals(vals)
return super(ResPartnerRelationType, self).create(vals)
@api.multi
def write(self, vals):
"""Handle existing relations if conditions change."""
self.check_existing(vals)
for rec in self:
rec_vals = vals.copy()
if rec_vals.get('is_symmetric', rec.is_symmetric):
self._update_right_vals(rec_vals)
super(ResPartnerRelationType, rec).write(rec_vals)
allow_self_disabled = 'allow_self' in vals and not vals['allow_self']
if allow_self_disabled:
self._handle_deactivation_of_allow_self()
return True
@api.multi
def unlink(self):
"""Allow delete of relation type, even when connections exist.
Relations can be deleted if relation type allows it.
"""
relation_model = self.env['res.partner.relation']
for rec in self:
if rec.handle_invalid_onchange == 'delete':
# Automatically delete relations, so existing relations
# do not prevent unlink of relation type:
relations = relation_model.search([
('type_id', '=', rec.id),
])
relations.unlink()
return super(ResPartnerRelationType, self).unlink()