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

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