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.

376 lines
14 KiB

  1. # -*- coding: utf-8 -*-
  2. '''Define model res.partner.relation'''
  3. ##############################################################################
  4. #
  5. # OpenERP, Open Source Management Solution
  6. # This module copyright (C) 2013 Therp BV (<http://therp.nl>).
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU Affero General Public License as
  10. # published by the Free Software Foundation, either version 3 of the
  11. # License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Affero General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Affero General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. ##############################################################################
  22. from openerp import osv, models, fields, api, exceptions, _
  23. from . import get_partner_type
  24. class ResPartnerRelation(models.Model):
  25. '''Model res.partner.relation is used to describe all links or relations
  26. between partners in the database.
  27. In many parts of the code we have to know whether the active partner is
  28. the left partner, or the right partner. If the active partner is the
  29. right partner we have to show the inverse name.
  30. Because the active partner is crucial for the working of partner
  31. relationships, we make sure on the res.partner model that the partner id
  32. is set in the context where needed.
  33. '''
  34. _name = 'res.partner.relation'
  35. _description = 'Partner relation'
  36. _order = 'active desc, date_start desc, date_end desc'
  37. def _search_any_partner_id(self, operator, value):
  38. return [
  39. '|',
  40. ('left_partner_id', operator, value),
  41. ('right_partner_id', operator, value),
  42. ]
  43. def _get_computed_fields(
  44. self, cr, uid, ids, field_names, arg, context=None):
  45. '''Return a dictionary of dictionaries, with for every partner for
  46. ids, the computed values.'''
  47. def get_values(self, dummy_field_names, dummy_arg, context=None):
  48. '''Get computed values for record'''
  49. values = {}
  50. on_right_partner = self._on_right_partner(self.right_partner_id.id)
  51. # type_selection_id
  52. values['type_selection_id'] = (
  53. ((self.type_id.id) * 10) + (on_right_partner and 1 or 0))
  54. # partner_id_display
  55. values['partner_id_display'] = (
  56. self.left_partner_id.id
  57. if on_right_partner
  58. else self.right_partner_id.id
  59. )
  60. return values
  61. return dict([
  62. (i.id, get_values(i, field_names, arg, context=context))
  63. for i in self.browse(cr, uid, ids, context=context)
  64. ])
  65. _columns = {
  66. 'type_selection_id': osv.fields.function(
  67. _get_computed_fields,
  68. multi="computed_fields",
  69. fnct_inv=lambda *args: None,
  70. type='many2one', obj='res.partner.relation.type.selection',
  71. string='Type',
  72. ),
  73. 'partner_id_display': osv.fields.function(
  74. _get_computed_fields,
  75. multi="computed_fields",
  76. fnct_inv=lambda *args: None,
  77. type='many2one', obj='res.partner',
  78. string='Partner'
  79. ),
  80. }
  81. allow_self = fields.Boolean(related='type_id.allow_self')
  82. left_contact_type = fields.Selection(
  83. lambda s: s.env['res.partner.relation.type']._get_partner_types(),
  84. 'Left Partner Type',
  85. compute='_get_partner_type_any',
  86. store=True,
  87. )
  88. right_contact_type = fields.Selection(
  89. lambda s: s.env['res.partner.relation.type']._get_partner_types(),
  90. 'Right Partner Type',
  91. compute='_get_partner_type_any',
  92. store=True,
  93. )
  94. any_partner_id = fields.Many2many(
  95. 'res.partner',
  96. string='Partner',
  97. compute='_get_partner_type_any',
  98. search='_search_any_partner_id'
  99. )
  100. left_partner_id = fields.Many2one(
  101. 'res.partner',
  102. string='Source Partner',
  103. required=True,
  104. auto_join=True,
  105. ondelete='cascade',
  106. )
  107. right_partner_id = fields.Many2one(
  108. 'res.partner',
  109. string='Destination Partner',
  110. required=True,
  111. auto_join=True,
  112. ondelete='cascade',
  113. )
  114. type_id = fields.Many2one(
  115. 'res.partner.relation.type',
  116. string='Type',
  117. required=True,
  118. auto_join=True,
  119. )
  120. date_start = fields.Date('Starting date')
  121. date_end = fields.Date('Ending date')
  122. active = fields.Boolean('Active', default=True)
  123. @api.one
  124. @api.depends('left_partner_id', 'right_partner_id')
  125. def _get_partner_type_any(self):
  126. self.left_contact_type = get_partner_type(self.left_partner_id)
  127. self.right_contact_type = get_partner_type(self.right_partner_id)
  128. self.any_partner_id = self.left_partner_id + self.right_partner_id
  129. def _on_right_partner(self, cr, uid, right_partner_id, context=None):
  130. '''Determine wether functions are called in a situation where the
  131. active partner is the right partner. Default False!
  132. '''
  133. if (context and 'active_ids' in context and
  134. right_partner_id in context.get('active_ids', [])):
  135. return True
  136. return False
  137. def _correct_vals(self, vals):
  138. """Fill type and left and right partner id, according to whether
  139. we have a normal relation type or an inverse relation type
  140. """
  141. vals = vals.copy()
  142. # If type_selection_id ends in 1, it is a reverse relation type
  143. if 'type_selection_id' in vals:
  144. prts_model = self.env['res.partner.relation.type.selection']
  145. type_selection_id = vals['type_selection_id']
  146. (type_id, is_reverse) = (
  147. prts_model.browse(type_selection_id).
  148. get_type_from_selection_id()
  149. )
  150. vals['type_id'] = type_id
  151. if self._context.get('active_id'):
  152. if is_reverse:
  153. vals['right_partner_id'] = self._context['active_id']
  154. else:
  155. vals['left_partner_id'] = self._context['active_id']
  156. if vals.get('partner_id_display'):
  157. if is_reverse:
  158. vals['left_partner_id'] = vals['partner_id_display']
  159. else:
  160. vals['right_partner_id'] = vals['partner_id_display']
  161. if vals.get('other_partner_id'):
  162. if is_reverse:
  163. vals['left_partner_id'] = vals['other_partner_id']
  164. else:
  165. vals['right_partner_id'] = vals['other_partner_id']
  166. del vals['other_partner_id']
  167. if vals.get('contact_type'):
  168. del vals['contact_type']
  169. return vals
  170. @api.multi
  171. def write(self, vals):
  172. """Override write to correct values, before being stored."""
  173. vals = self._correct_vals(vals)
  174. return super(ResPartnerRelation, self).write(vals)
  175. @api.model
  176. def create(self, vals):
  177. """Override create to correct values, before being stored."""
  178. vals = self._correct_vals(vals)
  179. return super(ResPartnerRelation, self).create(vals)
  180. def on_change_type_selection_id(
  181. self, cr, uid, dummy_ids, type_selection_id, context=None):
  182. '''Set domain on partner_id_display, when selection a relation type'''
  183. result = {
  184. 'domain': {'partner_id_display': []},
  185. 'value': {'type_id': False}
  186. }
  187. if not type_selection_id:
  188. return result
  189. prts_model = self.pool['res.partner.relation.type.selection']
  190. type_model = self.pool['res.partner.relation.type']
  191. (type_id, is_reverse) = (
  192. prts_model.get_type_from_selection_id(
  193. cr, uid, type_selection_id)
  194. )
  195. result['value']['type_id'] = type_id
  196. type_obj = type_model.browse(cr, uid, type_id, context=context)
  197. partner_domain = []
  198. check_contact_type = type_obj.contact_type_right
  199. check_partner_category = (
  200. type_obj.partner_category_right and
  201. type_obj.partner_category_right.id
  202. )
  203. if is_reverse:
  204. # partner_id_display is left partner
  205. check_contact_type = type_obj.contact_type_left
  206. check_partner_category = (
  207. type_obj.partner_category_left and
  208. type_obj.partner_category_left.id
  209. )
  210. if check_contact_type == 'c':
  211. partner_domain.append(('is_company', '=', True))
  212. if check_contact_type == 'p':
  213. partner_domain.append(('is_company', '=', False))
  214. if check_partner_category:
  215. partner_domain.append(
  216. ('category_id', 'child_of', check_partner_category))
  217. result['domain']['partner_id_display'] = partner_domain
  218. return result
  219. @api.one
  220. @api.constrains('date_start', 'date_end')
  221. def _check_dates(self):
  222. """End date should not be before start date, if not filled
  223. :raises exceptions.Warning: When constraint is violated
  224. """
  225. if (self.date_start and self.date_end and
  226. self.date_start > self.date_end):
  227. raise exceptions.Warning(
  228. _('The starting date cannot be after the ending date.')
  229. )
  230. @api.one
  231. @api.constrains('left_partner_id', 'type_id')
  232. def _check_partner_type_left(self):
  233. """Check left partner for required company or person
  234. :raises exceptions.Warning: When constraint is violated
  235. """
  236. self._check_partner_type("left")
  237. @api.one
  238. @api.constrains('right_partner_id', 'type_id')
  239. def _check_partner_type_right(self):
  240. """Check right partner for required company or person
  241. :raises exceptions.Warning: When constraint is violated
  242. """
  243. self._check_partner_type("right")
  244. @api.one
  245. def _check_partner_type(self, side):
  246. """Check partner to left or right for required company or person
  247. :param str side: left or right
  248. :raises exceptions.Warning: When constraint is violated
  249. """
  250. assert side in ['left', 'right']
  251. ptype = getattr(self.type_id, "contact_type_%s" % side)
  252. company = getattr(self, '%s_partner_id' % side).is_company
  253. if (ptype == 'c' and not company) or (ptype == 'p' and company):
  254. raise exceptions.Warning(
  255. _('The %s partner is not applicable for this relation type.') %
  256. side
  257. )
  258. @api.one
  259. @api.constrains('left_partner_id', 'right_partner_id')
  260. def _check_not_with_self(self):
  261. """Not allowed to link partner to same partner
  262. :raises exceptions.Warning: When constraint is violated
  263. """
  264. if self.left_partner_id == self.right_partner_id:
  265. if not self.allow_self:
  266. raise exceptions.Warning(
  267. _('Partners cannot have a relation with themselves.')
  268. )
  269. @api.one
  270. @api.constrains('left_partner_id', 'right_partner_id', 'active')
  271. def _check_relation_uniqueness(self):
  272. """Forbid multiple active relations of the same type between the same
  273. partners
  274. :raises exceptions.Warning: When constraint is violated
  275. """
  276. if not self.active:
  277. return
  278. domain = [
  279. ('type_id', '=', self.type_id.id),
  280. ('active', '=', True),
  281. ('id', '!=', self.id),
  282. ('left_partner_id', '=', self.left_partner_id.id),
  283. ('right_partner_id', '=', self.right_partner_id.id),
  284. ]
  285. if self.date_start:
  286. domain += ['|', ('date_end', '=', False),
  287. ('date_end', '>=', self.date_start)]
  288. if self.date_end:
  289. domain += ['|', ('date_start', '=', False),
  290. ('date_start', '<=', self.date_end)]
  291. if self.search(domain):
  292. raise exceptions.Warning(
  293. _('There is already a similar relation with overlapping dates')
  294. )
  295. def get_action_related_partners(self, cr, uid, ids, context=None):
  296. '''return a window action showing a list of partners taking part in the
  297. relations names by ids. Context key 'partner_relations_show_side'
  298. determines if we show 'left' side, 'right' side or 'all' (default)
  299. partners.
  300. If active_model is res.partner.relation.all, left=this and
  301. right=other'''
  302. if context is None:
  303. context = {}
  304. field_names = {}
  305. if context.get('active_model', self._name) == self._name:
  306. field_names = {
  307. 'left': ['left'],
  308. 'right': ['right'],
  309. 'all': ['left', 'right']
  310. }
  311. elif context.get('active_model') == 'res.partner.relation.all':
  312. field_names = {
  313. 'left': ['this'],
  314. 'right': ['other'],
  315. 'all': ['this', 'other']
  316. }
  317. else:
  318. assert False, 'Unknown active_model!'
  319. partner_ids = []
  320. field_names = field_names[
  321. context.get('partner_relations_show_side', 'all')]
  322. field_names = ['%s_partner_id' % n for n in field_names]
  323. for relation in self.pool[context.get('active_model')].read(
  324. cr, uid, ids, context=context, load='_classic_write'):
  325. for name in field_names:
  326. partner_ids.append(relation[name])
  327. return {
  328. 'name': _('Related partners'),
  329. 'type': 'ir.actions.act_window',
  330. 'res_model': 'res.partner',
  331. 'domain': [('id', 'in', partner_ids)],
  332. 'views': [(False, 'tree'), (False, 'form')],
  333. 'view_type': 'form'
  334. }