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
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()
|