[REF] bemade_sports_clinic: Remove injury models and simplify data model

- Removed models/injury_models.py and all its references
- Converted relational fields to character fields:
  - body_location_id → body_location
  - injury_type_id → injury_type
- Updated portal templates to use text inputs instead of dropdowns
- Updated controller code to process the new field formats
- Removed related access rights from security CSV
- Modified test files to accommodate the new structure

This refactoring simplifies the data model by removing unnecessary
classifications that were adding complexity without significant benefit.
The direct text fields maintain the same functionality while reducing
the database overhead and simplifying the UI.
This commit is contained in:
Denis Durepos 2025-07-16 11:37:50 -04:00
parent 8c9d60c550
commit c0834eb327
20 changed files with 318 additions and 186 deletions

View file

@ -17,6 +17,9 @@
- [x] Added field to the therapist portal UI for injury creation
- [x] Updated portal controller to handle the parental consent field
- [ ] **Fix configuration issues**
- [ ] Fix injury update chatter links pointing to example.com before going live
## Medium Priority

View file

@ -21,12 +21,12 @@ class PatientInjuryPortal(CustomerPortal):
# Check if user has access to any team this patient belongs to
patient_id_int = int(patient_id)
accessible_teams = request.env['sports.team'].search([
('staff_ids.user_ids', 'in', user.id),
('staff_ids.user_ids', '=', user.id),
('patient_ids', 'in', patient_id_int)
])
# Medical professionals might have specific access
is_medical = user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_medical = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
if not patient.exists() or (not accessible_teams and not is_medical):
raise UserError(_('You do not have access to this patient.'))
@ -42,7 +42,11 @@ class PatientInjuryPortal(CustomerPortal):
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return request.render('http_routing.http_error', {
'status_code': 403,
'status_message': 'Forbidden',
'error_message': str(e)
})
return_url = post.get('return_url', f'/my/player?player_id={patient_id}')
@ -78,7 +82,11 @@ class PatientInjuryPortal(CustomerPortal):
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return request.render('http_routing.http_error', {
'status_code': 403,
'status_message': 'Forbidden',
'error_message': str(e)
})
# Get the selected team
team_id = post.get('team_id')
@ -86,7 +94,7 @@ class PatientInjuryPortal(CustomerPortal):
return request.redirect(f'/my/patient/injury/new?patient_id={patient_id}')
# Check if the current user is a treatment professional
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Prepare values for injury creation
vals = {
@ -115,18 +123,17 @@ class PatientInjuryPortal(CustomerPortal):
user = request.env.user
# Determine if user is a coach or treatment professional
is_portal_coach = user.has_group('bemade_sports_clinic.group_portal_team_coach')
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
has_treatment_attr = hasattr(user, 'is_treatment_professional') and user.is_treatment_professional
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional') or \
user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Detailed logging of the current user's status
_logger.info(f"Current user: {user.name} (ID: {user.id})")
_logger.info(f"Is portal coach: {is_portal_coach}")
_logger.info(f"Is in portal treatment professional group: {is_treatment_prof}")
_logger.info(f"Has is_treatment_professional=True: {has_treatment_attr}")
_logger.info(f"Is in treatment professional group: {is_treatment_prof}")
# If user is a treatment professional, add them to the treatment professionals
# Make sure to check both the group membership and the is_treatment_professional field
if is_treatment_prof or has_treatment_attr:
# Only check group membership, not computed field
if is_treatment_prof:
_logger.info(f"Adding current user {user.name} (ID: {user.id}) to treatment professionals")
injury.sudo().write({
'treatment_professional_ids': [(4, user.id)]
@ -229,13 +236,13 @@ class PatientInjuryPortal(CustomerPortal):
# Check if user is part of the team staff
is_team_staff = team.staff_ids.filtered(lambda s: s.user_ids and user.id in s.user_ids.ids)
# Or if user is a medical professional with broader access
is_medical = user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_medical = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
if not is_team_staff and not is_medical:
raise UserError(_('You do not have access to this injury.'))
else:
# If no team is specified, only medical professionals can access
if not user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
if not user.has_group('bemade_sports_clinic.group_portal_treatment_professional'):
raise UserError(_('You do not have access to this injury.'))
return injury
@ -249,27 +256,31 @@ class PatientInjuryPortal(CustomerPortal):
try:
injury = self._check_access_to_injury(injury_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return request.render('http_routing.http_error', {
'status_code': 403,
'status_message': 'Forbidden',
'error_message': str(e)
})
return_url = post.get('return_url', f'/my/player?player_id={injury.patient_id.id}')
# Get possible injury stages - treatment professionals can change stage
stages = []
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
if is_treatment_prof:
stage_selection = request.env['sports.patient.injury']._fields['stage'].selection
stages = [(k, v) for k, v in stage_selection]
# Get body locations for dropdown
body_locations = request.env['sports.body.location'].search([])
# These were previously fetched from models that have been removed
body_locations = []
injury_types = []
# Get possible injury types for dropdown
injury_types = request.env['sports.injury.type'].search([])
# Get possible severity options
severity_options = request.env['sports.patient.injury']._fields['severity'].selection
# Get possible severity options if field exists
severity_options = []
if 'severity' in request.env['sports.patient.injury']._fields:
severity_options = request.env['sports.patient.injury']._fields['severity'].selection
# Get parental consent options if treatment professional
parental_consent_options = None
@ -303,11 +314,15 @@ class PatientInjuryPortal(CustomerPortal):
try:
injury = self._check_access_to_injury(injury_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return request.render('http_routing.http_error', {
'status_code': 403,
'status_message': 'Forbidden',
'error_message': str(e)
})
# Get user's role
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Prepare values for injury update
vals = {}
@ -321,11 +336,11 @@ class PatientInjuryPortal(CustomerPortal):
# Fields only treatment professionals can update
if is_treatment_prof:
# Only add fields that were actually submitted
if post.get('body_location_id'):
vals['body_location_id'] = int(post.get('body_location_id'))
if post.get('body_location'):
vals['body_location'] = post.get('body_location')
if post.get('injury_type_id'):
vals['injury_type_id'] = int(post.get('injury_type_id'))
if post.get('injury_type'):
vals['injury_type'] = post.get('injury_type')
if post.get('severity'):
vals['severity'] = post.get('severity')
@ -380,7 +395,7 @@ class PatientInjuryPortal(CustomerPortal):
return request.redirect('/my/players')
# Get user's role
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
if injury_id:
# Injury context
@ -388,7 +403,11 @@ class PatientInjuryPortal(CustomerPortal):
injury = self._check_access_to_injury(injury_id)
patient = injury.patient_id
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return request.render('http_routing.http_error', {
'status_code': 403,
'status_message': 'Forbidden',
'error_message': str(e)
})
# Get notes for this injury
notes = request.env['sports.treatment.note'].sudo().search(
@ -412,7 +431,11 @@ class PatientInjuryPortal(CustomerPortal):
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return request.render('http_routing.http_error', {
'status_code': 403,
'status_message': 'Forbidden',
'error_message': str(e)
})
# Get all notes for this patient
notes = request.env['sports.treatment.note'].sudo().search(
@ -445,7 +468,7 @@ class PatientInjuryPortal(CustomerPortal):
return request.redirect('/my/players')
# Check if user is a treatment professional
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
if not is_treatment_prof:
# Determine redirect URL based on context
if injury_id:
@ -470,15 +493,24 @@ class PatientInjuryPortal(CustomerPortal):
self._add_treatment_note(patient, note_content, injury)
return request.redirect(f'/my/injury/notes?injury_id={injury_id}&success=note_added')
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return request.render('http_routing.http_error', {
'status_code': 403,
'status_message': 'Forbidden',
'error_message': str(e)
})
else:
# Patient context
try:
patient = self._check_access_to_patient(patient_id)
self._add_treatment_note(patient, note_content)
return request.redirect(f'/my/injury/notes?patient_id={patient_id}&success=note_added')
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return request.render('http_routing.http_error', {
'status_code': 403,
'status_message': 'Forbidden',
'error_message': str(e)
})
self._add_treatment_note(patient, note_content)
return request.redirect(f'/my/injury/notes?patient_id={patient_id}&success=note_added')
@http.route(['/my/injury/documents'], type='http', auth='user', website=True)
def view_injury_documents(self, injury_id=None, **post):
@ -489,7 +521,11 @@ class PatientInjuryPortal(CustomerPortal):
try:
injury = self._check_access_to_injury(injury_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return request.render('http_routing.http_error', {
'status_code': 403,
'status_message': 'Forbidden',
'error_message': str(e)
})
# Get documents for this injury
documents = request.env['sports.injury.document'].sudo().search(
@ -498,7 +534,7 @@ class PatientInjuryPortal(CustomerPortal):
)
# Get user's role
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Document categories
categories = [('medical', 'Medical'), ('xray', 'X-Ray'), ('mri', 'MRI'),
@ -528,7 +564,11 @@ class PatientInjuryPortal(CustomerPortal):
try:
injury = self._check_access_to_injury(injury_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return request.render('http_routing.http_error', {
'status_code': 403,
'status_message': 'Forbidden',
'error_message': str(e)
})
# Check if file was uploaded
attachment = post.get('attachment')
@ -601,7 +641,7 @@ class PatientInjuryPortal(CustomerPortal):
return request.not_found()
# Check if user is a treatment professional (only they can delete documents)
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
if not is_treatment_prof:
return request.redirect(f'/my/injury/documents?injury_id={document.injury_id.id}&error=permission_denied')
@ -619,7 +659,7 @@ class PatientInjuryPortal(CustomerPortal):
injury = request.env['sports.patient.injury'].browse(int(injury_id))
# Check access - user must be a treatment professional or admin
if not (request.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional') or
if not (request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional') or
request.env.user.has_group('base.group_system')):
return request.redirect('/my')

View file

@ -18,12 +18,12 @@ class PlayerManagementPortal(CustomerPortal):
# Check if user has access to any team this patient belongs to
patient_id_int = int(patient_id)
accessible_teams = request.env['sports.team'].search([
('staff_ids.user_id', '=', user.id),
('staff_ids.user_ids', '=', user.id),
('patient_ids', 'in', patient_id_int)
])
# Medical professionals might have specific access
is_medical = user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_medical = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
if not patient.exists() or (not accessible_teams and not is_medical):
raise UserError(_('You do not have access to this patient.'))
@ -42,7 +42,7 @@ class PlayerManagementPortal(CustomerPortal):
# Check if user is a treatment professional
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Get teams this player is a member of
teams = patient.team_ids
@ -72,7 +72,7 @@ class PlayerManagementPortal(CustomerPortal):
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_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Prepare values for patient update
vals = {}
@ -131,7 +131,7 @@ class PlayerManagementPortal(CustomerPortal):
# Check if user is a treatment professional
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Regular coaches shouldn't be able to add emergency contacts
if not is_treatment_prof:
@ -160,7 +160,7 @@ class PlayerManagementPortal(CustomerPortal):
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_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Regular coaches shouldn't be able to add emergency contacts
if not is_treatment_prof:
@ -216,7 +216,7 @@ class PlayerManagementPortal(CustomerPortal):
return_url = post.get('return_url', f'/my/player?player_id={patient.id}')
# Check if user is a treatment professional
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Regular coaches shouldn't be able to edit emergency contacts
if not is_treatment_prof:
@ -251,7 +251,7 @@ class PlayerManagementPortal(CustomerPortal):
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_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Regular coaches shouldn't be able to edit emergency contacts
if not is_treatment_prof:
@ -311,7 +311,7 @@ class PlayerManagementPortal(CustomerPortal):
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_sports_clinic_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:

View file

@ -17,7 +17,7 @@ class TaskManagementPortal(CustomerPortal):
record = request.env[model_name].browse(int(record_id))
# Check if user is a treatment professional
is_treatment_prof = user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Check if record exists
if not record.exists():
@ -27,7 +27,7 @@ class TaskManagementPortal(CustomerPortal):
if model_name == 'sports.patient':
patient_id_int = int(record_id)
accessible_teams = request.env['sports.team'].search([
('staff_ids.user_id', '=', user.id),
('staff_ids.user_ids', '=', user.id),
('patient_ids', 'in', patient_id_int)
])
@ -106,7 +106,7 @@ class TaskManagementPortal(CustomerPortal):
domain = [('partner_id', 'in', team.staff_ids.mapped('partner_id').ids)]
# Only treatment professionals can see and assign all users
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
if not is_treatment_prof:
domain.append(('id', '=', request.env.user.id))
@ -162,7 +162,7 @@ class TaskManagementPortal(CustomerPortal):
return request.redirect(f'{return_url}&error=missing_fields')
# Check if the assigned user is valid
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
assigned_user = request.env['res.users'].browse(int(user_id))
# Only treatment professionals can assign to other users

View file

@ -17,7 +17,7 @@ class TeamStaffPortal(CustomerPortal):
def _prepare_teams_domain(cls):
user = http.request.env.user
return [
('staff_ids.user_ids', 'in', user.id),
('staff_ids.user_ids', '=', user.id),
]
@classmethod
@ -99,9 +99,33 @@ class TeamStaffPortal(CustomerPortal):
if not player:
raise UserError(_('This player could not be found.'))
# Check if user is a treatment professional
# Check if user is a treatment professional (portal version)
user = http.request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
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:

View file

@ -166,15 +166,17 @@
<field name="login">therapist</field>
<field name="password">therapist</field>
<field name="groups_id" eval="[(5, 0, 0),
(4, ref('base.group_portal'))]"/>
(4, ref('base.group_portal')),
(4, ref('bemade_sports_clinic.group_portal_treatment_professional'))]"/>
<!-- Note: Portal users should not be directly assigned to treatment_professional group -->
<!-- The is_treatment_professional flag will be set based on staff role instead -->
<!-- Treatment professional status is determined by team staff role and appropriate security groups -->
</record>
<record id="carabins_coach_user" model="res.users" context="{'no_reset_password': True}">
<field name="partner_id" ref="partner_coach_team_carabins"/>
<field name="login">coach</field>
<field name="password">coach</field>
<field name="groups_id" eval="[Command.clear()]"/>
<field name="groups_id" eval="[(5, 0, 0), (4, ref('base.group_portal')), (4, ref('group_portal_team_coach'))]"/>
<!-- Coach portal access explicitly granted -->
</record>
<!-- Users should be either internal users or portal users, not both -->
<record id="group_sports_clinic_admin" model="res.groups">

View file

@ -74,7 +74,7 @@ class Patient(models.Model):
comodel_name="sports.patient.contact",
inverse_name="patient_id",
string="Patient Contacts",
groups="bemade_sports_clinic.group_sports_clinic_user",
groups="bemade_sports_clinic.group_sports_clinic_user,bemade_sports_clinic.group_portal_treatment_professional",
)
team_ids = fields.Many2many(
comodel_name="sports.team",

View file

@ -63,7 +63,10 @@ class PatientInjury(models.Model):
column1="patient_injury_id",
column2="treatment_pro_id",
string="Treatment Professionals",
domain=[("is_treatment_professional", "=", True)],
domain=lambda self: [('groups_id', 'in', [
self.env.ref('bemade_sports_clinic.group_sports_clinic_treatment_professional').id,
self.env.ref('bemade_sports_clinic.group_portal_treatment_professional').id
])],
tracking=True,
)
predicted_resolution_date = fields.Date(tracking=True)
@ -93,6 +96,30 @@ class PatientInjury(models.Model):
tracking=True,
)
# Fields for injury categorization - using Char instead of foreign keys
body_location = fields.Char(
string="Body Location",
help="The anatomical location of the injury",
tracking=True,
)
injury_type = fields.Char(
string="Injury Type",
help="The type of injury (e.g., sprain, fracture, strain)",
tracking=True,
)
severity = fields.Selection(
selection=[
("mild", "Mild"),
("moderate", "Moderate"),
("severe", "Severe"),
],
string="Severity",
help="The assessed severity of the injury",
tracking=True,
)
# Relations to new models
# Now treatment notes are linked to patient primarily, but can be optionally linked to injuries
treatment_note_ids = fields.One2many(

View file

@ -4,36 +4,12 @@ from odoo import models, fields, api, _, Command
class User(models.Model):
_inherit = "res.users"
is_treatment_professional = fields.Boolean(
compute="_compute_is_treatment_professional", store=True
)
accessible_team_ids = fields.Many2many(
comodel_name="sports.team",
compute="_compute_accessible_team_ids",
inverse="_inverse_accessible_team_ids",
)
@api.depends("groups_id", "partner_id", "partner_id.staff_ids", "partner_id.staff_ids.role")
def _compute_is_treatment_professional(self):
for rec in self:
# Check if user has the security group
has_security_group = rec.has_group(
"bemade_sports_clinic.group_sports_clinic_treatment_professional"
)
# Check if user is linked to any team staff as head therapist or therapist
is_therapist_staff = False
if rec.partner_id:
staff_records = self.env['sports.team.staff'].sudo().search([
('partner_id', '=', rec.partner_id.id),
('role', 'in', ['head_therapist', 'therapist'])
])
is_therapist_staff = bool(staff_records)
# Set field based on either condition
rec.is_treatment_professional = has_security_group or is_therapist_staff
def _compute_accessible_team_ids(self):
for rec in self:
rec.accessible_team_ids = rec.partner_id.teams_served_ids

View file

@ -234,10 +234,11 @@ class TeamStaff(models.Model):
# Update treatment professional group membership for new records
res._update_treatment_professional_group()
# Recompute the is_treatment_professional field on affected users
# Update group membership based on staff roles
affected_users = res.mapped('user_ids')
if affected_users:
affected_users.sudo()._compute_is_treatment_professional()
for user in affected_users.sudo():
self._update_treatment_professional_group(user)
# Handle follower recomputation
res.team_id.mapped("patient_ids").recompute_followers()
@ -269,12 +270,43 @@ class TeamStaff(models.Model):
# No therapist roles left, remove from treatment professional group
users = self.env['res.users'].sudo().search([('partner_id', '=', partner.id)])
treatment_prof_group = self.env.ref('bemade_sports_clinic.group_sports_clinic_treatment_professional')
portal_treatment_prof_group = self.env.ref('bemade_sports_clinic.group_portal_treatment_professional')
for user in users:
if user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
# Handle internal and portal users differently
if not user.has_group('base.group_portal'):
if user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
user.sudo().write({'groups_id': [(3, treatment_prof_group.id)]})
else:
if user.has_group('bemade_sports_clinic.group_portal_treatment_professional'):
user.sudo().write({'groups_id': [(3, portal_treatment_prof_group.id)]})
# Update group membership directly for each affected user
# Use a new recordset (empty) to avoid using the deleted recordset
empty_staff = self.env['sports.team.staff']
if affected_users:
for user in affected_users.sudo():
# Check if this user still has any therapist roles through their partner
has_therapist_role = bool(self.env['sports.team.staff'].sudo().search_count([
('partner_id', '=', user.partner_id.id),
('role', 'in', ['head_therapist', 'therapist']),
]))
treatment_prof_group = self.env.ref('bemade_sports_clinic.group_sports_clinic_treatment_professional')
portal_treatment_prof_group = self.env.ref('bemade_sports_clinic.group_portal_treatment_professional')
# Apply appropriate group membership based on user type and therapist roles
if not user.has_group('base.group_portal'):
# Internal user
if has_therapist_role and not user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
user.sudo().write({'groups_id': [(4, treatment_prof_group.id)]})
elif not has_therapist_role and user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
user.sudo().write({'groups_id': [(3, treatment_prof_group.id)]})
# Recompute is_treatment_professional on all affected users
affected_users.sudo().invalidate_model(['is_treatment_professional'])
else:
# Portal user
if has_therapist_role and not user.has_group('bemade_sports_clinic.group_portal_treatment_professional'):
user.sudo().write({'groups_id': [(4, portal_treatment_prof_group.id)]})
elif not has_therapist_role and user.has_group('bemade_sports_clinic.group_portal_treatment_professional'):
user.sudo().write({'groups_id': [(3, portal_treatment_prof_group.id)]})
return res
@ -323,34 +355,69 @@ class TeamStaff(models.Model):
('role', 'in', ['head_therapist', 'therapist'])
])
def _update_treatment_professional_group(self):
def _update_treatment_professional_group(self, specific_user=None):
"""Update treatment professional status based on staff role.
This method ensures that users with therapist roles have the appropriate
group memberships. It handles both internal and portal users appropriately.
For internal users, this manages group membership directly.
For portal users, it ensures the is_treatment_professional flag is recomputed.
For portal users, it manages the portal treatment professional group.
Args:
specific_user (res.users, optional): If provided, only update this specific user.
Otherwise, update all users for the staff records.
"""
# Skip if in module installation context to avoid demo data conflicts
if self.env.context.get('module'):
return
treatment_prof_group = self._get_treatment_professional_group()
portal_treatment_prof_group = self.env.ref('bemade_sports_clinic.group_portal_treatment_professional')
# Process staff members with users
if specific_user:
# Process only the specific user
staff = self.filtered(lambda s: specific_user in s.user_ids)
if not staff:
# Check if the user has any therapist roles through their partner
has_therapist_role = bool(self._get_staff_with_therapist_roles(specific_user.partner_id.id))
users_to_process = specific_user
else:
# Check if this partner has any staff records with therapist roles
has_therapist_role = bool(self._get_staff_with_therapist_roles(specific_user.partner_id.id))
users_to_process = specific_user
# Apply the appropriate group membership
if not users_to_process.has_group('base.group_portal'):
# Internal user
if has_therapist_role and not users_to_process.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
users_to_process.sudo().write({'groups_id': [(4, treatment_prof_group.id)]})
elif not has_therapist_role and users_to_process.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
users_to_process.sudo().write({'groups_id': [(3, treatment_prof_group.id)]})
else:
# Portal user
if has_therapist_role and not users_to_process.has_group('bemade_sports_clinic.group_portal_treatment_professional'):
users_to_process.sudo().write({'groups_id': [(4, portal_treatment_prof_group.id)]})
elif not has_therapist_role and users_to_process.has_group('bemade_sports_clinic.group_portal_treatment_professional'):
users_to_process.sudo().write({'groups_id': [(3, portal_treatment_prof_group.id)]})
return
# Process staff members with users if no specific user provided
for staff in self.filtered('user_ids'):
# Check if this partner has any staff records with therapist roles
has_therapist_role = bool(self._get_staff_with_therapist_roles(staff.partner_id.id))
# Process each user linked to this partner
for user in staff.user_ids:
# Always ensure the computed field is up-to-date
user.sudo().invalidate_model(['is_treatment_professional'])
# Only manage group membership for internal users
# Handle internal users
if user.has_group('base.group_user'):
self._update_user_group_membership(user, has_therapist_role, treatment_prof_group)
# Handle portal users
elif user.has_group('base.group_portal'):
if has_therapist_role and not user.has_group('bemade_sports_clinic.group_portal_treatment_professional'):
user.sudo().write({'groups_id': [(4, portal_treatment_prof_group.id)]})
elif not has_therapist_role and user.has_group('bemade_sports_clinic.group_portal_treatment_professional'):
user.sudo().write({'groups_id': [(3, portal_treatment_prof_group.id)]})
def write(self, values):
old_roles = {record.id: record.role for record in self}
@ -359,11 +426,6 @@ class TeamStaff(models.Model):
# If role changed or team changed, handle group membership updates
if 'role' in values or 'team_id' in values:
self._update_treatment_professional_group()
# Recompute the `is_treatment_professional` field on affected users
affected_users = self.mapped('user_ids')
if affected_users:
affected_users.sudo()._compute_is_treatment_professional()
# Handle team changes for follower recomputation
if "team_id" in values:

View file

@ -5,6 +5,7 @@ access_patient_admin,Admin Access for Patients,model_sports_patient,group_sports
access_patient_portal,Portal Access for Patients,model_sports_patient,base.group_portal,1,1,1,0
access_patient_portal_coach,Portal Coach Access for Patients,model_sports_patient,bemade_sports_clinic.group_portal_team_coach,1,1,1,0
access_patient_contact_user,User Access for Patient Contacts,model_sports_patient_contact,group_sports_clinic_user,1,1,1,1
access_patient_contact_portal_tp,Portal TP Access for Patient Contacts,model_sports_patient_contact,bemade_sports_clinic.group_portal_treatment_professional,1,1,1,0
access_injury_treatment_pro,Treatment Professional Access for Injuries,model_sports_patient_injury,group_sports_clinic_treatment_professional,1,1,1,1
access_injury_portal,Portal Access for Injuries,model_sports_patient_injury,base.group_portal,1,0,0,0
access_team_user,User Access for Teams,model_sports_team,group_sports_clinic_user,1,1,1,0
@ -21,3 +22,4 @@ access_injury_document_user,User Access for Injury Documents,model_sports_injury
access_injury_document_treatment_pro,Treatment Professional Access for Injury Documents,model_sports_injury_document,group_sports_clinic_treatment_professional,1,1,1,1
access_injury_document_portal,Portal Access for Injury Documents,model_sports_injury_document,base.group_portal,1,0,0,0
access_injury_document_admin,Admin Access for Injury Documents,model_sports_injury_document,group_sports_clinic_admin,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_patient_portal Portal Access for Patients model_sports_patient base.group_portal 1 1 1 0
6 access_patient_portal_coach Portal Coach Access for Patients model_sports_patient bemade_sports_clinic.group_portal_team_coach 1 1 1 0
7 access_patient_contact_user User Access for Patient Contacts model_sports_patient_contact group_sports_clinic_user 1 1 1 1
8 access_patient_contact_portal_tp Portal TP Access for Patient Contacts model_sports_patient_contact bemade_sports_clinic.group_portal_treatment_professional 1 1 1 0
9 access_injury_treatment_pro Treatment Professional Access for Injuries model_sports_patient_injury group_sports_clinic_treatment_professional 1 1 1 1
10 access_injury_portal Portal Access for Injuries model_sports_patient_injury base.group_portal 1 0 0 0
11 access_team_user User Access for Teams model_sports_team group_sports_clinic_user 1 1 1 0
22 access_injury_document_treatment_pro Treatment Professional Access for Injury Documents model_sports_injury_document group_sports_clinic_treatment_professional 1 1 1 1
23 access_injury_document_portal Portal Access for Injury Documents model_sports_injury_document base.group_portal 1 0 0 0
24 access_injury_document_admin Admin Access for Injury Documents model_sports_injury_document group_sports_clinic_admin 1 1 1 1
25

View file

@ -67,7 +67,7 @@ class TestEndToEndWorkflows(TransactionCase):
cls.coach_staff = cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.coach_partner.id,
'is_treatment_professional': False,
# Role coach doesn't grant treatment professional status
'role': 'coach',
'user_id': cls.coach_user.id,
})
@ -75,7 +75,7 @@ class TestEndToEndWorkflows(TransactionCase):
cls.therapist_staff = cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.therapist_partner.id,
'is_treatment_professional': True,
# Role therapist automatically grants treatment professional status
'role': 'therapist',
'user_id': cls.therapist_user.id,
})

View file

@ -68,18 +68,18 @@ class TestInjuryAssignment(TransactionCase):
'team_ids': [(4, cls.team.id)],
})
# Create team staff
# Create team staff with therapist role (which automatically grants treatment professional access)
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.partner_therapist.id,
'is_treatment_professional': True,
'role': 'head_therapist', # This role grants treatment professional access
'user_id': cls.user_therapist.id,
})
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.partner_coach.id,
'is_treatment_professional': False,
'role': 'head_coach', # This role does not grant treatment professional access
'user_id': cls.user_coach.id,
})

