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