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.
 
 

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