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.

319 lines
12 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2013-2017 Therp BV <http://therp.nl>.
  3. # License AGPL-3.0 or later <http://www.gnu.org/licenses/agpl.html>.
  4. from openerp.osv.orm import Model, except_orm
  5. from openerp.osv import fields
  6. from openerp.tools.translate import _
  7. class ResPartnerRelation(Model):
  8. """Model res.partner.relation is used to describe all links or relations
  9. between partners in the database.
  10. In many parts of the code we have to know whether the active partner is
  11. the left partner, or the right partner. If the active partner is the
  12. right partner we have to show the inverse name.
  13. Because the active partner is crucial for the working of partner
  14. relationships, we make sure on the res.partner model that the partner id
  15. is set in the context where needed.
  16. """
  17. _name = 'res.partner.relation'
  18. _description = 'Partner relation'
  19. _order = 'active desc, date_start desc, date_end desc'
  20. def _correct_vals(self, cr, uid, vals, context=None):
  21. """Fill type and left and right partner id, according to wether
  22. we have a normal relation type or an inverse relation type"""
  23. vals = vals.copy()
  24. # If type_selection_id ends in 1, it is a reverse relation type
  25. if 'type_selection_id' in vals:
  26. prts_model = self.pool['res.partner.relation.type.selection']
  27. type_selection_id = vals['type_selection_id']
  28. (type_id, is_reverse) = (
  29. prts_model.get_type_from_selection_id(
  30. cr, uid, type_selection_id))
  31. vals['type_id'] = type_id
  32. if context.get('active_id'):
  33. if is_reverse:
  34. vals['right_partner_id'] = context['active_id']
  35. else:
  36. vals['left_partner_id'] = context['active_id']
  37. if vals.get('partner_id_display'):
  38. if is_reverse:
  39. vals['left_partner_id'] = vals['partner_id_display']
  40. else:
  41. vals['right_partner_id'] = vals['partner_id_display']
  42. return vals
  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 = (
  51. 'active_ids' in context and
  52. self.right_partner_id.id in context.get('active_ids', []) or
  53. False
  54. )
  55. # type_selection_id
  56. values['type_selection_id'] = (
  57. ((self.type_id.id) * 10) + (on_right_partner and 1 or 0))
  58. # partner_id_display
  59. values['partner_id_display'] = (
  60. self.left_partner_id.id
  61. if on_right_partner
  62. else self.right_partner_id.id
  63. )
  64. # is_relation_expired
  65. today = fields.date.context_today(self, cr, uid, context=context)
  66. values['is_relation_expired'] = (
  67. self.date_end and (self.date_end < today))
  68. # is_relation_future
  69. values['is_relation_future'] = self.date_start > today
  70. return values
  71. context = context or {}
  72. return dict([
  73. (i.id, get_values(i, field_names, arg, context=context))
  74. for i in self.browse(cr, uid, ids, context=context)
  75. ])
  76. def write(self, cr, uid, ids, vals, context=None):
  77. """Override write to correct values, before being stored."""
  78. vals = self._correct_vals(cr, uid, vals, context=context)
  79. return super(ResPartnerRelation, self).write(
  80. cr, uid, ids, vals, context=context)
  81. def create(self, cr, uid, vals, context=None):
  82. """Override create to correct values, before being stored."""
  83. vals = self._correct_vals(cr, uid, vals, context=context)
  84. return super(ResPartnerRelation, self).create(
  85. cr, uid, vals, context=context)
  86. def on_change_type_selection_id(
  87. self, cr, uid, dummy_ids, type_selection_id, context=None):
  88. """Set domain on partner_id_display, when selection a relation type"""
  89. result = {
  90. 'domain': {'partner_id_display': []},
  91. 'value': {'type_id': False}
  92. }
  93. if not type_selection_id:
  94. return result
  95. prts_model = self.pool['res.partner.relation.type.selection']
  96. type_model = self.pool['res.partner.relation.type']
  97. (type_id, is_reverse) = (
  98. prts_model.get_type_from_selection_id(
  99. cr, uid, type_selection_id)
  100. )
  101. result['value']['type_id'] = type_id
  102. type_obj = type_model.browse(cr, uid, type_id, context=context)
  103. partner_domain = []
  104. check_contact_type = type_obj.contact_type_right
  105. check_partner_category = (
  106. type_obj.partner_category_right and
  107. type_obj.partner_category_right.id
  108. )
  109. if is_reverse:
  110. # partner_id_display is left partner
  111. check_contact_type = type_obj.contact_type_left
  112. check_partner_category = (
  113. type_obj.partner_category_left and
  114. type_obj.partner_category_left.id
  115. )
  116. if check_contact_type == 'c':
  117. partner_domain.append(('is_company', '=', True))
  118. if check_contact_type == 'p':
  119. partner_domain.append(('is_company', '=', False))
  120. if check_partner_category:
  121. partner_domain.append(
  122. ('category_id', 'child_of', check_partner_category))
  123. result['domain']['partner_id_display'] = partner_domain
  124. return result
  125. _columns = {
  126. 'left_partner_id': fields.many2one(
  127. 'res.partner', string='Left partner', required=True,
  128. auto_join=True, ondelete='cascade'),
  129. 'right_partner_id': fields.many2one(
  130. 'res.partner', string='Right partner', required=True,
  131. auto_join=True, ondelete='cascade'),
  132. 'type_id': fields.many2one(
  133. 'res.partner.relation.type', string='Type', required=True,
  134. auto_join=True),
  135. 'date_start': fields.date('Starting date'),
  136. 'date_end': fields.date('Ending date'),
  137. 'type_selection_id': fields.function(
  138. _get_computed_fields,
  139. multi="computed_fields",
  140. fnct_inv=lambda *args: None,
  141. type='many2one', obj='res.partner.relation.type.selection',
  142. string='Type',
  143. ),
  144. 'partner_id_display': fields.function(
  145. _get_computed_fields,
  146. multi="computed_fields",
  147. fnct_inv=lambda *args: None,
  148. type='many2one', obj='res.partner',
  149. string='Partner'
  150. ),
  151. 'is_relation_expired': fields.function(
  152. _get_computed_fields,
  153. multi="computed_fields",
  154. type='boolean',
  155. method=True,
  156. string='Relation is expired',
  157. ),
  158. 'is_relation_future': fields.function(
  159. _get_computed_fields,
  160. multi="computed_fields",
  161. type='boolean',
  162. method=True,
  163. string='Relation is in the future',
  164. ),
  165. 'active': fields.boolean('Active'),
  166. }
  167. _defaults = {
  168. 'active': True,
  169. }
  170. def _check_dates(self, cr, uid, ids, context=None):
  171. """End date should not be before start date, if noth filled"""
  172. for line in self.browse(cr, uid, ids, context=context):
  173. if line.date_start and line.date_end:
  174. if line.date_start > line.date_end:
  175. return False
  176. return True
  177. def _check_partner_type_left(self, cr, uid, ids, context=None):
  178. """Check left partner for required company or person"""
  179. for this in self.browse(cr, uid, ids, context=context):
  180. ptype = this.type_id.contact_type_left
  181. company = this.left_partner_id.is_company
  182. if (ptype == 'c' and not company) or (ptype == 'p' and company):
  183. return False
  184. return True
  185. def _check_partner_type_right(self, cr, uid, ids, context=None):
  186. """Check right partner for required company or person"""
  187. for this in self.browse(cr, uid, ids, context=context):
  188. ptype = this.type_id.contact_type_right
  189. company = this.right_partner_id.is_company
  190. if (ptype == 'c' and not company) or (ptype == 'p' and company):
  191. return False
  192. return True
  193. def _check_not_with_self(self, cr, uid, ids, context=None):
  194. """Not allowed to link partner to same partner"""
  195. for this in self.browse(cr, uid, ids, context=context):
  196. if this.left_partner_id == this.right_partner_id:
  197. return False
  198. return True
  199. def _check_relation_uniqueness(self, cr, uid, ids, context=None):
  200. """Forbid multiple active relations of the same type between the same
  201. partners"""
  202. for this in self.browse(cr, uid, ids, context=context):
  203. if not this.active:
  204. continue
  205. domain = [
  206. ('type_id', '=', this.type_id.id),
  207. ('active', '=', True),
  208. ('id', '!=', this.id),
  209. ('left_partner_id', '=', this.left_partner_id.id),
  210. ('right_partner_id', '=', this.right_partner_id.id),
  211. ]
  212. if this.date_start:
  213. domain += [
  214. '|', ('date_end', '=', False),
  215. ('date_end', '>=', this.date_start),
  216. ]
  217. if this.date_end:
  218. domain += [
  219. '|', ('date_start', '=', False),
  220. ('date_start', '<=', this.date_end),
  221. ]
  222. if self.search(cr, uid, domain, context=context):
  223. raise except_orm(
  224. _('Overlapping relation'),
  225. _('There is already a similar relation '
  226. 'with overlapping dates')
  227. )
  228. return True
  229. _constraints = [
  230. (
  231. _check_dates,
  232. 'The starting date cannot be after the ending date.',
  233. ['date_start', 'date_end']
  234. ),
  235. (
  236. _check_partner_type_left,
  237. 'The left partner is not applicable for this relation type.',
  238. ['left_partner_id', 'type_id']
  239. ),
  240. (
  241. _check_partner_type_right,
  242. 'The right partner is not applicable for this relation type.',
  243. ['right_partner_id', 'type_id']
  244. ),
  245. (
  246. _check_not_with_self,
  247. 'Partners cannot have a relation with themselves.',
  248. ['left_partner_id', 'right_partner_id']
  249. ),
  250. (
  251. _check_relation_uniqueness,
  252. "The same relation can't be created twice.",
  253. ['left_partner_id', 'right_partner_id', 'active']
  254. )
  255. ]
  256. def get_action_related_partners(self, cr, uid, ids, context=None):
  257. """return a window action showing a list of partners taking part in the
  258. relations names by ids. Context key 'partner_relations_show_side'
  259. determines if we show 'left' side, 'right' side or 'all' (default)
  260. partners.
  261. If active_model is res.partner.relation.all, left=this and
  262. right=other
  263. """
  264. if context is None:
  265. context = {}
  266. field_names = {}
  267. if context.get('active_model', self._name) == self._name:
  268. field_names = {
  269. 'left': ['left'],
  270. 'right': ['right'],
  271. 'all': ['left', 'right']
  272. }
  273. elif context.get('active_model') == 'res.partner.relation.all':
  274. field_names = {
  275. 'left': ['this'],
  276. 'right': ['other'],
  277. 'all': ['this', 'other']
  278. }
  279. else:
  280. assert False, 'Unknown active_model!'
  281. partner_ids = []
  282. field_names = field_names[
  283. context.get('partner_relations_show_side', 'all')]
  284. field_names = ['%s_partner_id' % n for n in field_names]
  285. for relation in self.pool[context.get('active_model')].read(
  286. cr, uid, ids, context=context, load='_classic_write'):
  287. for name in field_names:
  288. partner_ids.append(relation[name])
  289. return {
  290. 'name': _('Related partners'),
  291. 'type': 'ir.actions.act_window',
  292. 'res_model': 'res.partner',
  293. 'domain': [('id', 'in', partner_ids)],
  294. 'views': [(False, 'tree'), (False, 'form')],
  295. 'view_type': 'form'
  296. }