Modified auto-assignment logic for therapists on new injuries. Added tests for defaults and auto-assignment.

This commit is contained in:
Denis Durepos 2025-08-15 20:29:33 -04:00
parent 580e61d840
commit a48450849c
5 changed files with 259 additions and 62 deletions

View file

@ -155,76 +155,62 @@ class PatientInjuryPortal(CustomerPortal, AccessControlMixin):
# Create the injury record - portal users now have create permission
injury = request.env['sports.patient.injury'].create(vals)
# Determine if user is a coach or treatment professional
# Determine role for assignment behavior
# Use request.env.user.has_group() directly to avoid security violations
is_portal_coach = request.env.user.has_group('bemade_sports_clinic.group_portal_team_coach')
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
user = request.env.user
# Assign treatment professionals based on user role
# Handle treatment professional assignments
treatment_prof_ids = []
# If user is a treatment professional, add them by default
# Assignment rules:
# - If any therapists were explicitly selected in the form, assign exactly those and do not auto-assign others.
# - If none were selected, auto-assign team therapists (if determinable) and do not auto-assign the creator by default.
# Read explicit selections (checkbox-based multi-select)
selected_tp_ids = []
if is_treatment_prof:
treatment_prof_ids.append(user.id)
# Also add any additional treatment professionals selected in the form (checkbox-based)
selected_tp_ids = request.httprequest.form.getlist('treatment_professional_ids[]')
if selected_tp_ids:
# Convert to integers and add to list (avoiding duplicates)
for tp_id in selected_tp_ids:
tp_id_int = int(tp_id)
if tp_id_int not in treatment_prof_ids:
treatment_prof_ids.append(tp_id_int)
# Assign treatment professionals if any were identified
if treatment_prof_ids:
injury.write({
'treatment_professional_ids': [(6, 0, treatment_prof_ids)]
})
selected_tp_ids = request.httprequest.form.getlist('treatment_professional_ids[]') or []
# Normalize to ints and remove empties
selected_tp_ids = [int(tp_id) for tp_id in selected_tp_ids if tp_id]
if selected_tp_ids:
# Respect explicit selection only
injury.write({'treatment_professional_ids': [(6, 0, selected_tp_ids)]})
else:
# User is not a treatment professional
pass
# Get current treatment professionals
treatment_profs = injury.treatment_professional_ids
# No explicit selection: perform team-based auto-assignment (if a single team context exists)
if team_id:
selected_team_id = int(team_id)
# Find therapists (head and regular) specifically for this team
team_staff = request.env['sports.team.staff'].sudo().search([
('team_id', '=', selected_team_id),
('role', 'in', ['head_therapist', 'therapist'])
])
# Always try to assign team therapists regardless of who created the injury
# Only when a single team context is determinable
if team_id:
selected_team_id = int(team_id)
# Find therapists (head and regular) specifically for this team
team_staff = request.env['sports.team.staff'].sudo().search([
('team_id', '=', selected_team_id),
('role', 'in', ['head_therapist', 'therapist'])
])
# Collect user IDs from team staff (prefer direct user_ids relation, fallback to partner mapping)
team_tp_user_ids = set()
for staff in team_staff:
if staff.user_ids:
for u in staff.user_ids:
team_tp_user_ids.add(u.id)
else:
users = request.env['res.users'].sudo().search([('partner_id', '=', staff.partner_id.id)])
for u in users:
team_tp_user_ids.add(u.id)
# Collect user IDs from team staff (prefer direct user_ids relation, fallback to partner mapping)
team_tp_user_ids = set()
for staff in team_staff:
if staff.user_ids:
for u in staff.user_ids:
team_tp_user_ids.add(u.id)
else:
users = request.env['res.users'].sudo().search([('partner_id', '=', staff.partner_id.id)])
for u in users:
team_tp_user_ids.add(u.id)
if user.id in team_tp_user_ids and (user.id not in (selected_tp_ids or [])):
# Do not auto-assign the creating user unless explicitly selected
team_tp_user_ids.discard(user.id)
if not team_tp_user_ids:
_logger.warning("No valid therapists found to assign to the injury for team %s", selected_team_id)
# Merge any team therapists with any already set/selected ones
merged_ids = set(treatment_prof_ids) | team_tp_user_ids if 'treatment_prof_ids' in locals() else team_tp_user_ids
if merged_ids:
injury.write({'treatment_professional_ids': [(6, 0, list(merged_ids))]})
else:
_logger.info(
"Skipping team-based therapist auto-assignment: patient %s has %s teams",
patient.id,
len(patient.team_ids),
)
if not team_tp_user_ids:
_logger.warning("No valid therapists found to assign to the injury for team %s", selected_team_id)
if team_tp_user_ids:
injury.write({'treatment_professional_ids': [(6, 0, list(team_tp_user_ids))]})
else:
_logger.info(
"Skipping team-based therapist auto-assignment: patient %s has %s teams",
patient.id,
len(patient.team_ids),
)
# Handle treatment note creation if provided by treatment professional
if is_treatment_prof and post.get('treatment_note'):

