# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import http from odoo.http import request from odoo.addons.helpdesk.controllers.portal import ( CustomerPortal as HelpdeskCustomerPortal, ) from odoo.exceptions import AccessError, MissingError class CustomerPortal(HelpdeskCustomerPortal): """Extend CustomerPortal to show tickets block for collaborators without ticket count condition""" def _prepare_home_portal_values(self, counters): values = super()._prepare_home_portal_values(counters) if "ticket_count" in counters: # Check if user is portal and is a collaborator in any team if request.env.user and request.env.user._is_portal(): partner = request.env.user.partner_id.commercial_partner_id # Check if user is a collaborator in any helpdesk team is_collaborator = ( request.env["helpdesk.team.collaborator"] .sudo() .search_count([("partner_id", "=", partner.id)]) > 0 ) if is_collaborator: # If user is collaborator, ensure block is always shown # by setting ticket_count to at least 1 # This removes the ticket count condition for collaborators current_count = values.get("ticket_count", 0) if current_count == 0: # Force ticket_count to 1 so the block is always shown for collaborators values["ticket_count"] = 1 # If not collaborator or not portal, use default behavior # (values already calculated by parent) return values def _prepare_my_tickets_values( self, page=1, date_begin=None, date_end=None, sortby=None, filterby="all", search=None, groupby="none", search_in="name", ): values = super()._prepare_my_tickets_values( page, date_begin, date_end, sortby, filterby, search, groupby, search_in ) # Check if user is portal and is a collaborator in any team if request.env.user and request.env.user._is_portal(): partner = request.env.user.partner_id commercial_partner = partner.commercial_partner_id # Get teams where user is a collaborator (try both partner_id and commercial_partner_id) collaborator_records = ( request.env["helpdesk.team.collaborator"] .sudo() .search( [ "|", ("partner_id", "=", partner.id), ("partner_id", "=", commercial_partner.id), ] ) ) # Get teams where user is a collaborator and has website form enabled collaborator_teams = collaborator_records.mapped("team_id").filtered( lambda t: t.use_website_helpdesk_form ) # DEBUG: Log para verificar qué está pasando import logging _logger = logging.getLogger(__name__) _logger.info( f"DEBUG: User {request.env.user.name} (partner_id={partner.id}, commercial_partner_id={commercial_partner.id})" ) _logger.info( f"DEBUG: Found {len(collaborator_records)} collaborator records" ) _logger.info( f"DEBUG: Found {len(collaborator_teams)} teams with website form enabled" ) # Filter by website published for portal users (managers see all) # TEMPORALMENTE: Comentado para debug - verificar si el problema es el filtro # if not request.env.user.has_group("helpdesk.group_helpdesk_manager"): # collaborator_teams = collaborator_teams.filtered( # lambda t: t.website_published and t.website_id == request.website # ) # If user is collaborator in teams with website form, get first available team _logger.info( f"DEBUG: collaborator_teams length = {len(collaborator_teams)}" ) _logger.info(f"DEBUG: collaborator_teams ids = {collaborator_teams.ids}") if collaborator_teams: # Get the first team (can be improved to select based on priority) available_team = collaborator_teams[0] # Build URL to the team form with contact_form parameter team_url = available_team.website_url team_url += "/?contact_form=1" values["collaborator_team"] = available_team values["collaborator_team_form_url"] = team_url _logger.info(f"DEBUG: Set collaborator_team_form_url = {team_url}") _logger.info( f"DEBUG: Team name = {available_team.name}, website_url = {available_team.website_url}" ) else: # Always set the value, even if False, for template debugging _logger.info( "DEBUG: No collaborator teams found, setting form_url to False" ) values["collaborator_team_form_url"] = False # Get teams where user is admin for "Manage Collaborators" button admin_teams = self._get_teams_where_admin() values["admin_teams"] = admin_teams _logger.info(f"DEBUG: admin_teams count = {len(admin_teams)}") for team in admin_teams: _logger.info(f"DEBUG: admin team = {team.name} (ID: {team.id})") else: # Not portal user values["collaborator_team_form_url"] = False values["admin_teams"] = request.env['helpdesk.team'] return values def _get_teams_where_admin(self): """Get teams where current user is admin collaborator""" if not request.env.user or not request.env.user._is_portal(): return request.env['helpdesk.team'] partner = request.env.user.partner_id collaborator_records = request.env['helpdesk.team.collaborator'].search([ ('partner_id', '=', partner.id), ('access_mode', '=', 'admin'), ]) return collaborator_records.mapped('team_id') @http.route(['/my/helpdesk/teams//collaborators'], type='http', auth="user", website=True) def portal_team_collaborators(self, team_id=None, **kw): """Page to manage collaborators for a team (only for admin users)""" from odoo import _ team = request.env['helpdesk.team'].browse([team_id]) if not team.exists(): raise MissingError(_("This team does not exist.")) # Check if user is admin of this team admin_teams = self._get_teams_where_admin() if team not in admin_teams: raise AccessError(_("You don't have permission to manage collaborators for this team.")) # Get admin's commercial_partner_id to filter available partners admin_user = request.env.user admin_commercial_partner_id = admin_user.partner_id.commercial_partner_id.id # Get all collaborators of the team (use sudo() to bypass security rules for admin users) # Filter to only show collaborators from the same network all_collaborators = team.sudo().collaborator_ids collaborators = all_collaborators.filtered( lambda c: c.partner_id.commercial_partner_id.id == admin_commercial_partner_id ).sorted(lambda c: c.partner_id.name or '') values = { 'team': team, 'collaborators': collaborators, 'page_name': 'team_collaborators', 'admin_commercial_partner_id': admin_commercial_partner_id, 'current_partner_id': admin_user.partner_id.id, } return request.render('helpdesk_extras.portal_team_collaborators', values) @http.route(['/my/helpdesk/teams//collaborators/new'], type='http', auth="user", website=True) def portal_new_collaborator(self, team_id=None, **kw): """Page to create a new contact and add as collaborator""" from odoo import _ team = request.env['helpdesk.team'].browse([team_id]) if not team.exists(): raise MissingError(_("This team does not exist.")) # Check if user is admin of this team admin_teams = self._get_teams_where_admin() if team not in admin_teams: raise AccessError(_("You don't have permission to manage collaborators for this team.")) values = { 'team': team, 'page_name': 'new_collaborator', } return request.render('helpdesk_extras.portal_new_collaborator', values) @http.route(['/my/helpdesk/teams//collaborators/add'], type='http', auth="user", website=True, methods=['POST'], csrf=True) def portal_add_collaborator(self, team_id=None, partner_id=None, access_mode='user_own', **kw): """Add a new collaborator to the team (only for admin users)""" from odoo import _ from odoo.exceptions import ValidationError team = request.env['helpdesk.team'].browse([team_id]) if not team.exists(): request.session['error'] = _("This team does not exist.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Check if user is admin of this team admin_teams = self._get_teams_where_admin() if team not in admin_teams: request.session['error'] = _("You don't have permission to manage collaborators for this team.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Get admin's commercial_partner_id admin_user = request.env.user admin_commercial_partner_id = admin_user.partner_id.commercial_partner_id.id # Validate partner_id if not partner_id: request.session['error'] = _("Partner is required.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') try: partner_id = int(partner_id) partner = request.env['res.partner'].sudo().browse([partner_id]) if not partner.exists(): request.session['error'] = _("Partner does not exist.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Validate partner belongs to same network if partner.commercial_partner_id.id != admin_commercial_partner_id: request.session['error'] = _("You can only add contacts from your company network.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Validate partner is external if not partner.partner_share: request.session['error'] = _("Only external partners can be added as collaborators.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Validate access_mode if access_mode not in ['admin', 'user_all', 'user_own']: access_mode = 'user_own' # Check if collaborator already exists existing = team.sudo().collaborator_ids.filtered(lambda c: c.partner_id.id == partner_id) if existing: request.session['error'] = _("This partner is already a collaborator.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Create collaborator team.sudo().write({ 'collaborator_ids': [(0, 0, { 'partner_id': partner_id, 'access_mode': access_mode, })] }) # Subscribe partner to team messages team.sudo().message_subscribe(partner_ids=[partner_id]) request.session['success'] = _("Collaborator added successfully.") except (ValueError, TypeError, ValidationError) as e: request.session['error'] = str(e) return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') @http.route(['/my/helpdesk/teams//collaborators//update'], type='http', auth="user", website=True, methods=['POST'], csrf=True) def portal_update_collaborator(self, team_id=None, collaborator_id=None, access_mode=None, **kw): """Update a collaborator's access mode (only for admin users)""" from odoo import _ team = request.env['helpdesk.team'].browse([team_id]) if not team.exists(): request.session['error'] = _("This team does not exist.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Check if user is admin of this team admin_teams = self._get_teams_where_admin() if team not in admin_teams: request.session['error'] = _("You don't have permission to manage collaborators for this team.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Get admin's commercial_partner_id admin_user = request.env.user admin_commercial_partner_id = admin_user.partner_id.commercial_partner_id.id # Find collaborator collaborator = team.sudo().collaborator_ids.filtered(lambda c: c.id == collaborator_id) if not collaborator: request.session['error'] = _("Collaborator not found.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Validate collaborator belongs to same network if collaborator.partner_id.commercial_partner_id.id != admin_commercial_partner_id: request.session['error'] = _("You can only manage contacts from your company network.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Validate access_mode if access_mode and access_mode in ['admin', 'user_all', 'user_own']: collaborator.sudo().write({'access_mode': access_mode}) request.session['success'] = _("Collaborator updated successfully.") else: request.session['error'] = _("Invalid access mode.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') @http.route(['/my/helpdesk/teams//collaborators//delete'], type='http', auth="user", website=True, methods=['POST'], csrf=True) def portal_delete_collaborator(self, team_id=None, collaborator_id=None, **kw): """Delete a collaborator from the team (only for admin users)""" from odoo import _ team = request.env['helpdesk.team'].browse([team_id]) if not team.exists(): request.session['error'] = _("This team does not exist.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Check if user is admin of this team admin_teams = self._get_teams_where_admin() if team not in admin_teams: request.session['error'] = _("You don't have permission to manage collaborators for this team.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Get admin's commercial_partner_id admin_user = request.env.user admin_commercial_partner_id = admin_user.partner_id.commercial_partner_id.id # Find collaborator collaborator = team.sudo().collaborator_ids.filtered(lambda c: c.id == collaborator_id) if not collaborator: request.session['error'] = _("Collaborator not found.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Validate collaborator belongs to same network if collaborator.partner_id.commercial_partner_id.id != admin_commercial_partner_id: request.session['error'] = _("You can only manage contacts from your company network.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Prevent self-deletion current_partner = admin_user.partner_id if collaborator.partner_id.id == current_partner.id: request.session['error'] = _("You cannot remove yourself as a collaborator.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Delete collaborator collaborator.sudo().unlink() request.session['success'] = _("Collaborator removed successfully.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') @http.route(['/my/helpdesk/teams//collaborators/search-partners'], type='json', auth="user", website=True, methods=['POST'], csrf=False) def portal_search_partners(self, team_id=None, search_term='', limit=20, **kw): """Search partners in the same commercial_partner_id network""" from odoo import _ team = request.env['helpdesk.team'].browse([team_id]) if not team.exists(): return {'error': _("This team does not exist.")} # Check if user is admin of this team admin_teams = self._get_teams_where_admin() if team not in admin_teams: return {'error': _("You don't have permission to manage collaborators for this team.")} # Get admin's commercial_partner_id admin_user = request.env.user admin_partner = admin_user.partner_id admin_commercial_partner = admin_partner.commercial_partner_id # Search partners in the same network # Use 'id' with 'child_of' to find all contacts in the commercial partner network # This recursively finds: # - The commercial partner itself # - All child contacts (using parent_id relationship) # This is the standard Odoo way to find all related contacts domain = [ ('id', 'child_of', admin_commercial_partner.id), ('partner_share', '=', True), '|', ('name', 'ilike', search_term), ('email', 'ilike', search_term), ] # Also exclude contacts that are already collaborators in this team existing_collaborator_ids = team.sudo().collaborator_ids.mapped('partner_id').ids if existing_collaborator_ids: domain.append(('id', 'not in', existing_collaborator_ids)) partners = request.env['res.partner'].sudo().search(domain, limit=limit) # Debug logging import logging _logger = logging.getLogger(__name__) _logger.info(f"Search partners - term: '{search_term}', found: {len(partners)}") for p in partners: _logger.info(f" - {p.name} (ID: {p.id}, email: {p.email})") result = { 'partners': [ { 'id': p.id, 'name': p.name, 'email': p.email or '', 'display_name': p.display_name, } for p in partners ] } _logger.info(f"Returning result: {len(result['partners'])} partners") return result @http.route(['/my/helpdesk/teams//collaborators/create'], type='http', auth="user", website=True, methods=['POST'], csrf=True) def portal_create_collaborator(self, team_id=None, **kw): """Create a new contact and add as collaborator in one step""" from odoo import _ from odoo.exceptions import ValidationError from odoo.tools import email_normalize team = request.env['helpdesk.team'].browse([team_id]) if not team.exists(): request.session['error'] = _("This team does not exist.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Check if user is admin of this team admin_teams = self._get_teams_where_admin() if team not in admin_teams: request.session['error'] = _("You don't have permission to manage collaborators for this team.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Get form data name = kw.get('name', '').strip() email = kw.get('email', '').strip() phone = kw.get('phone', '').strip() function_ = kw.get('function', '').strip() access_mode = kw.get('access_mode', 'user_own') # Validate inputs if not name: request.session['error'] = _("Name is required.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators/new') if not email: request.session['error'] = _("Email is required.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators/new') # Validate and normalize email email_normalized = email_normalize(email) if not email_normalized: request.session['error'] = _("Invalid email address format.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators/new') # Validate access_mode if access_mode not in ['admin', 'user_all', 'user_own']: access_mode = 'user_own' # Check if email already exists existing_partner = request.env['res.partner'].sudo().search([ ('email_normalized', '=', email_normalized) ], limit=1) if existing_partner: # Check if already a collaborator existing_collaborator = team.sudo().collaborator_ids.filtered( lambda c: c.partner_id.id == existing_partner.id ) if existing_collaborator: request.session['error'] = _("This contact is already a collaborator in this team.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') # Use existing partner partner = existing_partner else: # Get admin's commercial_partner_id admin_user = request.env.user admin_commercial_partner = admin_user.partner_id.commercial_partner_id # Create new partner try: partner_vals = { 'name': name, 'email': email_normalized, 'partner_share': True, 'type': 'contact', 'parent_id': admin_commercial_partner.id, } if phone: partner_vals['phone'] = phone if function_: partner_vals['function'] = function_ partner = request.env['res.partner'].sudo().create(partner_vals) # Log creation import logging _logger = logging.getLogger(__name__) _logger.info(f"Created new partner: {partner.name} (ID: {partner.id}) by admin {admin_user.name}") except Exception as e: import logging _logger = logging.getLogger(__name__) _logger.error(f"Error creating partner: {str(e)}", exc_info=True) request.session['error'] = _("An error occurred while creating the contact. Please try again.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators/new') # Add as collaborator try: # Check if already a collaborator existing_collaborator = team.sudo().collaborator_ids.filtered( lambda c: c.partner_id.id == partner.id ) if existing_collaborator: # Update access mode existing_collaborator.sudo().write({'access_mode': access_mode}) request.session['success'] = _("Collaborator updated successfully.") else: # Create new collaborator team.sudo().write({ 'collaborator_ids': [(0, 0, { 'partner_id': partner.id, 'access_mode': access_mode, })] }) # Subscribe partner to team messages team.sudo().message_subscribe(partner_ids=[partner.id]) request.session['success'] = _("Contact created and added as collaborator successfully.") except Exception as e: import logging _logger = logging.getLogger(__name__) _logger.error(f"Error adding collaborator: {str(e)}", exc_info=True) request.session['error'] = _("An error occurred while adding the collaborator. Please try again.") return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators/new') return request.redirect(f'/my/helpdesk/teams/{team_id}/collaborators') @http.route(['/my/helpdesk/teams//collaborators/create-partner'], type='json', auth="user", website=True, methods=['POST'], csrf=False) def portal_create_partner(self, team_id=None, name='', email='', phone='', function='', **kw): """Create a new partner in the same commercial_partner_id network""" from odoo import _ from odoo.exceptions import ValidationError from odoo.tools import email_normalize team = request.env['helpdesk.team'].browse([team_id]) if not team.exists(): return {'error': _("This team does not exist.")} # Check if user is admin of this team admin_teams = self._get_teams_where_admin() if team not in admin_teams: return {'error': _("You don't have permission to manage collaborators for this team.")} # Validate inputs if not name or not name.strip(): return {'error': _("Name is required.")} name = name.strip() # Validate and normalize email if provided email_normalized = None if email and email.strip(): email = email.strip() email_normalized = email_normalize(email) if not email_normalized: return {'error': _("Invalid email address format.")} # Check if email already exists existing_partner = request.env['res.partner'].sudo().search([ ('email_normalized', '=', email_normalized) ], limit=1) if existing_partner: return { 'error': _("A contact with email %s already exists.") % email, 'existing_partner_id': existing_partner.id, 'existing_partner_name': existing_partner.name, } # Get admin's commercial_partner_id admin_user = request.env.user admin_commercial_partner = admin_user.partner_id.commercial_partner_id # Create new partner try: partner_vals = { 'name': name, 'partner_share': True, 'type': 'contact', } if email_normalized: partner_vals['email'] = email_normalized if phone and phone.strip(): partner_vals['phone'] = phone.strip() if function and function.strip(): partner_vals['function'] = function.strip() # Set parent to commercial_partner (creates as child contact) partner_vals['parent_id'] = admin_commercial_partner.id partner = request.env['res.partner'].sudo().create(partner_vals) # Log creation import logging _logger = logging.getLogger(__name__) _logger.info(f"Created new partner: {partner.name} (ID: {partner.id}) by admin {admin_user.name}") return { 'partner': { 'id': partner.id, 'name': partner.name, 'email': partner.email or '', 'display_name': partner.display_name, } } except ValidationError as e: return {'error': str(e)} except Exception as e: import logging _logger = logging.getLogger(__name__) _logger.error(f"Error creating partner: {str(e)}", exc_info=True) return {'error': _("An error occurred while creating the contact. Please try again.")} @http.route(['/my/ticket//approve'], type='http', auth='public', methods=['POST'], website=True, csrf=True) def ticket_approve(self, ticket_id, access_token=None, **kwargs): """Approve ticket by customer""" from odoo import _, fields try: ticket_sudo = self._document_check_access('helpdesk.ticket', ticket_id, access_token) except (AccessError, MissingError): return request.redirect('/my') # Approve ticket_sudo.write({ 'customer_approval_status': 'approved', }) # Message in chatter ticket_sudo.message_post( body=_("Ticket approved by customer"), message_type='notification', subtype_xmlid='mail.mt_comment', ) return request.redirect(ticket_sudo.get_portal_url() + '?message=approved') @http.route(['/my/ticket//reject'], type='http', auth='public', methods=['POST'], website=True, csrf=True) def ticket_reject(self, ticket_id, access_token=None, reason=None, **kwargs): """Reject ticket by customer""" from odoo import _, fields try: ticket_sudo = self._document_check_access('helpdesk.ticket', ticket_id, access_token) except (AccessError, MissingError): return request.redirect('/my') # Reject ticket_sudo.write({ 'customer_approval_status': 'rejected', 'customer_rejection_reason': reason or _('No reason provided'), }) # Message in chatter rejection_msg = reason or _('No reason provided') ticket_sudo.message_post( body=_("Ticket rejected by customer: %s") % rejection_msg, message_type='comment', subtype_xmlid='mail.mt_comment', ) return request.redirect(ticket_sudo.get_portal_url() + '?message=rejected')