# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import operator from odoo import Command, api, fields, models, _ from odoo.exceptions import ValidationError class HelpdeskTeamShareWizard(models.TransientModel): _name = 'helpdesk.team.share.wizard' _inherit = 'portal.share' _description = 'Helpdesk Team Sharing' @api.model def default_get(self, fields): # The helpdesk team share action could be called in `helpdesk.team.collaborator` # and so we have to check the active_model and active_id to use # the right team. active_model = self._context.get('active_model', '') active_id = self._context.get('active_id', False) if active_model == 'helpdesk.team.collaborator': active_model = 'helpdesk.team' active_id = self._context.get('default_team_id', False) # Call parent - portal.share will handle _get_share_url # If the model doesn't have portal.mixin, we'll handle it in the wizard result = {} result['res_model'] = active_model or self._context.get('active_model', False) result['res_id'] = active_id or self._context.get('active_id', False) # Try to get share_link from portal.share, but handle if _get_share_url doesn't exist if result['res_model'] and result['res_id']: record = self.env[result['res_model']].browse(result['res_id']) base_url = record.get_base_url() # Check if record has _get_share_url method if hasattr(record, '_get_share_url'): try: share_url = record._get_share_url(redirect=True) result['share_link'] = base_url + share_url except: result['share_link'] = f"{base_url}/helpdesk/team/{result['res_id']}" else: # Fallback: generate a simple share URL result['share_link'] = f"{base_url}/helpdesk/team/{result['res_id']}" else: result['share_link'] = '' # Get other default values from portal.share if available try: portal_defaults = super(HelpdeskTeamShareWizard, self.with_context(active_model=active_model, active_id=active_id)).default_get(fields) # Merge portal defaults but keep our share_link portal_defaults['share_link'] = result.get('share_link', portal_defaults.get('share_link', '')) result.update(portal_defaults) except: pass # Continue with the rest of the logic if result.get('res_model') and result.get('res_id'): # Use sudo() to access team data to avoid security rule issues team = self.env[result['res_model']].sudo().browse(result['res_id']) # Check if we're editing a specific collaborator specific_collaborator_id = self._context.get('default_collaborator_id') specific_collaborator = None if specific_collaborator_id: try: specific_collaborator = team.collaborator_ids.filtered(lambda c: c.id == specific_collaborator_id) if not specific_collaborator: specific_collaborator = None else: specific_collaborator = specific_collaborator[0] except (ValueError, TypeError): specific_collaborator = None collaborator_vals_list = [] collaborator_ids = [] # If editing a specific collaborator, only include that one if specific_collaborator: collaborator_ids.append(specific_collaborator.partner_id.id) collaborator_vals_list.append({ 'partner_id': specific_collaborator.partner_id.id, 'partner_name': specific_collaborator.partner_id.display_name, 'access_mode': specific_collaborator.access_mode, }) else: # Include all collaborators for collaborator in team.collaborator_ids: collaborator_ids.append(collaborator.partner_id.id) collaborator_vals_list.append({ 'partner_id': collaborator.partner_id.id, 'partner_name': collaborator.partner_id.display_name, 'access_mode': collaborator.access_mode, }) # Also include followers if not editing a specific collaborator # Use sudo() to access message_partner_ids for follower in team.message_partner_ids: if follower.partner_share and follower.id not in collaborator_ids: collaborator_vals_list.append({ 'partner_id': follower.id, 'partner_name': follower.display_name, 'access_mode': 'user_own', }) if collaborator_vals_list: # Only sort if not editing a specific collaborator if not specific_collaborator: collaborator_vals_list.sort(key=operator.itemgetter('partner_name')) result['collaborator_ids'] = [ Command.create({ 'partner_id': collaborator['partner_id'], 'access_mode': collaborator['access_mode'], 'send_invitation': False }) for collaborator in collaborator_vals_list ] return result @api.model def _selection_target_model(self): team_model = self.env['ir.model']._get('helpdesk.team') return [(team_model.model, team_model.name)] share_link = fields.Char( "Public Link", help="Anyone with this link can access the helpdesk team." ) collaborator_ids = fields.One2many( 'helpdesk.team.share.collaborator.wizard', 'parent_wizard_id', string='Collaborators' ) existing_partner_ids = fields.Many2many( 'res.partner', compute='_compute_existing_partner_ids', export_string_translation=False ) @api.depends('res_model', 'res_id') def _compute_resource_ref(self): for wizard in self: if wizard.res_model and wizard.res_model == 'helpdesk.team': wizard.resource_ref = '%s,%s' % (wizard.res_model, wizard.res_id or 0) else: wizard.resource_ref = None @api.depends('collaborator_ids') def _compute_existing_partner_ids(self): for wizard in self: wizard.existing_partner_ids = wizard.collaborator_ids.partner_id @api.model_create_multi def create(self, vals_list): wizards = super().create(vals_list) for wizard in wizards: if not wizard.resource_ref: continue # Use sudo() to access team data to avoid security rule issues team = wizard.resource_ref.sudo() if not team: continue collaborator_ids_vals_list = [] team_collaborator_ids_to_remove = [ c.id for c in team.collaborator_ids if c.partner_id not in wizard.collaborator_ids.partner_id ] # Use sudo() to access message_partner_ids team_followers = team.message_partner_ids team_followers_to_add = [] team_followers_to_remove = [ partner.id for partner in team_followers if partner not in wizard.collaborator_ids.partner_id and partner.partner_share ] team_collaborator_per_partner_id = {c.partner_id.id: c for c in team.collaborator_ids} collaborator_ids_to_add = [] collaborator_ids_vals_list = [] for collaborator in wizard.collaborator_ids: partner_id = collaborator.partner_id.id team_collaborator = team_collaborator_per_partner_id.get(partner_id, self.env['helpdesk.team.collaborator']) if collaborator.access_mode in ("admin", "user_all", "user_own"): if not team_collaborator: collaborator_ids_to_add.append((partner_id, collaborator.access_mode)) elif team_collaborator.access_mode != collaborator.access_mode: collaborator_ids_vals_list.append( Command.update( team_collaborator.id, {'access_mode': collaborator.access_mode}, ) ) elif team_collaborator: team_collaborator_ids_to_remove.append(team_collaborator.id) if partner_id not in team_followers.ids: team_followers_to_add.append(partner_id) if collaborator_ids_to_add: partners_to_add = self.env['res.partner'].browse([pid for pid, _ in collaborator_ids_to_add]) # Validate partners before adding invalid_partners = partners_to_add.filtered(lambda p: not p.partner_share) if invalid_partners: raise ValidationError(_( "The following partners are internal users and cannot be added as collaborators: %s" ) % ', '.join(invalid_partners.mapped('display_name'))) partners = team._get_new_collaborators(partners_to_add) collaborator_per_partner = {pid: mode for pid, mode in collaborator_ids_to_add} collaborator_ids_vals_list.extend( Command.create({ 'partner_id': partner_id, 'access_mode': collaborator_per_partner[partner_id], }) for partner_id in partners.ids ) if team_collaborator_ids_to_remove: collaborator_ids_vals_list.extend( Command.delete(collaborator_id) for collaborator_id in team_collaborator_ids_to_remove ) team_vals = {} if collaborator_ids_vals_list: team_vals['collaborator_ids'] = collaborator_ids_vals_list if team_vals: team.write(team_vals) if team_followers_to_add: team.message_subscribe(partner_ids=team_followers_to_add) if team_followers_to_remove: team.message_unsubscribe(team_followers_to_remove) return wizards def action_share_record(self): # Confirmation dialog is only opened if new portal user(s) need to be created in a 'on invitation' website self.ensure_one() if not self.collaborator_ids: return on_invite = self.env['res.users']._get_signup_invitation_scope() == 'b2b' new_portal_user = self.collaborator_ids.filtered(lambda c: c.send_invitation and not c.partner_id.user_ids) and on_invite if not new_portal_user: return self.action_send_mail() return { 'name': _('Confirmation'), 'type': 'ir.actions.act_window', 'view_mode': 'form', 'res_model': 'helpdesk.team.share.wizard', 'res_id': self.id, 'target': 'new', 'context': self.env.context, } def action_send_mail(self): result = { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'type': 'success', 'message': _("Helpdesk team shared with your collaborators."), 'next': {'type': 'ir.actions.act_window_close'}, } } partner_ids_to_notify = [] for collaborator in self.collaborator_ids: if collaborator.send_invitation: partner_ids_to_notify.append(collaborator.partner_id.id) if partner_ids_to_notify: partners = self.env['res.partner'].browse(partner_ids_to_notify) # Validate that partners have email addresses partners_without_email = partners.filtered(lambda p: not p.email) if partners_without_email: raise ValidationError(_( "The following partners do not have email addresses and cannot receive invitations: %s" ) % ', '.join(partners_without_email.mapped('display_name'))) portal_partners = partners.filtered('user_ids') # send mail to users self._send_public_link(portal_partners) self._send_signup_link(partners=partners.with_context({'signup_valid': True}) - portal_partners) return result