bemade-addons/bemade_sports_clinic/controllers/team_staff_portal.py
Denis Durepos 14fa714fe8 feat: Add assignee display and reassignment functionality to activity views
- Implement context-sensitive assignee column display in activity views
- Show assignee column only when viewing specific records (teams, patients, injuries)
- Hide assignee column in general 'My Activities' view (users only see their own activities)
- Add reassignment modal with dropdown to select new treatment professional
- Implement /my/activity/reassign controller route with proper access control
- Add team-based security validation for reassignment operations
- Fix template variable passing issue where show_assignee wasn't reaching activity_list_table
- Add success feedback and proper return URL handling after reassignment
- Maintain context-sensitive activity filtering for optimal user experience
- All tests passing (76 tests, 0 failed, 0 errors)

This enhancement improves team coordination by providing clear visibility of activity
assignments and easy reassignment capabilities while maintaining proper security boundaries.
2025-07-29 15:44:09 -04:00

182 lines
8.8 KiB
Python

from odoo.addons.portal.controllers.portal import CustomerPortal, pager
from odoo import http, _
from odoo.exceptions import UserError
class TeamStaffPortal(CustomerPortal):
def _prepare_home_portal_values(self, counters):
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
def _prepare_teams_domain(cls):
user = http.request.env.user
return [
('staff_ids.user_ids', '=', user.id),
]
@classmethod
def _prepare_players_domain(cls, teams_domain):
team_ids = http.request.env['sports.team'].search(teams_domain).ids
return [
('team_ids', 'in', team_ids),
]
@classmethod
def _prepare_activities_domain(cls):
# Use controller-level team-based filtering for consistent security
# Record rules provide broad CRUD access, controller enforces team-based security
user = http.request.env.user
partner = user.partner_id
team_staff_rels = partner.team_staff_rel_ids
# Build team-based access domain for security filtering
return [
'|', '|',
'&', '&',
('res_model', '=', 'sports.patient'),
('res_id', '!=', False),
('res_id', 'in', team_staff_rels.mapped('team_id.patient_ids.id') or [0]),
'&', '&',
('res_model', '=', 'sports.patient.injury'),
('res_id', '!=', False),
('res_id', 'in', team_staff_rels.mapped('team_id.patient_ids.injury_ids.id') or [0]),
'&', '&',
('res_model', '=', 'sports.team'),
('res_id', '!=', False),
('res_id', 'in', team_staff_rels.mapped('team_id.id') or [0])
]
@http.route(route=['/my/teams', '/my/teams/page/<int: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 """
Teams = http.request.env['sports.team']
domain = self._prepare_teams_domain()
teams_count = Teams.search_count(domain)
pgr = pager(url='/my/teams', total=teams_count,
page=page, step=10, scope=5)
teams = http.request.env['sports.team'].search(self._prepare_teams_domain(),
offset=pgr['offset'],
limit=teams_count)
return http.request.render(template='bemade_sports_clinic.portal_my_teams',
qcontext={
'teams_count': teams_count,
'teams': teams,
'pager': pgr,
'page_name': 'my_teams',
})
@http.route(route=['/my/team', '/my/team/page/<int:page>'], type='http', auth='user', website=True)
def view_team(self, team_id, page=0, **kw):
""" Display the information for a team including its list of players """
team_id = int(team_id)
team = http.request.env['sports.team'].browse(team_id)
if not team:
raise UserError(_('This team could not be found.'))
players_count = team.player_count
pgr = pager(url=f'/my/team', total=players_count, page=page, step=10,
scope=5, url_args={'team_id': team_id})
players = http.request.env['sports.patient'].search([
('team_ids', 'in', team_id),
], offset=pgr['offset'], limit=players_count)
return http.request.render(
template='bemade_sports_clinic.portal_my_team_players',
qcontext={
'team': team,
'players_count': players_count,
'players': players,
'pager': pgr,
'page_name': 'my_teams',
}
)
@http.route(route=['/my/players', '/my/players/page/<int:page>'], type='http', auth='user', website=True)
def view_players(self, page=0, **kw):
""" Display the list of players that the portal user has access to """
teams_domain = self._prepare_teams_domain()
players_domain = self._prepare_players_domain(teams_domain)
players_count = http.request.env['sports.patient'].search_count(players_domain)
pgr = pager(url='/my/players', total=players_count, page=page, step=10, scope=5)
players = http.request.env['sports.patient'].search(players_domain,
offset=pgr['offset'],
limit=players_count)
return http.request.render(template='bemade_sports_clinic.portal_my_players',
qcontext={
'players_count': players_count,
'players': players,
'pager': pgr,
'page_name': 'my_players',
})
@http.route(route=['/my/player'], type='http',
auth='user', website=True)
def view_player(self, player_id, team_id=None,**kw):
""" Display the active injuries for a given player. """
player_id = int(player_id)
team_id = team_id and int(team_id)
player = http.request.env['sports.patient'].browse(player_id)
team = team_id and http.request.env['sports.team'].browse(team_id)
if not player:
raise UserError(_('This player could not be found.'))
# Check if user is a treatment professional (portal version)
user = http.request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Debug output
import logging
_logger = logging.getLogger(__name__)
_logger.info(f"DEBUG - User: {user.name} (login: {user.login}) is treatment prof: {is_treatment_prof}")
_logger.info(f"DEBUG - User groups: {', '.join([g.name for g in user.groups_id])}")
_logger.info(f"DEBUG - XML ID check: {user.has_group('bemade_sports_clinic.group_portal_treatment_professional')}")
# More detailed debugging
_logger.info(f"DEBUG - Player ID: {player_id}, Team ID: {team_id}")
if team_id:
_logger.info(f"DEBUG - Team name: {team.name if team else 'Team not found'}")
_logger.info(f"DEBUG - Team staff: {[(s.partner_id.name, s.role) for s in team.staff_ids]}")
_logger.info(f"DEBUG - Player teams: {[t.name for t in player.team_ids]}")
# Check team staff role
staff_records = http.request.env['sports.team.staff'].sudo().search([('partner_id', '=', user.partner_id.id)])
_logger.info(f"DEBUG - User's team staff roles: {[(s.team_id.name, s.role) for s in staff_records]}")
_logger.info(f"DEBUG - User's partner ID: {user.partner_id.id}")
# Check if user should have therapist role
has_therapist_role = any(s.role in ['head_therapist', 'therapist'] for s in staff_records)
_logger.info(f"DEBUG - User has therapist role: {has_therapist_role}")
# Show all injuries to treatment professionals, but only active ones to coaches
if is_treatment_prof:
injuries = player.injury_ids
else:
injuries = player.injury_ids.filtered(lambda r: r.stage == 'active')
# Create patient_info dictionary for protected fields (when user is a treatment professional)
# No need for sudo() now that we have proper field-level access rights
patient_info = {}
if is_treatment_prof:
# Include allergies and medical notes - direct access now that security is properly configured
patient_info['allergies'] = player.allergies
patient_info['team_info_notes'] = player.team_info_notes
return http.request.render(
template='bemade_sports_clinic.portal_my_player_injuries',
qcontext={
'player': player,
'injuries': injuries,
'team': team,
'page_name': 'my_player',
'is_treatment_prof': is_treatment_prof,
'patient_info': patient_info,
}
)