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.

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