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.

211 lines
7.9 KiB

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