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.

164 lines
6.5 KiB

  1. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  2. from odoo import api, fields, models
  3. from odoo.osv import expression
  4. class ResPartner(models.Model):
  5. _inherit = 'res.partner'
  6. contact_type = fields.Selection(
  7. [('standalone', 'Standalone Contact'),
  8. ('attached', 'Attached to existing Contact'),
  9. ],
  10. compute='_compute_contact_type',
  11. store=True,
  12. index=True,
  13. default='standalone')
  14. contact_id = fields.Many2one(
  15. 'res.partner',
  16. string='Main Contact',
  17. domain=[('is_company', '=', False),
  18. ('contact_type', '=', 'standalone'),
  19. ],
  20. )
  21. other_contact_ids = fields.One2many(
  22. 'res.partner', 'contact_id',
  23. string='Others Positions',
  24. )
  25. @api.multi
  26. @api.depends('contact_id')
  27. def _compute_contact_type(self):
  28. for rec in self:
  29. rec.contact_type = 'attached' if rec.contact_id else 'standalone'
  30. def _basecontact_check_context(self, mode):
  31. """ Remove 'search_show_all_positions' for non-search mode.
  32. Keeping it in context can result in unexpected behaviour (ex: reading
  33. one2many might return wrong result - i.e with "attached contact"
  34. removed even if it's directly linked to a company).
  35. Actually, is easier to override a dictionary value to indicate it
  36. should be ignored...
  37. """
  38. if (mode != 'search' and
  39. 'search_show_all_positions' in self.env.context):
  40. result = self.with_context(
  41. search_show_all_positions={'is_set': False})
  42. else:
  43. result = self
  44. return result
  45. @api.model
  46. def search(self, args, offset=0, limit=None, order=None, count=False):
  47. """ Display only standalone contact matching ``args`` or having
  48. attached contact matching ``args`` """
  49. ctx = self.env.context
  50. if (ctx.get('search_show_all_positions', {}).get('is_set') and
  51. not ctx['search_show_all_positions']['set_value']):
  52. args = expression.normalize_domain(args)
  53. attached_contact_args = expression.AND(
  54. (args, [('contact_type', '=', 'attached')])
  55. )
  56. attached_contacts = super(ResPartner, self).search(
  57. attached_contact_args)
  58. args = expression.OR((expression.AND((
  59. [('contact_type', '=', 'standalone')], args)),
  60. [('other_contact_ids', 'in', attached_contacts.ids)],
  61. ))
  62. return super(ResPartner, self).search(args, offset=offset,
  63. limit=limit, order=order,
  64. count=count)
  65. @api.model
  66. def create(self, vals):
  67. """ When creating, use a modified self to alter the context (see
  68. comment in _basecontact_check_context). Also, we need to ensure
  69. that the name on an attached contact is the same as the name on the
  70. contact it is attached to."""
  71. modified_self = self._basecontact_check_context('create')
  72. if not vals.get('name') and vals.get('contact_id'):
  73. vals['name'] = modified_self.browse(vals['contact_id']).name
  74. return super(ResPartner, modified_self).create(vals)
  75. @api.multi
  76. def read(self, fields=None, load='_classic_read'):
  77. modified_self = self._basecontact_check_context('read')
  78. return super(ResPartner, modified_self).read(fields=fields, load=load)
  79. @api.multi
  80. def write(self, vals):
  81. modified_self = self._basecontact_check_context('write')
  82. return super(ResPartner, modified_self).write(vals)
  83. @api.multi
  84. def unlink(self):
  85. modified_self = self._basecontact_check_context('unlink')
  86. return super(ResPartner, modified_self).unlink()
  87. @api.multi
  88. def _compute_commercial_partner(self):
  89. """ Returns the partner that is considered the commercial
  90. entity of this partner. The commercial entity holds the master data
  91. for all commercial fields (see :py:meth:`~_commercial_fields`) """
  92. result = super(ResPartner, self)._compute_commercial_partner()
  93. for partner in self:
  94. if partner.contact_type == 'attached' and not partner.parent_id:
  95. partner.commercial_partner_id = partner.contact_id
  96. return result
  97. def _contact_fields(self):
  98. """ Returns the list of contact fields that are synced from the parent
  99. when a partner is attached to him. """
  100. return ['name', 'title']
  101. def _contact_sync_from_parent(self):
  102. """ Handle sync of contact fields when a new parent contact entity
  103. is set, as if they were related fields
  104. """
  105. self.ensure_one()
  106. if self.contact_id:
  107. contact_fields = self._contact_fields()
  108. sync_vals = self.contact_id._update_fields_values(contact_fields)
  109. self.write(sync_vals)
  110. def update_contact(self, vals):
  111. if self.env.context.get('__update_contact_lock'):
  112. return
  113. contact_fields = self._contact_fields()
  114. contact_vals = dict(
  115. (field, vals[field]) for field in contact_fields if field in vals
  116. )
  117. if contact_vals:
  118. self.with_context(__update_contact_lock=True).write(contact_vals)
  119. @api.multi
  120. def _fields_sync(self, update_values):
  121. """Sync commercial fields and address fields from company and to
  122. children, contact fields from contact and to attached contact
  123. after create/update, just as if those were all modeled as
  124. fields.related to the parent
  125. """
  126. self.ensure_one()
  127. super(ResPartner, self)._fields_sync(update_values)
  128. contact_fields = self._contact_fields()
  129. # 1. From UPSTREAM: sync from parent contact
  130. if update_values.get('contact_id'):
  131. self._contact_sync_from_parent()
  132. # 2. To DOWNSTREAM: sync contact fields to parent or related
  133. elif any(field in contact_fields for field in update_values):
  134. update_ids = self.other_contact_ids.filtered(
  135. lambda p: not p.is_company)
  136. if self.contact_id:
  137. update_ids |= self.contact_id
  138. update_ids.update_contact(update_values)
  139. @api.onchange('contact_id')
  140. def _onchange_contact_id(self):
  141. if self.contact_id:
  142. self.name = self.contact_id.name
  143. @api.onchange('contact_type')
  144. def _onchange_contact_type(self):
  145. if self.contact_type == 'standalone':
  146. self.contact_id = False