- Refactor portal templates to eliminate intra-module template inheritance - Integrate emergency contacts section into player injuries template - Add full internal admin views for treatment notes with chatter support - Fix ORM warning in test_rights by using proper ORM commands - Update manifest to reflect new functionality (v18.0.1.9.0) This refactoring improves template stability by removing XPath errors and provides treatment professionals with better access to emergency contacts and treatment notes both in portal and backend interfaces.
386 lines
17 KiB
Python
386 lines
17 KiB
Python
from odoo import http, _
|
|
from odoo.http import request
|
|
from odoo.addons.portal.controllers.portal import CustomerPortal
|
|
from odoo.exceptions import AccessError, MissingError, UserError, ValidationError
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class TeamManagementPortal(CustomerPortal):
|
|
|
|
def _prepare_home_portal_values(self, counters):
|
|
values = super()._prepare_home_portal_values(counters)
|
|
return values
|
|
|
|
def _check_team_access(self, team_id, check_staff=False):
|
|
"""Verify the current user has access to this team.
|
|
|
|
:param int team_id: ID of the team to check access for
|
|
:param bool check_staff: If True, only allow team staff members
|
|
:return: The team record if access is granted
|
|
:raises: MissingError if team not found
|
|
:raises: AccessError if user doesn't have permission
|
|
"""
|
|
team = request.env['sports.team'].browse(int(team_id))
|
|
if not team:
|
|
raise MissingError(_("Team not found"))
|
|
|
|
user = request.env.user
|
|
|
|
# Check if user is a staff member of this team
|
|
is_team_staff = team.staff_ids.filtered(
|
|
lambda s: user.partner_id in s.user_ids.partner_id
|
|
)
|
|
|
|
# Check if user is a treatment professional with access
|
|
is_treatment_professional = user.has_group(
|
|
'bemade_sports_clinic.group_portal_treatment_professional'
|
|
)
|
|
|
|
if check_staff and not is_team_staff:
|
|
# Only team staff can perform certain actions
|
|
raise AccessError(_("Only team staff members can perform this action."))
|
|
|
|
if not (is_team_staff or is_treatment_professional):
|
|
raise AccessError(_("You don't have permission to access this team."))
|
|
|
|
return team
|
|
|
|
def _check_team_staff_access(self, team):
|
|
"""Check if the current user is a staff member of the team"""
|
|
user = request.env.user
|
|
return team.staff_ids.filtered(
|
|
lambda s: user.partner_id in s.user_ids.partner_id
|
|
)
|
|
|
|
def _check_treatment_professional_access(self):
|
|
"""Check if the current user is a treatment professional"""
|
|
return request.env.user.has_group(
|
|
'bemade_sports_clinic.group_portal_treatment_professional'
|
|
) or request.env.user.has_group('base.group_system')
|
|
|
|
@http.route(['/my/team/<int:team_id>/player/<int:player_id>/request_removal'],
|
|
type='http', auth="user", website=True, methods=['POST'])
|
|
def portal_request_player_removal(self, team_id, player_id, **post):
|
|
"""
|
|
Request removal of a player from the team.
|
|
This is used by coaches to request removal, which creates a task for the head therapist.
|
|
"""
|
|
try:
|
|
team = self._check_team_access(team_id, check_staff=True)
|
|
patient = request.env['sports.patient'].browse(int(player_id))
|
|
|
|
if not patient.exists():
|
|
raise MissingError(_("Player not found"))
|
|
|
|
if team not in patient.team_ids:
|
|
raise ValidationError(_("Player is not a member of this team"))
|
|
|
|
# Check if there's already a pending removal
|
|
if patient.pending_removal:
|
|
raise ValidationError(_("A removal request is already pending for this player"))
|
|
|
|
# Get and validate reason
|
|
reason = (post.get('reason') or '').strip()
|
|
if not reason:
|
|
raise ValidationError(_("Please provide a reason for the removal request"))
|
|
|
|
# Request removal (this will handle the activity creation and logging)
|
|
patient.sudo().request_team_removal(team.id, reason=reason)
|
|
|
|
# Store success message in session for display after redirect
|
|
request.session['notification'] = {
|
|
'type': 'success',
|
|
'title': _('Removal Request Submitted'),
|
|
'message': _('Your request to remove %s from the team has been submitted for review.') % patient.name,
|
|
'sticky': False,
|
|
}
|
|
|
|
return request.redirect(f"/my/team/{team_id}")
|
|
|
|
except Exception as e:
|
|
_logger.error("Error requesting player removal: %s", str(e), exc_info=True)
|
|
error_message = _("Error requesting removal: %s") % str(e)
|
|
return request.redirect(f"/my/team/{team_id}?error={error_message}".replace(' ', '+'))
|
|
|
|
@http.route(['/my/team/<int:team_id>/player/<int:player_id>/remove'],
|
|
type='http', auth="user", website=True, methods=['POST'])
|
|
def portal_remove_player(self, team_id, player_id, **post):
|
|
"""
|
|
Directly remove a player from the team.
|
|
Only accessible by treatment professionals or team staff.
|
|
"""
|
|
try:
|
|
team = self._check_team_access(team_id)
|
|
|
|
# Only treatment professionals or team staff can directly remove
|
|
if not (self._check_treatment_professional_access() or self._check_team_staff_access(team)):
|
|
raise AccessError(_("You don't have permission to remove players from this team."))
|
|
|
|
patient = request.env['sports.patient'].browse(int(player_id))
|
|
if not patient.exists():
|
|
raise MissingError(_("Player not found"))
|
|
|
|
if team not in patient.team_ids:
|
|
raise ValidationError(_("Player is not a member of this team"))
|
|
|
|
# Check if this is a pending removal that's being approved
|
|
is_approving_pending = patient.pending_removal and self._check_treatment_professional_access()
|
|
|
|
# Remove the player from the team
|
|
result = patient.sudo().remove_from_team(team.id, clear_pending=True)
|
|
|
|
# Store success message in session for display after redirect
|
|
request.session['notification'] = {
|
|
'type': 'success',
|
|
'title': _('Player Removed'),
|
|
'message': _('%s has been successfully removed from the team.') % patient.name,
|
|
'sticky': False,
|
|
}
|
|
|
|
return request.redirect(f"/my/team/{team_id}")
|
|
|
|
except Exception as e:
|
|
_logger.error("Error removing player: %s", str(e), exc_info=True)
|
|
error_message = _("Error removing player: %s") % str(e)
|
|
return request.redirect(f"/my/team/{team_id}?error={error_message}".replace(' ', '+'))
|
|
|
|
@http.route(['/my/team/<int:team_id>/add_player'],
|
|
type='http', auth="user", website=True)
|
|
def portal_add_player_form(self, team_id, **kw):
|
|
"""Display the form to add a new player to a team."""
|
|
try:
|
|
team = self._check_team_access(team_id)
|
|
values = self._prepare_portal_layout_values()
|
|
|
|
# Check for success/error messages
|
|
success = request.httprequest.args.get('success')
|
|
if success == 'player_reactivated':
|
|
values['success'] = _("An archived player was found and reactivated, and has been added to this team.")
|
|
elif success == 'player_added_to_team':
|
|
values['success'] = _("An existing player was found and has been added to this team.")
|
|
elif success == 'player_created':
|
|
values['success'] = _("A new player has been created and added to the team.")
|
|
|
|
values.update({
|
|
'team': team,
|
|
'page_name': 'add_player',
|
|
'error': request.httprequest.args.get('error'),
|
|
})
|
|
|
|
# Preserve form data if there was an error
|
|
if kw.get('error'):
|
|
values.update({
|
|
'first_name': kw.get('first_name', ''),
|
|
'last_name': kw.get('last_name', ''),
|
|
'email': kw.get('email', ''),
|
|
'phone': kw.get('phone', ''),
|
|
'date_of_birth': kw.get('date_of_birth', ''),
|
|
})
|
|
|
|
return request.render("bemade_sports_clinic.portal_add_player", values)
|
|
|
|
except (AccessError, MissingError) as e:
|
|
return request.redirect('/my')
|
|
except Exception as e:
|
|
_logger.exception("Error in portal_add_player_form")
|
|
values = request.params.copy()
|
|
values['error'] = _("An error occurred while loading the form. Please try again.")
|
|
return request.render("bemade_sports_clinic.portal_add_player", values)
|
|
|
|
@http.route(['/my/team', '/my/team/<int:team_id>'], type='http', auth="user", website=True)
|
|
def portal_team_players(self, team_id=None, **kw):
|
|
"""Display the list of players for a team."""
|
|
try:
|
|
if not team_id:
|
|
# If no team_id is provided, try to get it from the query string
|
|
team_id = request.httprequest.args.get('team_id')
|
|
if not team_id:
|
|
# If still no team_id, redirect to the teams list
|
|
return request.redirect('/my/teams')
|
|
|
|
team = self._check_team_access(team_id)
|
|
|
|
# Get all players for the team
|
|
players = request.env['sports.patient'].search([
|
|
('team_ids', 'in', [team.id]),
|
|
('active', '=', True)
|
|
], order='last_name, first_name')
|
|
|
|
# Check user permissions for UI elements
|
|
is_treatment_prof = request.env.user.has_group(
|
|
'bemade_sports_clinic.group_portal_treatment_professional')
|
|
is_admin = request.env.user.has_group('base.group_system')
|
|
is_team_staff = team.staff_ids.filtered(
|
|
lambda s: request.env.user.partner_id in s.user_ids.partner_id
|
|
)
|
|
|
|
values = {
|
|
'page_name': 'team_players',
|
|
'team': team,
|
|
'players': players,
|
|
'default_url': f'/my/team/{team.id}',
|
|
'user_has_group': request.env.user.has_group, # Pass the has_group method to template
|
|
'user': request.env.user,
|
|
'is_treatment_prof': is_treatment_prof or is_admin,
|
|
'is_team_staff': bool(is_team_staff),
|
|
}
|
|
|
|
# Add success/error messages if present in the URL
|
|
success = request.httprequest.args.get('success')
|
|
error = request.httprequest.args.get('error')
|
|
|
|
if success == 'player_removed':
|
|
values['success'] = _("Player has been successfully removed from the team.")
|
|
elif success == 'removal_requested':
|
|
values['success'] = _("A request to remove this player has been submitted to the head therapist.")
|
|
elif success == 'player_reactivated':
|
|
values['success'] = _("An archived player was found and reactivated, and has been added to this team.")
|
|
elif success == 'player_added_to_team':
|
|
values['success'] = _("Player has been added to the team.")
|
|
|
|
if error:
|
|
values['error'] = error
|
|
|
|
return request.render('bemade_sports_clinic.portal_my_team_players', values)
|
|
|
|
except (AccessError, MissingError) as e:
|
|
return request.redirect('/my/teams?error=%s' % str(e))
|
|
|
|
def _find_existing_patient(self, first_name, last_name, email=None, phone=None):
|
|
"""Search for an existing patient by name and contact information."""
|
|
domain = [
|
|
('first_name', '=ilike', first_name.strip()),
|
|
('last_name', '=ilike', last_name.strip()),
|
|
'|',
|
|
('active', '=', True),
|
|
('active', '=', False), # Include inactive to handle archived players
|
|
]
|
|
|
|
# Additional search criteria if email or phone is provided
|
|
if email and email.strip():
|
|
domain = ['|'] + domain + [
|
|
'&',
|
|
('partner_id.email', '=ilike', email.strip()),
|
|
('partner_id.email', '!=', False)
|
|
]
|
|
if phone and phone.strip():
|
|
domain = ['|'] + domain + [
|
|
'&',
|
|
('partner_id.phone', '=', phone.strip()),
|
|
('partner_id.phone', '!=', False)
|
|
]
|
|
|
|
return request.env['sports.patient'].sudo().search(domain, limit=1)
|
|
|
|
@http.route(['/my/team/<int:team_id>/add_player/submit'],
|
|
type='http', auth="user", website=True, methods=['POST'], csrf=True)
|
|
def portal_add_player_submit(self, team_id, **post):
|
|
"""Handle the form submission to add a new player."""
|
|
try:
|
|
team = self._check_team_access(team_id)
|
|
|
|
# Basic validation
|
|
first_name = (post.get('first_name') or '').strip()
|
|
last_name = (post.get('last_name') or '').strip()
|
|
email = (post.get('email') or '').strip()
|
|
phone = (post.get('phone') or '').strip()
|
|
|
|
if not first_name or not last_name:
|
|
raise UserError(_("First name and last name are required"))
|
|
|
|
# Check for existing player
|
|
existing_patient = self._find_existing_patient(
|
|
first_name, last_name, email, phone
|
|
)
|
|
|
|
if existing_patient:
|
|
# Determine the action taken for logging and messaging
|
|
action_taken = []
|
|
|
|
# Reactivate if archived
|
|
if not existing_patient.active:
|
|
existing_patient.write({'active': True})
|
|
action_taken.append("reactivated")
|
|
_logger.info(
|
|
"Reactivated archived player %s for team %s by user %s",
|
|
existing_patient.name, team.name, request.env.user.name
|
|
)
|
|
|
|
# Add to team if not already a member
|
|
if team not in existing_patient.team_ids:
|
|
existing_patient.write({
|
|
'team_ids': [(4, team.id)],
|
|
})
|
|
action_taken.append("added to team")
|
|
_logger.info(
|
|
"Added existing player %s to team %s by user %s",
|
|
existing_patient.name, team.name, request.env.user.name
|
|
)
|
|
|
|
# Determine the appropriate success message
|
|
if "reactivated" in action_taken:
|
|
success_param = "player_reactivated"
|
|
else:
|
|
success_param = "player_added_to_team"
|
|
|
|
# Redirect to team page with appropriate message
|
|
return request.redirect(
|
|
f"/my/team?team_id={team.id}&success={success_param}"
|
|
)
|
|
|
|
# No existing player found, create a new one
|
|
partner_vals = {
|
|
'name': f"{first_name} {last_name}",
|
|
'email': email or False,
|
|
'phone': phone or False,
|
|
'type': 'contact', # 'contact' is the default type for a partner
|
|
}
|
|
|
|
# Create the partner and patient
|
|
partner = request.env['res.partner'].sudo().create(partner_vals)
|
|
|
|
patient_vals = {
|
|
'partner_id': partner.id,
|
|
'first_name': first_name,
|
|
'last_name': last_name,
|
|
'team_ids': [(4, team.id)],
|
|
}
|
|
|
|
if post.get('date_of_birth'):
|
|
patient_vals['date_of_birth'] = post.get('date_of_birth')
|
|
|
|
patient = request.env['sports.patient'].sudo().create(patient_vals)
|
|
|
|
# Log the action
|
|
_logger.info(
|
|
"Created new player %s and added to team %s by user %s",
|
|
patient.name, team.name, request.env.user.name
|
|
)
|
|
|
|
# Redirect to team page with success message
|
|
return request.redirect(
|
|
f"/my/team?team_id={team.id}&success=player_created"
|
|
)
|
|
|
|
except UserError as e:
|
|
values = {
|
|
'error': str(e),
|
|
'team': team,
|
|
'page_name': 'add_player',
|
|
}
|
|
values.update(post)
|
|
return request.render("bemade_sports_clinic.portal_add_player", values)
|
|
|
|
except (AccessError, MissingError) as e:
|
|
return request.redirect('/my')
|
|
|
|
except Exception as e:
|
|
_logger.exception("Error in portal_add_player_submit")
|
|
values = {
|
|
'error': _("An error occurred while adding the player. Please try again later."),
|
|
'team': team,
|
|
'page_name': 'add_player',
|
|
}
|
|
values.update(post)
|
|
return request.render("bemade_sports_clinic.portal_add_player", values)
|