View file

@ -7,3 +7,5 @@ from . import test_player_removal
from . import test_mail_activity_portal_access
from . import test_mail_activity_portal_integration
from . import test_project_task_portal_security
from . import test_portal_activity_default_todo
from . import test_portal_injury_autoassign_integration

View file

@ -0,0 +1,72 @@
from odoo.tests import HttpCase, tagged
from odoo import Command
from odoo import fields
import re
@tagged("-at_install", "post_install")
class TestPortalActivityDefaultTodo(HttpCase):
"""Verify the activity creation form defaults the activity type to 'To Do'."""
@classmethod
def setUpClass(cls):
super().setUpClass()
# Org/team/patient setup
cls.organization = cls.env['res.partner'].create({
'name': 'Default Todo Org',
'is_company': True,
})
cls.team = cls.env['sports.team'].create({
'name': 'Default Todo Team',
'parent_id': cls.organization.id,
})
cls.patient = cls.env['sports.patient'].create({
'first_name': 'Default',
'last_name': 'TodoPatient',
'date_of_birth': '2005-01-01',
'team_ids': [(4, cls.team.id)],
})
# Create a portal treatment professional user and add to team staff
cls.tp_partner = cls.env['res.partner'].create({
'name': 'Portal TP',
'email': 'portal.tp.todo@example.com',
})
cls.tp_user = cls.env['res.users'].with_context(no_reset_password=True).create({
'partner_id': cls.tp_partner.id,
'login': 'portal.tp.todo@example.com',
'password': 'tp',
'name': cls.tp_partner.name,
'groups_id': [
Command.link(cls.env.ref('base.group_portal').id),
Command.link(cls.env.ref('bemade_sports_clinic.group_portal_treatment_professional').id),
]
})
cls.env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.tp_partner.id,
'role': 'therapist',
})
# Resolve the expected default To Do activity type id using the same priority as controller
todo_type = cls.env.ref('mail.mail_activity_data_todo', raise_if_not_found=False)
if not todo_type:
todo_type = cls.env['mail.activity.type'].search([('category', '=', 'todo')], limit=1)
if not todo_type:
todo_type = cls.env['mail.activity.type'].search([('name', 'ilike', 'todo')], limit=1)
cls.todo_type_id = todo_type and todo_type.id or False
def test_activity_create_form_defaults_todo(self):
self.assertTrue(self.todo_type_id, "A 'To Do' activity type must exist for this test")
# Login
self.authenticate('portal.tp.todo@example.com', 'tp')
# Open create activity form for patient
resp = self.url_open(f"/my/activity/create?model=sports.patient&res_id={self.patient.id}", timeout=30)
self.assertEqual(resp.status_code, 200)
# Assert the To Do option is pre-selected in the HTML
# QWeb renders selected attribute when t-att-selected resolves truthy, typically selected="selected"
pattern = rf'<option[^>]+value="{self.todo_type_id}"[^>]+selected'
self.assertRegex(resp.text, pattern, msg="Expected the 'To Do' activity type to be pre-selected")

View file