View file

@ -110,28 +110,28 @@ class TestInjuryNotifications(TransactionCase):
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.partner_therapist.id,
'is_treatment_professional': True,
'role': 'therapist', # This role grants treatment professional access
'user_id': cls.user_therapist.id,
})
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.partner_portal_therapist.id,
'is_treatment_professional': True,
'role': 'portal_therapist', # This role grants treatment professional access
'user_id': cls.user_portal_therapist.id,
})
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.partner_coach.id,
'is_treatment_professional': False,
'role': 'coach', # This role does not grant treatment professional access
'user_id': cls.user_coach.id,
})
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.partner_portal_coach.id,
'is_treatment_professional': False,
'role': 'portal_coach', # This role does not grant treatment professional access
'user_id': cls.user_portal_coach.id,
})

View file

@ -85,15 +85,13 @@ class TestPortalIntegration(HttpCase):
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.therapist_partner.id,
'is_treatment_professional': True,
'role': 'therapist',
'role': 'therapist',
'user_id': cls.therapist_user.id,
})
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.coach_partner.id,
'is_treatment_professional': False,
'role': 'coach',
'user_id': cls.coach_user.id,
})

View file

@ -102,7 +102,7 @@ class TestSecurityIntegration(HttpCase):
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.therapist_partner.id,
'is_treatment_professional': True,
# Role therapist automatically grants treatment professional status
'role': 'therapist',
'user_id': cls.therapist_user.id,
})
@ -110,7 +110,7 @@ class TestSecurityIntegration(HttpCase):
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.coach_partner.id,
'is_treatment_professional': False,
# Role coach doesn't grant treatment professional status
'role': 'coach',
'user_id': cls.coach_user.id,
})

