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.

345 lines
12 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2014-2016 Therp BV <http://therp.nl>
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. """Abstract model to show each relation from two sides."""
  5. from psycopg2.extensions import AsIs
  6. from openerp import _, api, fields, models
  7. from openerp.tools import drop_view_if_exists
  8. PADDING = 10
  9. _RECORD_TYPES = [
  10. ('a', 'Left partner to right partner'),
  11. ('b', 'Right partner to left partner'),
  12. ]
  13. class ResPartnerRelationAll(models.AbstractModel):
  14. """Abstract model to show each relation from two sides."""
  15. _auto = False
  16. _log_access = False
  17. _name = 'res.partner.relation.all'
  18. _description = 'All (non-inverse + inverse) relations between partners'
  19. _order = (
  20. 'this_partner_id, type_selection_id,'
  21. 'date_end desc, date_start desc'
  22. )
  23. _additional_view_fields = []
  24. """append to this list if you added fields to res_partner_relation that
  25. you need in this model and related fields are not adequate (ie for sorting)
  26. You must use the same name as in res_partner_relation.
  27. Don't overwrite this list in your declaration but append in _auto_init:
  28. def _auto_init(self, cr, context=None):
  29. self._additional_view_fields.append('my_field')
  30. return super(ResPartnerRelationAll, self)._auto_init(
  31. cr, context=context)
  32. my_field = fields...
  33. """
  34. this_partner_id = fields.Many2one(
  35. comodel_name='res.partner',
  36. string='One Partner',
  37. required=True,
  38. )
  39. other_partner_id = fields.Many2one(
  40. comodel_name='res.partner',
  41. string='Other Partner',
  42. required=True,
  43. )
  44. type_selection_id = fields.Many2one(
  45. comodel_name='res.partner.relation.type.selection',
  46. string='Relation Type',
  47. required=True,
  48. )
  49. relation_id = fields.Many2one(
  50. comodel_name='res.partner.relation',
  51. string='Relation',
  52. readonly=True,
  53. )
  54. record_type = fields.Selection(
  55. selection=_RECORD_TYPES,
  56. string='Record Type',
  57. readonly=True,
  58. )
  59. date_start = fields.Date('Starting date')
  60. date_end = fields.Date('Ending date')
  61. active = fields.Boolean(
  62. string='Active',
  63. help="Records with date_end in the past are inactive",
  64. )
  65. any_partner_id = fields.Many2many(
  66. comodel_name='res.partner',
  67. string='Partner',
  68. compute=lambda self: None,
  69. search='_search_any_partner_id'
  70. )
  71. def _auto_init(self, cr, context=None):
  72. drop_view_if_exists(cr, self._table)
  73. additional_view_fields = ','.join(self._additional_view_fields)
  74. additional_view_fields = (',' + additional_view_fields)\
  75. if additional_view_fields else ''
  76. cr.execute(
  77. """\
  78. CREATE OR REPLACE VIEW %(table)s AS
  79. SELECT
  80. rel.id * %(padding)s AS id,
  81. rel.id AS relation_id,
  82. cast('a' AS CHAR(1)) AS record_type,
  83. rel.left_partner_id AS this_partner_id,
  84. rel.right_partner_id AS other_partner_id,
  85. rel.date_start,
  86. rel.date_end,
  87. (rel.date_end IS NULL OR rel.date_end >= current_date) AS active,
  88. rel.type_id * %(padding)s AS type_selection_id
  89. %(additional_view_fields)s
  90. FROM res_partner_relation rel
  91. UNION SELECT
  92. rel.id * %(padding)s + 1,
  93. rel.id,
  94. CAST('b' AS CHAR(1)),
  95. rel.right_partner_id,
  96. rel.left_partner_id,
  97. rel.date_start,
  98. rel.date_end,
  99. rel.date_end IS NULL OR rel.date_end >= current_date,
  100. CASE
  101. WHEN typ.is_symmetric THEN rel.type_id * %(padding)s
  102. ELSE rel.type_id * %(padding)s + 1
  103. END
  104. %(additional_view_fields)s
  105. FROM res_partner_relation rel
  106. JOIN res_partner_relation_type typ ON (rel.type_id = typ.id)
  107. """,
  108. {
  109. 'table': AsIs(self._table),
  110. 'padding': PADDING,
  111. 'additional_view_fields': AsIs(additional_view_fields),
  112. }
  113. )
  114. return super(ResPartnerRelationAll, self)._auto_init(
  115. cr, context=context
  116. )
  117. @api.model
  118. def _search_any_partner_id(self, operator, value):
  119. """Search relation with partner, no matter on which side."""
  120. # pylint: disable=no-self-use
  121. return [
  122. '|',
  123. ('this_partner_id', operator, value),
  124. ('other_partner_id', operator, value),
  125. ]
  126. @api.multi
  127. def name_get(self):
  128. return {
  129. this.id: '%s %s %s' % (
  130. this.this_partner_id.name,
  131. this.type_selection_id.display_name,
  132. this.other_partner_id.name,
  133. )
  134. for this in self
  135. }
  136. @api.onchange('type_selection_id')
  137. def onchange_type_selection_id(self):
  138. """Add domain on partners according to category and contact_type."""
  139. def check_partner_domain(partner, partner_domain, side):
  140. """Check wether partner_domain results in empty selection
  141. for partner, or wrong selection of partner already selected.
  142. """
  143. warning = {}
  144. if partner:
  145. test_domain = [('id', '=', partner.id)] + partner_domain
  146. else:
  147. test_domain = partner_domain
  148. partner_model = self.env['res.partner']
  149. partners_found = partner_model.search(test_domain, limit=1)
  150. if not partners_found:
  151. warning['title'] = _('Error!')
  152. if partner:
  153. warning['message'] = (
  154. _('%s partner incompatible with relation type.') %
  155. side.title()
  156. )
  157. else:
  158. warning['message'] = (
  159. _('No %s partner available for relation type.') %
  160. side
  161. )
  162. return warning
  163. this_partner_domain = []
  164. other_partner_domain = []
  165. if self.type_selection_id.contact_type_this:
  166. this_partner_domain.append((
  167. 'is_company', '=',
  168. self.type_selection_id.contact_type_this == 'c'
  169. ))
  170. if self.type_selection_id.partner_category_this:
  171. this_partner_domain.append((
  172. 'category_id', 'in',
  173. self.type_selection_id.partner_category_this.ids
  174. ))
  175. if self.type_selection_id.contact_type_other:
  176. other_partner_domain.append((
  177. 'is_company', '=',
  178. self.type_selection_id.contact_type_other == 'c'
  179. ))
  180. if self.type_selection_id.partner_category_other:
  181. other_partner_domain.append((
  182. 'category_id', 'in',
  183. self.type_selection_id.partner_category_other.ids
  184. ))
  185. result = {'domain': {
  186. 'this_partner_id': this_partner_domain,
  187. 'other_partner_id': other_partner_domain,
  188. }}
  189. # Check wether domain results in no choice or wrong choice of partners:
  190. warning = {}
  191. if this_partner_domain:
  192. warning = check_partner_domain(
  193. self.this_partner_id, this_partner_domain, _('this')
  194. )
  195. if not warning and other_partner_domain:
  196. warning = check_partner_domain(
  197. self.other_partner_id, other_partner_domain, _('other')
  198. )
  199. if warning:
  200. result['warning'] = warning
  201. return result
  202. @api.onchange(
  203. 'this_partner_id',
  204. 'other_partner_id',
  205. )
  206. def onchange_partner_id(self):
  207. """Set domain on type_selection_id based on partner(s) selected."""
  208. def check_type_selection_domain(type_selection_domain):
  209. """If type_selection_id already selected, check wether it
  210. is compatible with the computed type_selection_domain. An empty
  211. selection can practically only occur in a practically empty
  212. database, and will not lead to problems. Therefore not tested.
  213. """
  214. warning = {}
  215. if not (type_selection_domain and self.type_selection_id):
  216. return warning
  217. test_domain = (
  218. [('id', '=', self.type_selection_id.id)] +
  219. type_selection_domain
  220. )
  221. type_model = self.env['res.partner.relation.type.selection']
  222. types_found = type_model.search(test_domain, limit=1)
  223. if not types_found:
  224. warning['title'] = _('Error!')
  225. warning['message'] = _(
  226. 'Relation type incompatible with selected partner(s).'
  227. )
  228. return warning
  229. type_selection_domain = []
  230. if self.this_partner_id:
  231. type_selection_domain += [
  232. '|',
  233. ('contact_type_this', '=', False),
  234. ('contact_type_this', '=',
  235. self.this_partner_id.get_partner_type()),
  236. '|',
  237. ('partner_category_this', '=', False),
  238. ('partner_category_this', 'in',
  239. self.this_partner_id.category_id.ids),
  240. ]
  241. if self.other_partner_id:
  242. type_selection_domain += [
  243. '|',
  244. ('contact_type_other', '=', False),
  245. ('contact_type_other', '=',
  246. self.other_partner_id.get_partner_type()),
  247. '|',
  248. ('partner_category_other', '=', False),
  249. ('partner_category_other', 'in',
  250. self.other_partner_id.category_id.ids),
  251. ]
  252. result = {'domain': {
  253. 'type_selection_id': type_selection_domain,
  254. }}
  255. # Check wether domain results in no choice or wrong choice for
  256. # type_selection_id:
  257. warning = check_type_selection_domain(type_selection_domain)
  258. if warning:
  259. result['warning'] = warning
  260. return result
  261. @api.model
  262. def _correct_vals(self, vals):
  263. """Fill left and right partner from this and other partner."""
  264. vals = vals.copy()
  265. if 'this_partner_id' in vals:
  266. vals['left_partner_id'] = vals['this_partner_id']
  267. del vals['this_partner_id']
  268. if 'other_partner_id' in vals:
  269. vals['right_partner_id'] = vals['other_partner_id']
  270. del vals['other_partner_id']
  271. if 'type_selection_id' not in vals:
  272. return vals
  273. selection = self.type_selection_id.browse(vals['type_selection_id'])
  274. type_id = selection.type_id.id
  275. is_inverse = selection.is_inverse
  276. vals['type_id'] = type_id
  277. del vals['type_selection_id']
  278. # Need to switch right and left partner if we are in reverse id:
  279. if 'left_partner_id' in vals or 'right_partner_id' in vals:
  280. if is_inverse:
  281. left_partner_id = False
  282. right_partner_id = False
  283. if 'left_partner_id' in vals:
  284. right_partner_id = vals['left_partner_id']
  285. del vals['left_partner_id']
  286. if 'right_partner_id' in vals:
  287. left_partner_id = vals['right_partner_id']
  288. del vals['right_partner_id']
  289. if left_partner_id:
  290. vals['left_partner_id'] = left_partner_id
  291. if right_partner_id:
  292. vals['right_partner_id'] = right_partner_id
  293. return vals
  294. @api.multi
  295. def write(self, vals):
  296. """divert non-problematic writes to underlying table"""
  297. vals = self._correct_vals(vals)
  298. for rec in self:
  299. rec.relation_id.write(vals)
  300. return True
  301. @api.model
  302. def create(self, vals):
  303. """Divert non-problematic creates to underlying table.
  304. Create a res.partner.relation but return the converted id.
  305. """
  306. is_inverse = False
  307. if 'type_selection_id' in vals:
  308. selection = self.type_selection_id.browse(
  309. vals['type_selection_id']
  310. )
  311. is_inverse = selection.is_inverse
  312. vals = self._correct_vals(vals)
  313. res = self.relation_id.create(vals)
  314. return_id = res.id * PADDING + (is_inverse and 1 or 0)
  315. return self.browse(return_id)
  316. @api.multi
  317. def unlink(self):
  318. """divert non-problematic creates to underlying table"""
  319. # pylint: disable=arguments-differ
  320. for rec in self:
  321. rec.relation_id.unlink()
  322. return True