From f5911f076764b29ed63db4e637516f5eb7077c78 Mon Sep 17 00:00:00 2001 From: Denis Durepos Date: Sun, 27 Jul 2025 21:32:10 -0400 Subject: [PATCH] Fix portal activity modal dismiss buttons and XML syntax errors - Replace unreliable data-dismiss='modal' with direct JavaScript closeModal() calls - Add robust closeModal() function with Bootstrap detection and DOM fallback - Fix Complete, Reschedule, and Cancel modal dismiss button functionality - Wrap JavaScript in CDATA section to resolve XML parsing errors with unescaped ampersands - Add copyFeedbackToHidden() and copyDateToHidden() functions for proper form data transfer - Ensure all modal action buttons work consistently across portal UI All portal activity modal interactions now functional with console logging for debugging. --- .../controllers/task_management_portal.py | 30 +- .../controllers/team_staff_portal.py | 10 + bemade_sports_clinic/models/patient.py | 8 + bemade_sports_clinic/models/patient_injury.py | 11 + bemade_sports_clinic/models/sports_team.py | 19 +- .../views/sports_clinic_portal_views.xml | 60 +++- .../task_management_portal_templates.xml | 277 +++++++++++------- 7 files changed, 296 insertions(+), 119 deletions(-) diff --git a/bemade_sports_clinic/controllers/task_management_portal.py b/bemade_sports_clinic/controllers/task_management_portal.py index 56d7989..2977180 100644 --- a/bemade_sports_clinic/controllers/task_management_portal.py +++ b/bemade_sports_clinic/controllers/task_management_portal.py @@ -39,11 +39,20 @@ class TaskManagementPortal(CustomerPortal): # User must be staff on at least one of the patient's teams if not (user_teams & patient_teams): raise UserError(_('You do not have access to this injury.')) + + # For team records, check if user is staff on the team + elif model_name == 'sports.team': + # Check if user has access through team staff relationships + user_teams = user.partner_id.team_staff_rel_ids.mapped('team_id') + + # User must be staff on this specific team + if record not in user_teams: + raise UserError(_('You do not have access to this team.')) return record @http.route(['/my/activities'], type='http', auth='user', website=True) - def view_activities(self, model=None, **kw): + def view_activities(self, model=None, res_id=None, **kw): """Display list of activities assigned to the current user""" user = request.env.user partner = user.partner_id @@ -54,8 +63,11 @@ class TaskManagementPortal(CustomerPortal): # Apply model filtering if specified if model: domain.append(('res_model', '=', model)) + # Apply res_id filtering if specified + if res_id: + domain.append(('res_id', '=', int(res_id))) else: - domain.append(('res_model', 'in', ['sports.patient', 'sports.patient.injury'])) + domain.append(('res_model', 'in', ['sports.patient', 'sports.patient.injury', 'sports.team'])) # Get activities assigned to this user activities = request.env['mail.activity'].search(domain, order='date_deadline asc') @@ -63,6 +75,7 @@ class TaskManagementPortal(CustomerPortal): # Group activities by model patient_activities = activities.filtered(lambda a: a.res_model == 'sports.patient') injury_activities = activities.filtered(lambda a: a.res_model == 'sports.patient.injury') + team_activities = activities.filtered(lambda a: a.res_model == 'sports.team') # Get activity types for filtering activity_types = request.env['mail.activity.type'].search([]) @@ -73,6 +86,7 @@ class TaskManagementPortal(CustomerPortal): 'activities': activities, 'patient_activities': patient_activities, 'injury_activities': injury_activities, + 'team_activities': team_activities, 'activity_types': activity_types, 'page_name': 'activities', 'today': date.today().strftime('%Y-%m-%d'), @@ -84,7 +98,7 @@ class TaskManagementPortal(CustomerPortal): def create_activity_form(self, model=None, res_id=None, **kw): """Display form to create a new activity""" # Validate model and res_id - valid_models = ['sports.patient', 'sports.patient.injury'] + valid_models = ['sports.patient', 'sports.patient.injury', 'sports.team'] if model not in valid_models or not res_id: return request.redirect('/my/activities') @@ -118,6 +132,8 @@ class TaskManagementPortal(CustomerPortal): # Default return URL if model == 'sports.patient': return_url = f'/my/player?player_id={res_id}' + elif model == 'sports.team': + return_url = f'/my/team?team_id={res_id}' else: # sports.patient.injury return_url = f'/my/player?player_id={record.patient_id.id}' @@ -145,7 +161,7 @@ class TaskManagementPortal(CustomerPortal): res_id = post.get('res_id') # Validate model and res_id - valid_models = ['sports.patient', 'sports.patient.injury'] + valid_models = ['sports.patient', 'sports.patient.injury', 'sports.team'] if model not in valid_models or not res_id: return request.redirect('/my/activities') @@ -258,7 +274,7 @@ class TaskManagementPortal(CustomerPortal): separator = '&' if '?' in return_url else '?' return request.redirect(f'{return_url}{separator}success=activity_updated') - @http.route(['/my/activity/complete'], type='http', auth='user', website=True, methods=['POST'], csrf=False) + @http.route(['/my/activity/complete'], type='http', auth='user', website=True, methods=['POST']) def complete_activity(self, **post): """Mark an activity as done""" activity_id = post.get('activity_id') @@ -280,7 +296,7 @@ class TaskManagementPortal(CustomerPortal): # Redirect to activities page return request.redirect('/my/activities') - @http.route(['/my/activity/cancel'], type='http', auth='user', website=True, methods=['POST'], csrf=False) + @http.route(['/my/activity/cancel'], type='http', auth='user', website=True, methods=['POST']) def cancel_activity(self, **post): """Cancel an activity""" activity_id = post.get('activity_id') @@ -299,7 +315,7 @@ class TaskManagementPortal(CustomerPortal): # Redirect to activities page return request.redirect('/my/activities') - @http.route(['/my/activity/reschedule'], type='http', auth='user', website=True, methods=['POST'], csrf=False) + @http.route(['/my/activity/reschedule'], type='http', auth='user', website=True, methods=['POST']) def reschedule_activity(self, **post): """Reschedule an activity to a new date""" activity_id = post.get('activity_id') diff --git a/bemade_sports_clinic/controllers/team_staff_portal.py b/bemade_sports_clinic/controllers/team_staff_portal.py index 0bbf891..d3a024e 100644 --- a/bemade_sports_clinic/controllers/team_staff_portal.py +++ b/bemade_sports_clinic/controllers/team_staff_portal.py @@ -8,9 +8,12 @@ class TeamStaffPortal(CustomerPortal): rtn = super()._prepare_home_portal_values(counters) teams_domain = self._prepare_teams_domain() players_domain = self._prepare_players_domain(teams_domain) + activities_domain = self._prepare_activities_domain() rtn['teams_count'] = http.request.env['sports.team'].search_count(teams_domain) rtn['players_count'] = http.request.env['sports.patient'].search_count( players_domain) + rtn['activities_count'] = http.request.env['mail.activity'].search_count( + activities_domain) return rtn @classmethod @@ -27,6 +30,13 @@ class TeamStaffPortal(CustomerPortal): ('team_ids', 'in', team_ids), ] + @classmethod + def _prepare_activities_domain(cls): + user = http.request.env.user + return [ + ('user_id', '=', user.id), + ] + @http.route(route=['/my/teams', '/my/teams/page/'], type='http', auth='user', website=True) def view_teams(self, page=0, **kw): """ Display the list of teams that a portal user has access to """ diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index 135a8fe..bcc235f 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -130,6 +130,7 @@ class Patient(models.Model): ) last_consultation_date = fields.Date(tracking=True) active_injury_count = fields.Integer(compute="_compute_active_injury_count") + activity_count = fields.Integer(compute="_compute_activity_count") allergies = fields.Text( groups="bemade_sports_clinic.group_sports_clinic_treatment_professional,bemade_sports_clinic.group_portal_treatment_professional", ) @@ -265,6 +266,13 @@ class Patient(models.Model): [('patient_id', '=', patient.id)] ) + def _compute_activity_count(self): + for rec in self: + rec.activity_count = self.env['mail.activity'].search_count([ + ('res_model', '=', 'sports.patient'), + ('res_id', '=', rec.id) + ]) + def action_view_patient_form(self): self.ensure_one() return { diff --git a/bemade_sports_clinic/models/patient_injury.py b/bemade_sports_clinic/models/patient_injury.py index 11cfa32..c8c2fdf 100644 --- a/bemade_sports_clinic/models/patient_injury.py +++ b/bemade_sports_clinic/models/patient_injury.py @@ -141,6 +141,10 @@ class PatientInjury(models.Model): string='Document Count', compute='_compute_document_count' ) + activity_count = fields.Integer( + string='Activity Count', + compute='_compute_activity_count' + ) @api.depends('treatment_note_ids') def _compute_treatment_note_count(self): @@ -152,6 +156,13 @@ class PatientInjury(models.Model): for record in self: record.document_count = len(record.document_ids) + def _compute_activity_count(self): + for rec in self: + rec.activity_count = self.env['mail.activity'].search_count([ + ('res_model', '=', 'sports.patient.injury'), + ('res_id', '=', rec.id) + ]) + @api.constrains("injury_date_na", "injury_date") def constrain_date_blank_only_if_na(self): for rec in self: diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index 0a98305..e4b5bb9 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -19,6 +19,7 @@ class SportsTeam(models.Model): player_count = fields.Integer(compute="_compute_player_counts") injured_count = fields.Integer(compute="_compute_player_counts") healthy_count = fields.Integer(compute="_compute_player_counts") + activity_count = fields.Integer(compute="_compute_activity_count") parent_id = fields.Many2one( comodel_name="res.partner", string="Parent Organization", @@ -96,6 +97,13 @@ class SportsTeam(models.Model): staff = rec.staff_ids.filtered(lambda r: r.role == "head_therapist") rec.head_therapist_id = staff.partner_id if staff else False + def _compute_activity_count(self): + for rec in self: + rec.activity_count = self.env['mail.activity'].search_count([ + ('res_model', '=', 'sports.team'), + ('res_id', '=', rec.id) + ]) + def _compute_allowed_user_ids(self): for rec in self: rec.allowed_user_ids = rec.staff_ids.user_ids @@ -214,16 +222,13 @@ class TeamStaff(models.Model): def _action_revoke_portal_access(self): """Private method containing the actual sudo operations for revoking portal access.""" - group_portal = self.env.ref("base.group_portal") group_public = self.env.ref("base.group_public") - # Deactivate the user and remove from portal group + + # Deactivate the user and set to public user type (standard Odoo approach) if self.user_ids: - self.user_ids.write( + self.user_ids.sudo().write( { - "groups_id": [ - Command.unlink(group_portal.id), - Command.link(group_public.id), - ], + "groups_id": [(6, 0, [group_public.id])], # Set to public user only "active": False, } ) diff --git a/bemade_sports_clinic/views/sports_clinic_portal_views.xml b/bemade_sports_clinic/views/sports_clinic_portal_views.xml index 6f1eff1..8cd6cd2 100644 --- a/bemade_sports_clinic/views/sports_clinic_portal_views.xml +++ b/bemade_sports_clinic/views/sports_clinic_portal_views.xml @@ -13,6 +13,11 @@ /my/players players_count + + Activities + /my/activities + activities_count + @@ -432,18 +516,7 @@ - +