bemade-addons/bemade_sports_clinic/controllers/player_management_portal.py
Denis Durepos 06b9c6d201 portal(events): polish headers and detail view
- Stack action buttons onto a second line in list/detail views\n- Remove redundant team/venue under event title (retained in details card)\n- Minor styling/consistency tweaks
2025-09-01 20:21:36 -04:00

945 lines
45 KiB
Python

import logging
from odoo import http, fields, _
from odoo.exceptions import UserError, ValidationError
from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal, pager
from .access_control_mixin import AccessControlMixin
from dateutil.relativedelta import relativedelta
_logger = logging.getLogger(__name__)
class PlayerManagementPortal(CustomerPortal, AccessControlMixin):
"""Controller for player management functionality in the portal"""
# Access control methods now inherited from AccessControlMixin
@http.route(['/my/player/create'], type='http', auth='user', website=True)
def create_player_form(self, **get):
"""Standalone create player with search-first workflow.
Shows a search UI first; only renders the full create form when a search
is attempted and yields no matching players.
"""
user = request.env.user
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
is_coach = request.env.user.has_group('bemade_sports_clinic.group_portal_team_coach')
# Gather query params for search-first flow
first_name = (get.get('first_name') or '').strip()
last_name = (get.get('last_name') or '').strip()
dob = (get.get('date_of_birth') or '').strip()
attempted = any(k in get for k in ('first_name', 'last_name', 'date_of_birth'))
searched = bool(first_name or last_name or dob)
# Perform search similar to team add/link, but without team context
Patient = request.env['sports.patient']
active_rs = Patient.browse([])
archived_rs = Patient.browse([])
if searched and (first_name or last_name):
like_first = f"%{first_name}%" if first_name else "%"
like_last = f"%{last_name}%" if last_name else "%"
domain = [
('first_name', 'ilike', like_first),
('last_name', 'ilike', like_last),
]
if dob:
domain.append(('date_of_birth', '=', dob))
active_rs = Patient.search(domain + [('active', '=', True)], limit=20)
# Only TPs/admins can see archived
if is_treatment_prof or request.env.user.has_group('base.group_system'):
archived_rs = Patient.with_context(active_test=False).search(domain + [('active', '=', False)], limit=20)
def _to_dict(p):
return {
'id': p.id,
'name': p.name,
'first_name': p.first_name,
'last_name': p.last_name,
'date_of_birth': p.date_of_birth or '',
'active': bool(p.active),
}
# Staff-only teams for multi-select
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
values = self._prepare_portal_layout_values()
values.update({
'page_name': 'create_player',
'is_treatment_prof': is_treatment_prof,
'is_coach': is_coach,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
# search-first context
'first_name': first_name,
'last_name': last_name,
'date_of_birth': dob,
'attempted': attempted,
'searched': searched,
'active_results': [_to_dict(p) for p in active_rs],
'archived_results': [_to_dict(p) for p in archived_rs],
})
if attempted and not searched:
values['error'] = _('Provide at least a first or last name to search')
return request.render('bemade_sports_clinic.portal_create_player', values)
@http.route(['/my/player/create/save'], type='http', auth='user', website=True, methods=['POST'])
def create_player_submit(self, **post):
"""Handle standalone player creation submit."""
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
is_coach = user.has_group('bemade_sports_clinic.group_portal_team_coach')
first_name = (post.get('first_name') or '').strip()
last_name = (post.get('last_name') or '').strip()
dob = (post.get('date_of_birth') or '').strip()
if not first_name or not last_name:
request.session['notification'] = {
'type': 'danger',
'message': _('First name and last name are required')
}
return request.redirect('/my/player/create')
# Team selection (optional). Restrict to staff teams to avoid tampering
try:
selected_team_ids = [int(tid) for tid in request.httprequest.form.getlist('team_ids')]
except Exception:
selected_team_ids = []
allowed_team_ids = request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids
selected_team_ids = [tid for tid in selected_team_ids if tid in allowed_team_ids]
# Require at least one team for treatment professionals to ensure access control
if is_treatment_prof and not selected_team_ids:
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
values = self._prepare_portal_layout_values()
values.update({
'page_name': 'create_player',
'is_treatment_prof': is_treatment_prof,
'is_coach': is_coach,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
# keep the original search context so the create form shows
'first_name': first_name,
'last_name': last_name,
'date_of_birth': dob,
'attempted': True,
'searched': True,
'active_results': [],
'archived_results': [],
# provide posted values to prefill the form
'form_data': dict(post),
'error': _('Please select at least one team to assign to this player.'),
})
return request.render('bemade_sports_clinic.portal_create_player', values)
# Validate DOB format and range if provided
if dob:
try:
# Will raise if the date is not a valid string for a Date field
dob_date = fields.Date.to_date(dob)
except Exception:
_logger.info('Invalid date_of_birth provided on create: %s', dob)
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
values = self._prepare_portal_layout_values()
values.update({
'page_name': 'create_player',
'is_treatment_prof': is_treatment_prof,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
# keep the original search context so the create form shows
'first_name': first_name,
'last_name': last_name,
'date_of_birth': dob,
'attempted': True,
'searched': True,
'active_results': [],
'archived_results': [],
# provide posted values to prefill the form
'form_data': dict(post),
'error': _('Please enter a valid Date of Birth (YYYY-MM-DD).'),
})
return request.render('bemade_sports_clinic.portal_create_player', values)
# Range checks: not in the future, not older than 120 years
today = fields.Date.context_today(request.env.user) or fields.Date.today()
min_dob = today - relativedelta(years=120)
if dob_date > today or dob_date < min_dob:
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
values = self._prepare_portal_layout_values()
values.update({
'page_name': 'create_player',
'is_treatment_prof': is_treatment_prof,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
# keep the original search context so the create form shows
'first_name': first_name,
'last_name': last_name,
'date_of_birth': dob,
'attempted': True,
'searched': True,
'active_results': [],
'archived_results': [],
# provide posted values to prefill the form
'form_data': dict(post),
'error': _('Date of Birth must not be in the future and not be more than 120 years ago.'),
})
return request.render('bemade_sports_clinic.portal_create_player', values)
vals = {
'first_name': first_name,
'last_name': last_name,
}
if dob:
vals['date_of_birth'] = dob
# Optional contact and address fields
for key in ['email', 'phone', 'street', 'street2', 'city', 'zip']:
if key in post:
vals[key] = post.get(key) or False
if post.get('state_id'):
try:
vals['state_id'] = int(post.get('state_id'))
except Exception:
pass
if post.get('country_id'):
try:
vals['country_id'] = int(post.get('country_id'))
except Exception:
pass
# Additional fields for treatment professionals
if is_treatment_prof:
if selected_team_ids:
vals['team_ids'] = [(6, 0, list(set(selected_team_ids)))]
if 'allergies' in post:
_all = (post.get('allergies') or '').strip()
vals['allergies'] = _all if _all else False
if 'team_info_notes' in post:
_notes = (post.get('team_info_notes') or '').strip()
vals['team_info_notes'] = _notes if _notes else False
if post.get('match_status'):
vals['match_status'] = post.get('match_status')
if post.get('practice_status'):
vals['practice_status'] = post.get('practice_status')
# Use public create method with permission checks
try:
patient = request.env['sports.patient'].create_portal_patient(vals)
except ValidationError as ve:
# Re-render the create form (search-first view) with preserved input and a friendly message
_logger.info('ValidationError during player create: %s', ve)
# Rebuild the context needed by the search-first page
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
values = self._prepare_portal_layout_values()
values.update({
'page_name': 'create_player',
'is_treatment_prof': is_treatment_prof,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
# keep the original search context so the create form shows
'first_name': first_name,
'last_name': last_name,
'date_of_birth': dob,
'attempted': True,
'searched': True,
'active_results': [],
'archived_results': [],
# provide posted values to prefill the form
'form_data': dict(post),
'error': str(ve) or _('Invalid combination of match and practice status.'),
})
return request.render('bemade_sports_clinic.portal_create_player', values)
except Exception as e:
# Render the create form with a generic error and preserve inputs
_logger.exception('Error creating patient from portal create')
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
values = self._prepare_portal_layout_values()
values.update({
'page_name': 'create_player',
'is_treatment_prof': is_treatment_prof,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
# keep the original search context so the create form shows
'first_name': first_name,
'last_name': last_name,
'date_of_birth': dob,
'attempted': True,
'searched': True,
'active_results': [],
'archived_results': [],
# provide posted values to prefill the form
'form_data': dict(post),
'error': str(e) or _('There was an error creating the player. Please review your inputs and try again.'),
})
return request.render('bemade_sports_clinic.portal_create_player', values)
# Optionally create a primary emergency contact if provided (TPs and coaches)
if is_treatment_prof or is_coach:
ec_name = (post.get('ec_name') or '').strip()
ec_type = (post.get('ec_contact_type') or '').strip()
if ec_name and ec_type:
contact_vals = {
'patient_id': patient.id,
'name': ec_name,
'contact_type': ec_type,
}
if post.get('ec_mobile'):
contact_vals['mobile'] = post.get('ec_mobile')
if post.get('ec_email'):
contact_vals['email'] = post.get('ec_email')
try:
request.env['sports.patient.contact'].sudo().create(contact_vals)
except Exception:
_logger.exception('Failed to create primary emergency contact during player create')
return request.redirect(f"/my/player?player_id={patient.id}")
@http.route(['/my/player/edit'], type='http', auth='user', website=True)
def edit_player_form(self, patient_id, **post):
"""Show form to edit player information"""
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return_url = post.get('return_url', f'/my/player?player_id={patient_id}')
# Check if user is a treatment professional or coach
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
is_coach = user.has_group('bemade_sports_clinic.group_portal_team_coach')
teams = patient.team_ids
# All teams for multi-select limited to current user's staff teams
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
# Determine team context from query or post and whether a coach can request removal
# Only consider a team context if the current user is staff on that team and the player is a member
raw_team_ctx = request.httprequest.args.get('team_id') or post.get('team_id')
team_context_id = None
if raw_team_ctx:
try:
cand_id = int(raw_team_ctx)
# Validate access via shared mixin helper
team_obj = self._check_team_access(cand_id, check_staff=True)
if team_obj and team_obj in teams:
team_context_id = cand_id
except Exception:
# Ignore invalid team context silently
team_context_id = None
# Compute removal visibility/target for coaches
player_team_count = len(teams)
can_request_removal = bool(is_coach and ((team_context_id is not None) or (player_team_count == 1)))
removal_team_id = None
if can_request_removal:
if team_context_id is not None:
removal_team_id = team_context_id
elif player_team_count == 1:
removal_team_id = teams[0].id
# Create a dictionary with patient info for protected fields
patient_info = {}
# Only include protected fields if user has appropriate permissions
if is_treatment_prof:
# Access fields directly - field-level security is already defined
# with appropriate groups for each field
# Additional fields available to treatment professionals
# Basic fields
patient_info['date_of_birth'] = patient.date_of_birth
patient_info['age'] = patient.age
patient_info['allergies'] = patient.allergies
patient_info['team_info_notes'] = patient.team_info_notes
# Status fields
patient_info['match_status'] = patient.match_status
patient_info['practice_status'] = patient.practice_status
# Injury tracking fields
patient_info['injured_since'] = patient.injured_since
# Add any other protected fields that should be available to treatment professionals
# You can add more fields here as needed
# Patient info prepared for treatment professional view
# Get Canada and Canadian provinces/territories for address dropdowns
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
# Determine primary emergency contact (lowest sequence)
primary_contact = request.env['sports.patient.contact'].search([
('patient_id', '=', patient.id)
], order='sequence,id', limit=1)
values = {
'patient': patient, # Keep original patient
'patient_info': patient_info, # Add patient_info for protected fields
'teams': teams,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
# Primary emergency contact defaults for prefill
'ec_name': primary_contact.name if primary_contact else '',
'ec_contact_type': primary_contact.contact_type if primary_contact else '',
'ec_mobile': primary_contact.mobile if primary_contact else '',
'ec_email': primary_contact.email if primary_contact else '',
'return_url': return_url,
'page_name': 'edit_player',
'is_treatment_prof': is_treatment_prof,
'is_coach': is_coach,
# Coach removal request context
'can_request_removal': can_request_removal,
'removal_team_id': removal_team_id,
'team_context_id': team_context_id,
}
return request.render('bemade_sports_clinic.portal_edit_player', values)
@http.route(['/my/player/save'], type='http', auth='user', website=True, methods=['POST'])
def edit_player_submit(self, **post):
"""Process the form submission to update player information"""
patient_id = post.get('patient_id')
if not patient_id:
return request.redirect('/my/players')
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
# Check if user is a treatment professional or coach
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
is_coach = request.env.user.has_group('bemade_sports_clinic.group_portal_team_coach')
# If DOB is being changed/provided, validate format upfront to avoid unhelpful errors
dob_str = (post.get('date_of_birth') or '').strip()
if dob_str:
try:
dob_date = fields.Date.to_date(dob_str)
except Exception:
# Rebuild dropdowns and re-render with preserved inputs
user = request.env.user
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
values = {
'patient': patient,
'patient_info': {},
'teams': patient.team_ids,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
'return_url': post.get('return_url', f'/my/player?player_id={patient_id}'),
'page_name': 'edit_player',
'is_treatment_prof': is_treatment_prof,
'is_coach': is_coach,
'error': _('Please enter a valid Date of Birth (YYYY-MM-DD).'),
'form_data': dict(post),
}
return request.render('bemade_sports_clinic.portal_edit_player', values)
# Range checks: not in the future, not older than 120 years
today = fields.Date.context_today(request.env.user) or fields.Date.today()
min_dob = today - relativedelta(years=120)
if dob_date > today or dob_date < min_dob:
user = request.env.user
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
values = {
'patient': patient,
'patient_info': {},
'teams': patient.team_ids,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
'return_url': post.get('return_url', f'/my/player?player_id={patient_id}'),
'page_name': 'edit_player',
'is_treatment_prof': is_treatment_prof,
'is_coach': is_coach,
'error': _('Date of Birth must not be in the future and not be more than 120 years ago.'),
'form_data': dict(post),
}
return request.render('bemade_sports_clinic.portal_edit_player', values)
# Prepare values for patient update
vals = {}
# Basic information - any portal user with access can update these
if post.get('first_name') and post.get('last_name'):
vals.update({
'first_name': post.get('first_name'),
'last_name': post.get('last_name'),
})
# Contact information
if post.get('email'):
vals.update({
'email': post.get('email'),
})
if post.get('phone'):
vals.update({
'phone': post.get('phone'),
})
# Address information - any portal user with access can update these
address_fields = ['street', 'street2', 'city', 'zip']
for field in address_fields:
if field in post:
vals[field] = post.get(field) or False
# Handle state and country selections
if post.get('state_id'):
try:
state_id = int(post.get('state_id'))
vals['state_id'] = state_id
except (ValueError, TypeError):
pass # Invalid state_id, skip
if post.get('country_id'):
try:
country_id = int(post.get('country_id'))
vals['country_id'] = country_id
except (ValueError, TypeError):
pass # Invalid country_id, skip
# Additional fields that only treatment professionals can update
if is_treatment_prof:
# Team assignments via multi-select
try:
team_id_list = [int(tid) for tid in request.httprequest.form.getlist('team_ids')]
except Exception:
team_id_list = []
if team_id_list:
# Defense-in-depth: restrict to teams where current user is staff
allowed_team_ids = request.env['sports.team.staff'].search([
('partner_id', '=', request.env.user.partner_id.id)
]).mapped('team_id').ids
filtered_team_ids = [tid for tid in team_id_list if tid in allowed_team_ids]
vals['team_ids'] = [(6, 0, list(set(filtered_team_ids)))]
if post.get('date_of_birth'):
vals.update({
'date_of_birth': post.get('date_of_birth'),
})
# Medical information
if 'allergies' in post:
_all = (post.get('allergies') or '').strip()
vals.update({
'allergies': _all if _all else False,
})
if 'team_info_notes' in post:
_notes = (post.get('team_info_notes') or '').strip()
vals.update({
'team_info_notes': _notes if _notes else False,
})
# Status fields
if post.get('match_status'):
vals.update({
'match_status': post.get('match_status'),
})
if post.get('practice_status'):
vals.update({
'practice_status': post.get('practice_status'),
})
# Update the patient - no sudo needed as field-level security is in place
if vals:
try:
patient.write(vals)
except ValidationError as ve:
# Re-render edit form with preserved input and error
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
# All teams for multi-select limited to current user's staff teams
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
values = {
'patient': patient,
'patient_info': {},
'teams': patient.team_ids,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
'return_url': post.get('return_url', f'/my/player?player_id={patient_id}'),
'page_name': 'edit_player',
'is_treatment_prof': is_treatment_prof,
'is_coach': is_coach,
'error': str(ve) or _('Invalid combination of match and practice status.'),
'form_data': dict(post),
}
return request.render('bemade_sports_clinic.portal_edit_player', values)
except Exception as e:
# Generic exception: re-render with generic error and preserved inputs
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
all_teams = request.env['sports.team'].search([
('id', 'in', request.env['sports.team.staff'].search([
('partner_id', '=', user.partner_id.id)
]).mapped('team_id').ids)
], order='name')
values = {
'patient': patient,
'patient_info': {},
'teams': patient.team_ids,
'all_teams': all_teams,
'states': states,
'countries': countries,
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
'return_url': post.get('return_url', f'/my/player?player_id={patient_id}'),
'page_name': 'edit_player',
'is_treatment_prof': is_treatment_prof,
'is_coach': is_coach,
'error': str(e) or _('An unexpected error occurred.'),
'form_data': dict(post),
}
return request.render('bemade_sports_clinic.portal_edit_player', values)
# Update or create primary emergency contact (TPs and coaches), regardless of patient field changes
if is_treatment_prof or is_coach:
ec_name = (post.get('ec_name') or '').strip()
ec_type = (post.get('ec_contact_type') or '').strip()
ec_mobile = (post.get('ec_mobile') or '').strip()
ec_email = (post.get('ec_email') or '').strip()
# If any EC field provided, attempt update/create
if any([ec_name, ec_type, ec_mobile, ec_email]):
primary_contact = request.env['sports.patient.contact'].search([
('patient_id', '=', patient.id)
], order='sequence,id', limit=1)
if primary_contact:
update_vals = {}
# Only update name/type if provided (avoid clearing required selection)
if ec_name:
update_vals['name'] = ec_name
if ec_type:
update_vals['contact_type'] = ec_type
# For mobile/email, explicitly set to value or False to clear
if 'ec_mobile' in post:
update_vals['mobile'] = ec_mobile or False
if 'ec_email' in post:
update_vals['email'] = ec_email or False
if update_vals:
try:
primary_contact.sudo().write(update_vals)
except Exception:
_logger.exception('Failed to update primary emergency contact for patient %s', patient.id)
else:
# Create only if we have minimally required fields
if ec_name and ec_type:
create_vals = {
'patient_id': patient.id,
'name': ec_name,
'contact_type': ec_type,
'sequence': 0,
}
if 'ec_mobile' in post:
create_vals['mobile'] = ec_mobile or False
if 'ec_email' in post:
create_vals['email'] = ec_email or False
try:
request.env['sports.patient.contact'].sudo().create(create_vals)
except Exception:
_logger.exception('Failed to create primary emergency contact for patient %s', patient.id)
return request.redirect(f'/my/player?player_id={patient_id}')
@http.route(['/my/player/contact/add'], type='http', auth='user', website=True)
def add_contact_form(self, patient_id, **post):
"""Show form to add a new emergency contact for a player"""
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return_url = post.get('return_url', f'/my/player?player_id={patient_id}')
# Check if user is a treatment professional or coach
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
is_coach = user.has_group('bemade_sports_clinic.group_portal_team_coach')
# Only TPs and coaches can add emergency contacts
if not (is_treatment_prof or is_coach):
return request.redirect(return_url)
values = {
'patient': patient,
'return_url': return_url,
'page_name': 'add_contact',
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
}
return request.render('bemade_sports_clinic.portal_add_contact', values)
@http.route(['/my/player/contact/save'], type='http', auth='user', website=True, methods=['POST'])
def add_contact_submit(self, **post):
"""Process the form submission to add a new emergency contact"""
patient_id = post.get('patient_id')
if not patient_id:
return request.redirect('/my/players')
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
# Check if user is a treatment professional or coach
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
is_coach = request.env.user.has_group('bemade_sports_clinic.group_portal_team_coach')
# Only TPs and coaches can add emergency contacts
if not (is_treatment_prof or is_coach):
return request.redirect(f'/my/player?player_id={patient_id}')
# Required fields
if not post.get('name') or not post.get('contact_type'):
values = {
'patient': patient,
'error': _("Name and contact type are required fields"),
'return_url': f'/my/player?player_id={patient_id}',
'page_name': 'add_contact',
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
}
values.update(post)
return request.render('bemade_sports_clinic.portal_add_contact', values)
# Prepare values for contact creation
vals = {
'patient_id': int(patient_id),
'name': post.get('name'),
'contact_type': post.get('contact_type'),
}
# Optional fields
if post.get('mobile'):
vals['mobile'] = post.get('mobile')
if post.get('email'):
vals['email'] = post.get('email')
# Create the contact
request.env['sports.patient.contact'].sudo().create(vals)
return request.redirect(f'/my/player?player_id={patient_id}')
@http.route(['/my/player/contact/edit'], type='http', auth='user', website=True)
def edit_contact_form(self, contact_id, **post):
"""Show form to edit an existing emergency contact"""
contact = request.env['sports.patient.contact'].browse(int(contact_id))
if not contact.exists():
return request.redirect('/my/players')
try:
patient = self._check_access_to_patient(contact.patient_id.id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return_url = post.get('return_url', f'/my/player?player_id={patient.id}')
# Check if user is a treatment professional or coach
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
is_coach = request.env.user.has_group('bemade_sports_clinic.group_portal_team_coach')
# Only TPs and coaches can edit emergency contacts
if not (is_treatment_prof or is_coach):
return request.redirect(return_url)
values = {
'patient': patient,
'contact': contact,
'return_url': return_url,
'page_name': 'edit_contact',
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
}
return request.render('bemade_sports_clinic.portal_edit_contact', values)
@http.route(['/my/player/contact/update'], type='http', auth='user', website=True, methods=['POST'])
def edit_contact_submit(self, **post):
"""Process the form submission to update an emergency contact"""
contact_id = post.get('contact_id')
if not contact_id:
return request.redirect('/my/players')
contact = request.env['sports.patient.contact'].browse(int(contact_id))
if not contact.exists():
return request.redirect('/my/players')
try:
patient = self._check_access_to_patient(contact.patient_id.id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
# Check if user is a treatment professional
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
is_coach = request.env.user.has_group('bemade_sports_clinic.group_portal_team_coach')
# Only TPs and coaches can edit emergency contacts
if not (is_treatment_prof or is_coach):
return request.redirect(f'/my/player?player_id={patient.id}')
# Required fields
if not post.get('name') or not post.get('contact_type'):
values = {
'patient': patient,
'contact': contact,
'error': _("Name and contact type are required fields"),
'return_url': f'/my/player?player_id={patient.id}',
'page_name': 'edit_contact',
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
}
values.update(post)
return request.render('bemade_sports_clinic.portal_edit_contact', values)
# Prepare values for contact update
vals = {
'name': post.get('name'),
'contact_type': post.get('contact_type'),
}
# Optional fields
if post.get('mobile'):
vals['mobile'] = post.get('mobile')
else:
vals['mobile'] = False
if post.get('email'):
vals['email'] = post.get('email')
else:
vals['email'] = False
# Update the contact
contact.sudo().write(vals)
return request.redirect(f'/my/player?player_id={patient.id}')
@http.route(['/my/player/contact/delete'], type='http', auth='user', website=True, methods=['POST'])
def delete_contact(self, contact_id, **post):
"""Delete an emergency contact"""
contact = request.env['sports.patient.contact'].browse(int(contact_id))
if not contact.exists():
return request.redirect('/my/players')
try:
patient = self._check_access_to_patient(contact.patient_id.id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
# Check if user is a treatment professional
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Regular coaches shouldn't be able to delete emergency contacts
if not is_treatment_prof:
return request.redirect(f'/my/player?player_id={patient.id}')
# Delete the contact
contact.sudo().unlink()
return request.redirect(f'/my/player?player_id={patient.id}')