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.

157 lines
6.3 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. [
  8. ("standalone", "Standalone Contact"),
  9. ("attached", "Attached to existing Contact"),
  10. ],
  11. compute="_compute_contact_type",
  12. store=True,
  13. index=True,
  14. default="standalone",
  15. )
  16. contact_id = fields.Many2one(
  17. "res.partner",
  18. string="Main Contact",
  19. domain=[("is_company", "=", False), ("contact_type", "=", "standalone")],
  20. )
  21. other_contact_ids = fields.One2many(
  22. "res.partner",
  23. "contact_id",
  24. string="Others Positions",
  25. )
  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 "search_show_all_positions" in self.env.context:
  39. result = self.with_context(search_show_all_positions={"is_set": False})
  40. else:
  41. result = self
  42. return result
  43. @api.model
  44. def search(self, args, offset=0, limit=None, order=None, count=False):
  45. """Display only standalone contact matching ``args`` or having
  46. attached contact matching ``args``"""
  47. ctx = self.env.context
  48. if (
  49. ctx.get("search_show_all_positions", {}).get("is_set")
  50. and not ctx["search_show_all_positions"]["set_value"]
  51. ):
  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(attached_contact_args)
  57. args = expression.OR(
  58. (
  59. expression.AND(([("contact_type", "=", "standalone")], args)),
  60. [("other_contact_ids", "in", attached_contacts.ids)],
  61. )
  62. )
  63. return super(ResPartner, self).search(
  64. args, offset=offset, limit=limit, order=order, count=count
  65. )
  66. @api.model
  67. def create(self, vals):
  68. """When creating, use a modified self to alter the context (see
  69. comment in _basecontact_check_context). Also, we need to ensure
  70. that the name on an attached contact is the same as the name on the
  71. contact it is attached to."""
  72. modified_self = self._basecontact_check_context("create")
  73. if not vals.get("name") and vals.get("contact_id"):
  74. vals["name"] = modified_self.browse(vals["contact_id"]).name
  75. return super(ResPartner, modified_self).create(vals)
  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. def write(self, vals):
  80. modified_self = self._basecontact_check_context("write")
  81. return super(ResPartner, modified_self).write(vals)
  82. def unlink(self):
  83. modified_self = self._basecontact_check_context("unlink")
  84. return super(ResPartner, modified_self).unlink()
  85. def _compute_commercial_partner(self):
  86. """Returns the partner that is considered the commercial
  87. entity of this partner. The commercial entity holds the master data
  88. for all commercial fields (see :py:meth:`~_commercial_fields`)"""
  89. result = super(ResPartner, self)._compute_commercial_partner()
  90. for partner in self:
  91. if partner.contact_type == "attached" and not partner.parent_id:
  92. partner.commercial_partner_id = partner.contact_id
  93. return result
  94. def _contact_fields(self):
  95. """Returns the list of contact fields that are synced from the parent
  96. when a partner is attached to him."""
  97. return ["name", "title"]
  98. def _contact_sync_from_parent(self):
  99. """Handle sync of contact fields when a new parent contact entity
  100. is set, as if they were related fields
  101. """
  102. self.ensure_one()
  103. if self.contact_id:
  104. contact_fields = self._contact_fields()
  105. sync_vals = self.contact_id._update_fields_values(contact_fields)
  106. self.write(sync_vals)
  107. def update_contact(self, vals):
  108. if self.env.context.get("__update_contact_lock"):
  109. return
  110. contact_fields = self._contact_fields()
  111. contact_vals = {field: vals[field] for field in contact_fields if field in vals}
  112. if contact_vals:
  113. self.with_context(__update_contact_lock=True).write(contact_vals)
  114. def _fields_sync(self, update_values):
  115. """Sync commercial fields and address fields from company and to
  116. children, contact fields from contact and to attached contact
  117. after create/update, just as if those were all modeled as
  118. fields.related to the parent
  119. """
  120. self.ensure_one()
  121. super(ResPartner, self)._fields_sync(update_values)
  122. contact_fields = self._contact_fields()
  123. # 1. From UPSTREAM: sync from parent contact
  124. if update_values.get("contact_id"):
  125. self._contact_sync_from_parent()
  126. # 2. To DOWNSTREAM: sync contact fields to parent or related
  127. elif any(field in contact_fields for field in update_values):
  128. update_ids = self.other_contact_ids.filtered(lambda p: not p.is_company)
  129. if self.contact_id:
  130. update_ids |= self.contact_id
  131. update_ids.update_contact(update_values)
  132. @api.onchange("contact_id")
  133. def _onchange_contact_id(self):
  134. if self.contact_id:
  135. self.name = self.contact_id.name
  136. @api.onchange("contact_type")
  137. def _onchange_contact_type(self):
  138. if self.contact_type == "standalone":
  139. self.contact_id = False