helpdesk_team_share_wizard.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import operator
  4. from odoo import Command, api, fields, models, _
  5. from odoo.exceptions import ValidationError
  6. class HelpdeskTeamShareWizard(models.TransientModel):
  7. _name = 'helpdesk.team.share.wizard'
  8. _inherit = 'portal.share'
  9. _description = 'Helpdesk Team Sharing'
  10. @api.model
  11. def default_get(self, fields):
  12. # The helpdesk team share action could be called in `helpdesk.team.collaborator`
  13. # and so we have to check the active_model and active_id to use
  14. # the right team.
  15. active_model = self._context.get('active_model', '')
  16. active_id = self._context.get('active_id', False)
  17. if active_model == 'helpdesk.team.collaborator':
  18. active_model = 'helpdesk.team'
  19. active_id = self._context.get('default_team_id', False)
  20. # Call parent - portal.share will handle _get_share_url
  21. # If the model doesn't have portal.mixin, we'll handle it in the wizard
  22. result = {}
  23. result['res_model'] = active_model or self._context.get('active_model', False)
  24. result['res_id'] = active_id or self._context.get('active_id', False)
  25. # Try to get share_link from portal.share, but handle if _get_share_url doesn't exist
  26. if result['res_model'] and result['res_id']:
  27. record = self.env[result['res_model']].browse(result['res_id'])
  28. base_url = record.get_base_url()
  29. # Check if record has _get_share_url method
  30. if hasattr(record, '_get_share_url'):
  31. try:
  32. share_url = record._get_share_url(redirect=True)
  33. result['share_link'] = base_url + share_url
  34. except:
  35. result['share_link'] = f"{base_url}/helpdesk/team/{result['res_id']}"
  36. else:
  37. # Fallback: generate a simple share URL
  38. result['share_link'] = f"{base_url}/helpdesk/team/{result['res_id']}"
  39. else:
  40. result['share_link'] = ''
  41. # Get other default values from portal.share if available
  42. try:
  43. portal_defaults = super(HelpdeskTeamShareWizard, self.with_context(active_model=active_model, active_id=active_id)).default_get(fields)
  44. # Merge portal defaults but keep our share_link
  45. portal_defaults['share_link'] = result.get('share_link', portal_defaults.get('share_link', ''))
  46. result.update(portal_defaults)
  47. except:
  48. pass
  49. # Continue with the rest of the logic
  50. if result.get('res_model') and result.get('res_id'):
  51. # Use sudo() to access team data to avoid security rule issues
  52. team = self.env[result['res_model']].sudo().browse(result['res_id'])
  53. # Check if we're editing a specific collaborator
  54. specific_collaborator_id = self._context.get('default_collaborator_id')
  55. specific_collaborator = None
  56. if specific_collaborator_id:
  57. try:
  58. specific_collaborator = team.collaborator_ids.filtered(lambda c: c.id == specific_collaborator_id)
  59. if not specific_collaborator:
  60. specific_collaborator = None
  61. else:
  62. specific_collaborator = specific_collaborator[0]
  63. except (ValueError, TypeError):
  64. specific_collaborator = None
  65. collaborator_vals_list = []
  66. collaborator_ids = []
  67. # If editing a specific collaborator, only include that one
  68. if specific_collaborator:
  69. collaborator_ids.append(specific_collaborator.partner_id.id)
  70. collaborator_vals_list.append({
  71. 'partner_id': specific_collaborator.partner_id.id,
  72. 'partner_name': specific_collaborator.partner_id.display_name,
  73. 'access_mode': specific_collaborator.access_mode,
  74. })
  75. else:
  76. # Include all collaborators
  77. for collaborator in team.collaborator_ids:
  78. collaborator_ids.append(collaborator.partner_id.id)
  79. collaborator_vals_list.append({
  80. 'partner_id': collaborator.partner_id.id,
  81. 'partner_name': collaborator.partner_id.display_name,
  82. 'access_mode': collaborator.access_mode,
  83. })
  84. # Also include followers if not editing a specific collaborator
  85. # Use sudo() to access message_partner_ids
  86. for follower in team.message_partner_ids:
  87. if follower.partner_share and follower.id not in collaborator_ids:
  88. collaborator_vals_list.append({
  89. 'partner_id': follower.id,
  90. 'partner_name': follower.display_name,
  91. 'access_mode': 'user_own',
  92. })
  93. if collaborator_vals_list:
  94. # Only sort if not editing a specific collaborator
  95. if not specific_collaborator:
  96. collaborator_vals_list.sort(key=operator.itemgetter('partner_name'))
  97. result['collaborator_ids'] = [
  98. Command.create({
  99. 'partner_id': collaborator['partner_id'],
  100. 'access_mode': collaborator['access_mode'],
  101. 'send_invitation': False
  102. })
  103. for collaborator in collaborator_vals_list
  104. ]
  105. return result
  106. @api.model
  107. def _selection_target_model(self):
  108. team_model = self.env['ir.model']._get('helpdesk.team')
  109. return [(team_model.model, team_model.name)]
  110. share_link = fields.Char(
  111. "Public Link",
  112. help="Anyone with this link can access the helpdesk team."
  113. )
  114. collaborator_ids = fields.One2many(
  115. 'helpdesk.team.share.collaborator.wizard',
  116. 'parent_wizard_id',
  117. string='Collaborators'
  118. )
  119. existing_partner_ids = fields.Many2many(
  120. 'res.partner',
  121. compute='_compute_existing_partner_ids',
  122. export_string_translation=False
  123. )
  124. @api.depends('res_model', 'res_id')
  125. def _compute_resource_ref(self):
  126. for wizard in self:
  127. if wizard.res_model and wizard.res_model == 'helpdesk.team':
  128. wizard.resource_ref = '%s,%s' % (wizard.res_model, wizard.res_id or 0)
  129. else:
  130. wizard.resource_ref = None
  131. @api.depends('collaborator_ids')
  132. def _compute_existing_partner_ids(self):
  133. for wizard in self:
  134. wizard.existing_partner_ids = wizard.collaborator_ids.partner_id
  135. @api.model_create_multi
  136. def create(self, vals_list):
  137. wizards = super().create(vals_list)
  138. for wizard in wizards:
  139. if not wizard.resource_ref:
  140. continue
  141. # Use sudo() to access team data to avoid security rule issues
  142. team = wizard.resource_ref.sudo()
  143. if not team:
  144. continue
  145. collaborator_ids_vals_list = []
  146. team_collaborator_ids_to_remove = [
  147. c.id
  148. for c in team.collaborator_ids
  149. if c.partner_id not in wizard.collaborator_ids.partner_id
  150. ]
  151. # Use sudo() to access message_partner_ids
  152. team_followers = team.message_partner_ids
  153. team_followers_to_add = []
  154. team_followers_to_remove = [
  155. partner.id
  156. for partner in team_followers
  157. if partner not in wizard.collaborator_ids.partner_id and partner.partner_share
  158. ]
  159. team_collaborator_per_partner_id = {c.partner_id.id: c for c in team.collaborator_ids}
  160. collaborator_ids_to_add = []
  161. collaborator_ids_vals_list = []
  162. for collaborator in wizard.collaborator_ids:
  163. partner_id = collaborator.partner_id.id
  164. team_collaborator = team_collaborator_per_partner_id.get(partner_id, self.env['helpdesk.team.collaborator'])
  165. if collaborator.access_mode in ("admin", "user_all", "user_own"):
  166. if not team_collaborator:
  167. collaborator_ids_to_add.append((partner_id, collaborator.access_mode))
  168. elif team_collaborator.access_mode != collaborator.access_mode:
  169. collaborator_ids_vals_list.append(
  170. Command.update(
  171. team_collaborator.id,
  172. {'access_mode': collaborator.access_mode},
  173. )
  174. )
  175. elif team_collaborator:
  176. team_collaborator_ids_to_remove.append(team_collaborator.id)
  177. if partner_id not in team_followers.ids:
  178. team_followers_to_add.append(partner_id)
  179. if collaborator_ids_to_add:
  180. partners_to_add = self.env['res.partner'].browse([pid for pid, _ in collaborator_ids_to_add])
  181. # Validate partners before adding
  182. invalid_partners = partners_to_add.filtered(lambda p: not p.partner_share)
  183. if invalid_partners:
  184. raise ValidationError(_(
  185. "The following partners are internal users and cannot be added as collaborators: %s"
  186. ) % ', '.join(invalid_partners.mapped('display_name')))
  187. partners = team._get_new_collaborators(partners_to_add)
  188. collaborator_per_partner = {pid: mode for pid, mode in collaborator_ids_to_add}
  189. collaborator_ids_vals_list.extend(
  190. Command.create({
  191. 'partner_id': partner_id,
  192. 'access_mode': collaborator_per_partner[partner_id],
  193. }) for partner_id in partners.ids
  194. )
  195. if team_collaborator_ids_to_remove:
  196. collaborator_ids_vals_list.extend(
  197. Command.delete(collaborator_id) for collaborator_id in team_collaborator_ids_to_remove
  198. )
  199. team_vals = {}
  200. if collaborator_ids_vals_list:
  201. team_vals['collaborator_ids'] = collaborator_ids_vals_list
  202. if team_vals:
  203. team.write(team_vals)
  204. if team_followers_to_add:
  205. team.message_subscribe(partner_ids=team_followers_to_add)
  206. if team_followers_to_remove:
  207. team.message_unsubscribe(team_followers_to_remove)
  208. return wizards
  209. def action_share_record(self):
  210. # Confirmation dialog is only opened if new portal user(s) need to be created in a 'on invitation' website
  211. self.ensure_one()
  212. if not self.collaborator_ids:
  213. return
  214. on_invite = self.env['res.users']._get_signup_invitation_scope() == 'b2b'
  215. new_portal_user = self.collaborator_ids.filtered(lambda c: c.send_invitation and not c.partner_id.user_ids) and on_invite
  216. if not new_portal_user:
  217. return self.action_send_mail()
  218. return {
  219. 'name': _('Confirmation'),
  220. 'type': 'ir.actions.act_window',
  221. 'view_mode': 'form',
  222. 'res_model': 'helpdesk.team.share.wizard',
  223. 'res_id': self.id,
  224. 'target': 'new',
  225. 'context': self.env.context,
  226. }
  227. def action_send_mail(self):
  228. result = {
  229. 'type': 'ir.actions.client',
  230. 'tag': 'display_notification',
  231. 'params': {
  232. 'type': 'success',
  233. 'message': _("Helpdesk team shared with your collaborators."),
  234. 'next': {'type': 'ir.actions.act_window_close'},
  235. }
  236. }
  237. partner_ids_to_notify = []
  238. for collaborator in self.collaborator_ids:
  239. if collaborator.send_invitation:
  240. partner_ids_to_notify.append(collaborator.partner_id.id)
  241. if partner_ids_to_notify:
  242. partners = self.env['res.partner'].browse(partner_ids_to_notify)
  243. # Validate that partners have email addresses
  244. partners_without_email = partners.filtered(lambda p: not p.email)
  245. if partners_without_email:
  246. raise ValidationError(_(
  247. "The following partners do not have email addresses and cannot receive invitations: %s"
  248. ) % ', '.join(partners_without_email.mapped('display_name')))
  249. portal_partners = partners.filtered('user_ids')
  250. # send mail to users
  251. self._send_public_link(portal_partners)
  252. self._send_signup_link(partners=partners.with_context({'signup_valid': True}) - portal_partners)
  253. return result