- 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
945 lines
45 KiB
Python
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}')
|