Modified auto-assignment logic for therapists on new injuries. Added tests for defaults and auto-assignment.
This commit is contained in:
parent
580e61d840
commit
a48450849c
5 changed files with 259 additions and 62 deletions
|
|
@ -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'):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
@ -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}")
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Reference in a new issue