@ -0,0 +1,137 @@
from odoo.tests import HttpCase, tagged
from odoo import Command
@tagged("-at_install", "post_install")
class TestPortalInjuryAutoAssign(HttpCase):
"""Validate therapist auto-assignment during portal injury creation.
Scenarios:
- Treatment Professional creates injury: assigned = current TP + selected TPs + team therapists (unique set)
- Coach creates injury: assigned = team therapists only (no coach user)
"""
@classmethod
def setUpClass(cls):
super().setUpClass()
env = cls.env
# Base org and team
cls.org = env['res.partner'].create({
'name': 'AutoAssign Org',
'is_company': True,
})
cls.team = env['sports.team'].create({
'name': 'AutoAssign Team',
'parent_id': cls.org.id,
})
# Patient
cls.patient = env['sports.patient'].create({
'first_name': 'Injury',
'last_name': 'Subject',
'date_of_birth': '2006-06-06',
'team_ids': [(4, cls.team.id)],
})
# Users: TP1 (actor), TP2 (select extra), Coach (actor)
base_portal = env.ref('base.group_portal')
tp_group_portal = env.ref('bemade_sports_clinic.group_portal_treatment_professional')
coach_group = env.ref('bemade_sports_clinic.group_portal_team_coach')
# Team therapists via team staff: map partner to user(s)
cls.tp1_partner = env['res.partner'].create({'name': 'TP1', 'email': 'tp1@example.com'})
cls.tp1_user = env['res.users'].with_context(no_reset_password=True).create({
'partner_id': cls.tp1_partner.id,
'login': 'tp1@example.com',
'password': 'tp1',
'name': 'TP1',
'groups_id': [Command.link(base_portal.id), Command.link(tp_group_portal.id)],
})
env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.tp1_partner.id,
'role': 'therapist',
})
# Second therapist, also part of team via staff
cls.tp2_partner = env['res.partner'].create({'name': 'TP2', 'email': 'tp2@example.com'})
cls.tp2_user = env['res.users'].with_context(no_reset_password=True).create({
'partner_id': cls.tp2_partner.id,
'login': 'tp2@example.com',
'password': 'tp2',
'name': 'TP2',
'groups_id': [Command.link(base_portal.id), Command.link(tp_group_portal.id)],
})
env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.tp2_partner.id,
'role': 'head_therapist',
})
# Additional TP3 not in team; used for explicit selection by TP1
cls.tp3_partner = env['res.partner'].create({'name': 'TP3', 'email': 'tp3@example.com'})
cls.tp3_user = env['res.users'].with_context(no_reset_password=True).create({
'partner_id': cls.tp3_partner.id,
'login': 'tp3@example.com',
'password': 'tp3',
'name': 'TP3',
'groups_id': [Command.link(base_portal.id), Command.link(tp_group_portal.id)],
})
# Coach user
cls.coach_partner = env['res.partner'].create({'name': 'Coach', 'email': 'coach@example.com'})
cls.coach_user = env['res.users'].with_context(no_reset_password=True).create({
'partner_id': cls.coach_partner.id,
'login': 'coach@example.com',
'password': 'coach',
'name': 'Coach',
'groups_id': [Command.link(base_portal.id), Command.link(coach_group.id)],
})
# Add coach as staff for access (role coach)
env['sports.team.staff'].create({
'team_id': cls.team.id,
'partner_id': cls.coach_partner.id,
'role': 'coach',
})
def _latest_injury(self):
return self.env['sports.patient.injury'].search([('patient_id', '=', self.patient.id)], order='id desc', limit=1)
def test_autoassign_when_tp_creates_injury(self):
# TP1 logs in and creates injury selecting TP3 additionally, team set
self.authenticate('tp1@example.com', 'tp1')
data = {
'patient_id': str(self.patient.id),
'team_id': str(self.team.id),
'diagnosis': 'Hamstring strain',
# Multi-select checkboxes come as repeated keys treatment_professional_ids[]
'treatment_professional_ids[]': [str(self.tp3_user.id)],
}
resp = self.url_open('/my/patient/injury/create', data=data, timeout=30)
self.assertEqual(resp.status_code, 200)
injury = self._latest_injury()
self.assertTrue(injury, 'Injury should be created')
assigned = set(injury.treatment_professional_ids.ids)
# Expect TP1 (actor), both team therapists TP1+TP2, and selected TP3
expected = {self.tp1_user.id, self.tp2_user.id, self.tp3_user.id}
# TP1 is also a team therapist via staff, ensure no duplicates and inclusion
self.assertTrue(expected.issubset(assigned), f"Assigned professionals {assigned} should include {expected}")
def test_autoassign_when_coach_creates_injury(self):
# Coach logs in and creates injury; should auto-assign only team therapists (TP1, TP2), not coach
self.authenticate('coach@example.com', 'coach')
data = {
'patient_id': str(self.patient.id),
'team_id': str(self.team.id),
'diagnosis': 'Ankle sprain',
}
resp = self.url_open('/my/patient/injury/create', data=data, timeout=30)
self.assertEqual(resp.status_code, 200)
injury = self._latest_injury()
self.assertTrue(injury, 'Injury should be created by coach')
assigned = set(injury.treatment_professional_ids.ids)
expected_team = {self.tp1_user.id, self.tp2_user.id}
self.assertEqual(assigned, expected_team, f"Coach flow should assign only team therapists {expected_team}, got {assigned}")

View file

@ -153,7 +153,7 @@
</div>
</t>
</div>
<small class="text-muted">Select multiple professionals (you will be added automatically)</small>
<small class="text-muted">Select one or more professionals to assign. If left blank, team therapists will be auto-assigned. You will not be auto-assigned unless selected.</small>
</div>
</div>
<div class="col-lg-6">