# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import re from odoo import api, fields, models, _ from odoo.exceptions import ValidationError class HelpdeskTeamShareCollaboratorWizard(models.TransientModel): _name = 'helpdesk.team.share.collaborator.wizard' _description = 'Helpdesk Team Sharing Collaborator Wizard' parent_wizard_id = fields.Many2one( 'helpdesk.team.share.wizard', export_string_translation=False, ) partner_id = fields.Many2one( 'res.partner', string='Collaborator', required=True, ) access_mode = fields.Selection( [ ('admin', 'Administrator'), ('user_all', 'User - All Tickets'), ('user_own', 'User - Own Tickets'), ], string='Access Mode', required=True, default='user_own', help="Administrator: can view all tickets and manage other users.\n" "User - All Tickets: can view all tickets and create own tickets.\n" "User - Own Tickets: can only create and view own tickets." ) send_invitation = fields.Boolean( string='Send Invitation', compute='_compute_send_invitation', store=True, readonly=False, default=True, ) @api.depends('partner_id', 'access_mode') def _compute_send_invitation(self): team = self.parent_wizard_id.resource_ref for collaborator in self: if ( collaborator.partner_id not in team.message_partner_ids or (collaborator.access_mode != 'user_own' and collaborator.partner_id not in team.collaborator_ids.partner_id) ): collaborator.send_invitation = True else: collaborator.send_invitation = False @api.constrains('partner_id') def _check_partner_share(self): """Validate that partner is a portal/external partner""" for collaborator in self: if collaborator.partner_id and not collaborator.partner_id.partner_share: raise ValidationError(_( "Partner '%s' is an internal user and cannot be added as a collaborator. " "Only external partners (portal users) can be collaborators." ) % collaborator.partner_id.display_name) @api.constrains('partner_id') def _check_partner_commercial_partner(self): """Validate that partner belongs to the same commercial_partner_id as admin""" for collaborator in self: if not collaborator.partner_id or not collaborator.parent_wizard_id: continue # Get admin's commercial_partner_id from context admin_commercial_partner_id = collaborator._context.get('default_admin_commercial_partner_id') if not admin_commercial_partner_id: # Try to get from parent wizard context admin_commercial_partner_id = collaborator.parent_wizard_id._context.get('default_admin_commercial_partner_id') if admin_commercial_partner_id: partner_commercial_id = collaborator.partner_id.commercial_partner_id.id if partner_commercial_id != admin_commercial_partner_id: raise ValidationError(_( "Partner '%s' does not belong to your contact network. " "You can only add contacts from your company network." ) % collaborator.partner_id.display_name) @api.constrains('partner_id') def _check_partner_email(self): """Validate that partner has a valid email if invitation will be sent""" for collaborator in self: if collaborator.partner_id and collaborator.send_invitation: email = collaborator.partner_id.email if not email: raise ValidationError(_( "Partner '%s' does not have an email address. " "An email is required to send an invitation." ) % collaborator.partner_id.display_name) # Basic email format validation email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' if not re.match(email_pattern, email): raise ValidationError(_( "Partner '%s' has an invalid email address format: %s" ) % (collaborator.partner_id.display_name, email)) @api.constrains('access_mode') def _check_access_mode(self): """Validate access mode value""" valid_modes = ['admin', 'user_all', 'user_own'] for collaborator in self: if collaborator.access_mode and collaborator.access_mode not in valid_modes: raise ValidationError(_( "Invalid access mode '%s'. Valid modes are: %s" ) % (collaborator.access_mode, ', '.join(valid_modes)))