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

  1. # Copyright 2013-2018 Therp BV <https://therp.nl>.
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  3. """Define the type of relations that can exist between partners."""
  4. from odoo import _, api, fields, models
  5. from odoo.exceptions import ValidationError
  6. from odoo.osv.expression import AND, OR
  7. HANDLE_INVALID_ONCHANGE = [
  8. ("restrict", _("Do not allow change that will result in invalid relations")),
  9. ("ignore", _("Allow existing relations that do not fit changed conditions")),
  10. ("end", _("End relations per today, if they do not fit changed conditions")),
  11. ("delete", _("Delete relations that do not fit changed conditions")),
  12. ]
  13. class ResPartnerRelationType(models.Model):
  14. """Model that defines relation types that might exist between partners"""
  15. _name = "res.partner.relation.type"
  16. _description = "Partner Relation Type"
  17. _order = "name"
  18. name = fields.Char(string="Name", required=True, translate=True)
  19. name_inverse = fields.Char(string="Inverse name", required=True, translate=True)
  20. contact_type_left = fields.Selection(
  21. selection="get_partner_types", string="Left partner type"
  22. )
  23. contact_type_right = fields.Selection(
  24. selection="get_partner_types", string="Right partner type"
  25. )
  26. partner_category_left = fields.Many2one(
  27. comodel_name="res.partner.category", string="Left partner category"
  28. )
  29. partner_category_right = fields.Many2one(
  30. comodel_name="res.partner.category", string="Right partner category"
  31. )
  32. allow_self = fields.Boolean(
  33. string="Reflexive",
  34. help="This relation can be set up with the same partner left and " "right",
  35. default=False,
  36. )
  37. is_symmetric = fields.Boolean(
  38. string="Symmetric",
  39. help="This relation is the same from right to left as from left to" " right",
  40. default=False,
  41. )
  42. handle_invalid_onchange = fields.Selection(
  43. selection=HANDLE_INVALID_ONCHANGE,
  44. string="Invalid relation handling",
  45. required=True,
  46. default="restrict",
  47. help="When adding relations criteria like partner type and category"
  48. " are checked.\n"
  49. "However when you change the criteria, there might be relations"
  50. " that do not fit the new criteria.\n"
  51. "Specify how this situation should be handled.",
  52. )
  53. @api.model
  54. def get_partner_types(self):
  55. """A partner can be an organisation or an individual."""
  56. # pylint: disable=no-self-use
  57. return [("c", _("Organisation")), ("p", _("Person"))]
  58. @api.model
  59. def _end_active_relations(self, relations):
  60. """End the relations that are active.
  61. If a relation is current, that is, if it has a start date
  62. in the past and end date in the future (or no end date),
  63. the end date will be set to the current date.
  64. If a relation has a end date in the past, then it is inactive and
  65. will not be modified.
  66. :param relations: a recordset of relations (not necessarily all active)
  67. """
  68. today = fields.Date.today()
  69. for relation in relations:
  70. if relation.date_start and relation.date_start >= today:
  71. relation.unlink()
  72. elif not relation.date_end or relation.date_end > today:
  73. relation.write({"date_end": today})
  74. def check_existing(self, vals):
  75. """Check wether records exist that do not fit new criteria."""
  76. relation_model = self.env["res.partner.relation"]
  77. def get_type_condition(vals, side):
  78. """Add if needed check for contact type."""
  79. fieldname1 = "contact_type_%s" % side
  80. fieldname2 = "%s_partner_id.is_company" % side
  81. contact_type = fieldname1 in vals and vals[fieldname1] or False
  82. if contact_type == "c":
  83. # Records that are not companies are invalid:
  84. return [(fieldname2, "=", False)]
  85. if contact_type == "p":
  86. # Records that are companies are invalid:
  87. return [(fieldname2, "=", True)]
  88. return []
  89. def get_category_condition(vals, side):
  90. """Add if needed check for partner category."""
  91. fieldname1 = "partner_category_%s" % side
  92. fieldname2 = "%s_partner_id.category_id" % side
  93. category_id = fieldname1 in vals and vals[fieldname1] or False
  94. if category_id:
  95. # Records that do not have the specified category are invalid:
  96. return [(fieldname2, "not in", [category_id])]
  97. return []
  98. for this in self:
  99. handling = (
  100. "handle_invalid_onchange" in vals
  101. and vals["handle_invalid_onchange"]
  102. or this.handle_invalid_onchange
  103. )
  104. if handling == "ignore":
  105. continue
  106. invalid_conditions = []
  107. for side in ["left", "right"]:
  108. invalid_conditions = OR(
  109. [invalid_conditions, get_type_condition(vals, side)]
  110. )
  111. invalid_conditions = OR(
  112. [invalid_conditions, get_category_condition(vals, side)]
  113. )
  114. if not invalid_conditions:
  115. return
  116. # only look at relations for this type
  117. invalid_domain = AND([[("type_id", "=", this.id)], invalid_conditions])
  118. invalid_relations = relation_model.with_context(active_test=False).search(
  119. invalid_domain
  120. )
  121. if invalid_relations:
  122. if handling == "restrict":
  123. raise ValidationError(
  124. _(
  125. "There are already relations not satisfying the"
  126. " conditions for partner type or category."
  127. )
  128. )
  129. elif handling == "delete":
  130. invalid_relations.unlink()
  131. else:
  132. self._end_active_relations(invalid_relations)
  133. def _get_reflexive_relations(self):
  134. """Get all reflexive relations for this relation type.
  135. :return: a recordset of res.partner.relation.
  136. """
  137. self.env.cr.execute(
  138. """
  139. SELECT id FROM res_partner_relation
  140. WHERE left_partner_id = right_partner_id
  141. AND type_id = %(relation_type_id)s
  142. """,
  143. {"relation_type_id": self.id},
  144. )
  145. reflexive_relation_ids = [r[0] for r in self.env.cr.fetchall()]
  146. return self.env["res.partner.relation"].browse(reflexive_relation_ids)
  147. def _check_no_existing_reflexive_relations(self):
  148. """Check that no reflexive relation exists for these relation types."""
  149. for relation_type in self:
  150. relations = relation_type._get_reflexive_relations()
  151. if relations:
  152. raise ValidationError(
  153. _(
  154. "Reflexivity could not be disabled for the relation "
  155. "type {relation_type}. There are existing reflexive "
  156. "relations defined for the following partners: "
  157. "{partners}"
  158. ).format(
  159. relation_type=relation_type.display_name,
  160. partners=relations.mapped("left_partner_id.display_name"),
  161. )
  162. )
  163. def _delete_existing_reflexive_relations(self):
  164. """Delete existing reflexive relations for these relation types."""
  165. for relation_type in self:
  166. relations = relation_type._get_reflexive_relations()
  167. relations.unlink()
  168. def _end_active_reflexive_relations(self):
  169. """End active reflexive relations for these relation types."""
  170. for relation_type in self:
  171. reflexive_relations = relation_type._get_reflexive_relations()
  172. self._end_active_relations(reflexive_relations)
  173. def _handle_deactivation_of_allow_self(self):
  174. """Handle the deactivation of reflexivity on these relations types."""
  175. restrict_relation_types = self.filtered(
  176. lambda t: t.handle_invalid_onchange == "restrict"
  177. )
  178. restrict_relation_types._check_no_existing_reflexive_relations()
  179. delete_relation_types = self.filtered(
  180. lambda t: t.handle_invalid_onchange == "delete"
  181. )
  182. delete_relation_types._delete_existing_reflexive_relations()
  183. end_relation_types = self.filtered(lambda t: t.handle_invalid_onchange == "end")
  184. end_relation_types._end_active_reflexive_relations()
  185. def _update_right_vals(self, vals):
  186. """Make sure that on symmetric relations, right vals follow left vals.
  187. @attention: All fields ending in `_right` will have their values
  188. replaced by the values of the fields whose names end
  189. in `_left`.
  190. """
  191. vals["name_inverse"] = vals.get("name", self.name)
  192. # For all left keys in model, take value for right either from
  193. # left key in vals, or if not present, from right key in self:
  194. left_keys = [key for key in self._fields if key.endswith("_left")]
  195. for left_key in left_keys:
  196. right_key = left_key.replace("_left", "_right")
  197. vals[right_key] = vals.get(left_key, self[left_key])
  198. if hasattr(vals[right_key], "id"):
  199. vals[right_key] = vals[right_key].id
  200. @api.model
  201. def create(self, vals):
  202. if vals.get("is_symmetric"):
  203. self._update_right_vals(vals)
  204. return super(ResPartnerRelationType, self).create(vals)
  205. def write(self, vals):
  206. """Handle existing relations if conditions change."""
  207. self.check_existing(vals)
  208. for rec in self:
  209. rec_vals = vals.copy()
  210. if rec_vals.get("is_symmetric", rec.is_symmetric):
  211. self._update_right_vals(rec_vals)
  212. super(ResPartnerRelationType, rec).write(rec_vals)
  213. allow_self_disabled = "allow_self" in vals and not vals["allow_self"]
  214. if allow_self_disabled:
  215. self._handle_deactivation_of_allow_self()
  216. return True
  217. def unlink(self):
  218. """Allow delete of relation type, even when connections exist.
  219. Relations can be deleted if relation type allows it.
  220. """
  221. relation_model = self.env["res.partner.relation"]
  222. for rec in self:
  223. if rec.handle_invalid_onchange == "delete":
  224. # Automatically delete relations, so existing relations
  225. # do not prevent unlink of relation type:
  226. relations = relation_model.search([("type_id", "=", rec.id)])
  227. relations.unlink()
  228. return super(ResPartnerRelationType, self).unlink()