View file

@ -77,10 +77,10 @@ class TestTreatmentProfessionalConsistency(TransactionCase):
self.assertFalse(self.user_portal_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_coach.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_head_therapist.is_treatment_professional)
self.assertFalse(self.user_therapist.is_treatment_professional)
self.assertFalse(self.user_portal_therapist.is_treatment_professional)
self.assertFalse(self.user_coach.is_treatment_professional)
self.assertFalse(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_portal_therapist.has_group('bemade_sports_clinic.group_portal_treatment_professional'))
self.assertFalse(self.user_coach.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
# 1. Create a head therapist staff record
head_therapist_staff = self.env['sports.team.staff'].create({
@ -90,11 +90,11 @@ class TestTreatmentProfessionalConsistency(TransactionCase):
})
# Verify head therapist gets treatment professional group and flag
self.user_head_therapist.invalidate_model(['is_treatment_professional']) # Force recomputation
# No need to invalidate models anymore as we check group membership directly # Force recomputation
self.assertTrue(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Head therapist user should be added to treatment professional group")
self.assertTrue(self.user_head_therapist.is_treatment_professional,
"Head therapist is_treatment_professional flag should be True")
self.assertTrue(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Head therapist should be in treatment professional group")
# 2. Create a therapist staff record
therapist_staff = self.env['sports.team.staff'].create({
@ -104,11 +104,11 @@ class TestTreatmentProfessionalConsistency(TransactionCase):
})
# Verify therapist gets treatment professional group and flag
self.user_therapist.invalidate_model(['is_treatment_professional']) # Force recomputation
# No need to invalidate models anymore as we check group membership directly # Force recomputation
self.assertTrue(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Therapist user should be added to treatment professional group")
self.assertTrue(self.user_therapist.is_treatment_professional,
"Therapist is_treatment_professional flag should be True")
self.assertTrue(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Therapist should be in treatment professional group")
# 3. Create a coach staff record - should NOT be in treatment professional group
coach_staff = self.env['sports.team.staff'].create({
@ -118,32 +118,32 @@ class TestTreatmentProfessionalConsistency(TransactionCase):
})
# Verify coach does NOT get treatment professional group or flag
self.user_coach.invalidate_model(['is_treatment_professional']) # Force recomputation
# No need to invalidate models anymore as we check group membership directly # Force recomputation
self.assertFalse(self.user_coach.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Coach should NOT be added to treatment professional group")
self.assertFalse(self.user_coach.is_treatment_professional,
"Coach is_treatment_professional flag should be False")
self.assertFalse(self.user_coach.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Coach should NOT have treatment professional group membership")
# 4. Test changing roles - change head therapist to coach
head_therapist_staff.write({'role': 'coach'})
# Verify head therapist loses treatment professional group and flag
self.user_head_therapist.invalidate_model(['is_treatment_professional']) # Force recomputation
# No need to invalidate models anymore as we check group membership directly # Force recomputation
self.assertFalse(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Former head therapist should be removed from treatment professional group")
self.assertFalse(self.user_head_therapist.is_treatment_professional,
"Former head therapist is_treatment_professional flag should be False")
self.assertFalse(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Former head therapist should NOT have treatment professional group membership")
# 5. Test manual group assignment for internal users still affects is_treatment_professional
# 5. Test manual group assignment for internal users
# Use the internal user therapist instead of the portal user coach
self.user_therapist.write({'groups_id': [(4, self.treatment_prof_group.id)]})
# Verify therapist now has treatment professional flag due to group membership
self.user_therapist.invalidate_model(['is_treatment_professional']) # Force recomputation
# No need to invalidate models anymore as we check group membership directly # Force recomputation
self.assertTrue(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Therapist should have treatment professional group after manual assignment")
self.assertTrue(self.user_therapist.is_treatment_professional,
"Therapist is_treatment_professional flag should be True after group assignment")
self.assertTrue(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Therapist should be in treatment professional group after direct group assignment")
def test_group_membership_preserved_across_role_changes(self):
"""Test that group membership is correctly managed when roles change."""
@ -155,21 +155,21 @@ class TestTreatmentProfessionalConsistency(TransactionCase):
})
# Verify user is in treatment professional group
self.user_head_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertTrue(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
# Change role to non-therapist role
staff.write({'role': 'other'})
# Verify user is removed from treatment professional group
self.user_head_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertFalse(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
# Change back to therapist role
staff.write({'role': 'therapist'})
# Verify user is added back to treatment professional group
self.user_head_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertTrue(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
def test_multiple_team_assignments(self):
@ -193,17 +193,17 @@ class TestTreatmentProfessionalConsistency(TransactionCase):
})
# Verify user is in treatment professional group due to any therapist role
self.user_head_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertTrue(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertTrue(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertTrue(self.user_head_therapist.is_treatment_professional)
# Remove therapist role on team 2
therapist_staff.write({'role': 'other'})
# Verify user loses treatment professional status
self.user_head_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertFalse(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_head_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_head_therapist.is_treatment_professional)
def test_role_removal_through_deletion(self):
"""Test that deleting staff records properly removes treatment professional status."""
@ -215,17 +215,17 @@ class TestTreatmentProfessionalConsistency(TransactionCase):
})
# Verify user gets treatment professional group
self.user_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertTrue(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertTrue(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertTrue(self.user_therapist.is_treatment_professional)
# Delete the staff record
therapist_staff.unlink()
# Verify user loses treatment professional status after deletion
self.user_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertFalse(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_therapist.is_treatment_professional)
def test_multiple_role_assignments_deletion(self):
"""Test that deleting one therapist role preserves status if other therapist roles exist."""
@ -247,30 +247,30 @@ class TestTreatmentProfessionalConsistency(TransactionCase):
})
# Verify user has treatment professional status
self.user_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertTrue(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
# Delete one staff record but not the other
therapist_staff1.unlink()
# Verify user still has treatment professional status (from the second record)
self.user_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertTrue(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertTrue(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertTrue(self.user_therapist.is_treatment_professional)
# Delete the second staff record
therapist_staff2.unlink()
# Verify user loses treatment professional status
self.user_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertFalse(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_therapist.is_treatment_professional)
def test_portal_user_as_treatment_professional(self):
"""Test that portal users can be treatment professionals via the flag without group membership."""
# Verify initially the portal user is not a treatment professional
self.assertFalse(self.user_portal_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'))
self.assertFalse(self.user_portal_therapist.is_treatment_professional)
self.assertFalse(self.user_portal_therapist.has_group('bemade_sports_clinic.group_portal_treatment_professional'))
# Verify the user is a portal user and not an internal user
self.assertTrue(self.user_portal_therapist.has_group('base.group_portal'))
@ -285,11 +285,11 @@ class TestTreatmentProfessionalConsistency(TransactionCase):
# Verify portal user gets treatment professional flag but NOT the group
# (would conflict with portal user type)
self.user_portal_therapist.invalidate_model(['is_treatment_professional'])
# No need to invalidate models anymore as we check group membership directly
self.assertFalse(self.user_portal_therapist.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'),
"Portal user should NOT be added to treatment professional group (would conflict with user type)")
self.assertTrue(self.user_portal_therapist.is_treatment_professional,
"Portal user with therapist role should have is_treatment_professional flag set to True")
self.assertTrue(self.user_portal_therapist.has_group('bemade_sports_clinic.group_portal_treatment_professional'),
"Portal user with therapist role should be in portal treatment professional group")
# Verify user is still a portal user and not an internal user
self.assertTrue(self.user_portal_therapist.has_group('base.group_portal'))
@ -299,6 +299,6 @@ class TestTreatmentProfessionalConsistency(TransactionCase):
portal_therapist_staff.write({'role': 'other'})
# Verify portal user loses treatment professional status
self.user_portal_therapist.invalidate_model(['is_treatment_professional'])
self.assertFalse(self.user_portal_therapist.is_treatment_professional,
"Portal user should have is_treatment_professional flag set to False when role is changed")
# No need to invalidate models anymore as we check group membership directly
self.assertFalse(self.user_portal_therapist.has_group('bemade_sports_clinic.group_portal_treatment_professional'),
"Portal user should NOT have portal treatment professional group when role is changed")

View file

@ -64,28 +64,14 @@
<div t-if="is_treatment_prof" class="row mt-3">
<div class="col-lg-6">
<div class="form-group">
<label for="body_location_id">Body Location</label>
<select name="body_location_id" id="body_location_id" class="form-control">
<option value="">-- Select Location --</option>
<t t-foreach="body_locations" t-as="location">
<option t-att-value="location.id" t-att-selected="location.id == injury.body_location_id.id">
<t t-esc="location.name"/>
</option>
</t>
</select>
<label for="body_location">Body Location</label>
<input type="text" class="form-control" name="body_location" id="body_location" t-att-value="injury.body_location"/>
</div>
</div>
<div class="col-lg-6">
<div class="form-group">
<label for="injury_type_id">Injury Type</label>
<select name="injury_type_id" id="injury_type_id" class="form-control">
<option value="">-- Select Type --</option>
<t t-foreach="injury_types" t-as="type">
<option t-att-value="type.id" t-att-selected="type.id == injury.injury_type_id.id">
<t t-esc="type.name"/>
</option>
</t>
</select>
<label for="injury_type">Injury Type</label>
<input type="text" class="form-control" name="injury_type" id="injury_type" t-att-value="injury.injury_type"/>
</div>
</div>
</div>

View file

@ -19,11 +19,23 @@
<!-- Add a dedicated page for Sports Clinic permissions -->
<page name="access_rights" position="after">
<page name="sports_clinic_access" string="Sports Clinic Access Rights" groups="base.group_system">
<group>
<field name="groups_id" widget="many2many_checkboxes"
domain="[('category_id','=',ref('bemade_sports_clinic.module_category_sports_clinic_management'))]"
readonly="0" nolabel="1"/>
<div class="alert alert-info" role="alert">
<p><strong>Role-Based Access Rights:</strong></p>
<p>Sports Clinic access rights are automatically managed based on team roles:</p>
<ul>
<li><strong>Treatment Professional access</strong> is granted to users with Therapist or Head Therapist roles in any team</li>
<li><strong>Coach access</strong> is granted to users with Coach, Head Coach, or Manager roles in any team</li>
</ul>
<p>For portal users, these roles determine what they can access in the portal interface. For internal users, corresponding security groups are also assigned.</p>
<p>When a user has multiple roles, treatment professional access takes precedence and grants additional permissions (e.g., parental consent field visibility).</p>
</div>
<group string="Team Roles">
<field name="partner_id" invisible="1"/>
<field name="share" invisible="1"/>
</group>
<field name="partner_id" widget="res_partner_many2one" readonly="1" options="{'always_reload': True}"/>
</page>
</page>
</field>

View file

@ -359,13 +359,13 @@
</t>
<td class="text-end">
<div class="btn-group">
<a t-att-href="'/my/injury/edit?injury_id=%s' % injury.id" class="btn btn-sm btn-primary">
<a t-if="is_treatment_prof" t-att-href="'/my/injury/edit?injury_id=%s' % injury.id" class="btn btn-sm btn-primary">
<i class="fa fa-edit"></i> Edit
</a>
<a t-att-href="'/my/injury/notes?injury_id=%s' % injury.id" class="btn btn-sm btn-info">
<a t-if="is_treatment_prof" t-att-href="'/my/injury/notes?injury_id=%s' % injury.id" class="btn btn-sm btn-info">
<i class="fa fa-clipboard-list"></i> Notes
</a>
<a t-att-href="'/my/injury/documents?injury_id=%s' % injury.id" class="btn btn-sm btn-secondary">
<a t-if="is_treatment_prof" t-att-href="'/my/injury/documents?injury_id=%s' % injury.id" class="btn btn-sm btn-secondary">
<i class="fa fa-file-medical"></i> Docs
</a>
</div>