bemade-addons/bemade_sports_clinic/models/patient_injury.py
Denis Durepos c0834eb327 [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.
2025-07-16 11:37:50 -04:00

404 lines
16 KiB
Python

from odoo import models, fields, api, _
from datetime import datetime, date
import pytz
from odoo.exceptions import ValidationError, UserError, AccessError
import logging
_logger = logging.getLogger(__name__)
external_tracking_fields = {
"diagnosis",
"predicted_resolution_date",
"resolution_date",
"external_notes",
}
# Include only fields not already included in external_tracking_fields here
internal_tracking_fields = {
"internal_notes",
"parental_consent",
}
class PatientInjury(models.Model):
_name = "sports.patient.injury"
_description = "Patient Injury"
_inherit = ["mail.thread", "mail.activity.mixin"]
_rec_name = "diagnosis"
_order = "create_date desc, id desc"
@api.model
def _today(self):
"""Get the current date in the user's time zone."""
return datetime.now(pytz.timezone(self.env.user.tz or "GMT"))
# TODO: Find a way to improve notifications send about tracking injury details
# TODO: Add field consentement_parental = fields.Selection(oui, non, non-applicable)
patient_id = fields.Many2one(
comodel_name="sports.patient",
string="Patient",
readonly=True,
required=True,
ondelete="cascade",
)
patient_name = fields.Char(related="patient_id.name")
team_id = fields.Many2one(
comodel_name="sports.team",
string="Team",
help="The team for which this injury was reported, especially important when a player belongs to multiple teams.",
)
diagnosis = fields.Char(tracking=True)
injury_date = fields.Date(
string="Date of Injury",
default=_today,
)
injury_date_na = fields.Boolean(string="N/A", default=False)
internal_notes = fields.Html(tracking=True)
external_notes = fields.Html(tracking=True)
treatment_professional_ids = fields.Many2many(
comodel_name="res.users",
relation="patient_injury_treatment_pro_rel",
column1="patient_injury_id",
column2="treatment_pro_id",
string="Treatment Professionals",
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)
resolution_date = fields.Date(
tracking=True, help="The date when the injury was actually resolved."
)
stage = fields.Selection(
selection=[
("unverified", "Unverified"),
("active", "Active"),
("resolved", "Resolved")
],
string="Status",
default="unverified",
tracking=True,
copy=False,
help="""
- Unverified: Injury has been reported but not yet verified by a treatment professional
- Active: Injury has been verified and is being treated
- Resolved: Injury has been resolved
"""
)
parental_consent = fields.Selection(
string="Consent for Disclosure to Parent",
selection=[("yes", "Yes"), ("no", "No"), ("na", "Not Applicable")],
help="Whether the patient has given their consent to share injury details with their parents.",
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(
comodel_name='sports.treatment.note',
inverse_name='injury_id',
string='Treatment Notes',
help='Treatment notes specifically linked to this injury'
)
treatment_note_count = fields.Integer(
string='Treatment Note Count',
compute='_compute_treatment_note_count'
)
document_ids = fields.One2many(
comodel_name='sports.injury.document',
inverse_name='injury_id',
string='Documents'
)
document_count = fields.Integer(
string='Document Count',
compute='_compute_document_count'
)
@api.depends('treatment_note_ids')
def _compute_treatment_note_count(self):
for record in self:
record.treatment_note_count = len(record.treatment_note_ids)
@api.depends('document_ids')
def _compute_document_count(self):
for record in self:
record.document_count = len(record.document_ids)
@api.constrains("injury_date_na", "injury_date")
def constrain_date_blank_only_if_na(self):
for rec in self:
if not rec.injury_date_na and not rec.injury_date:
raise ValidationError(
_("If injury date is not set, the N/A box must be checked.")
)
@api.onchange("injury_date_na")
def _onchange_injury_date_na(self):
for rec in self:
if rec.injury_date_na:
rec.injury_date = None
@api.onchange("injury_date")
def _onchange_injury_date(self):
for rec in self:
if rec.injury_date:
rec.injury_date_na = False
def action_verify_injury(self):
"""Verify an injury, changing its status from unverified to active.
Only treatment professionals or internal users with appropriate rights can verify injuries."""
self.ensure_one()
if self.stage != "unverified":
raise UserError(_("Only unverified injuries can be verified."))
# Check if current user is a treatment professional or has appropriate rights
if not (self.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional') or
self.env.user.has_group('base.group_system')):
raise AccessError(_("Only treatment professionals can verify injuries."))
self.write({'stage': 'active'})
message = _("Injury verified by %s") % self.env.user.name
self.message_post(body=message)
return True
def action_resolve_injury(self):
"""Mark an injury as resolved."""
self.ensure_one()
if self.stage == "resolved":
return True
self.write({
'stage': 'resolved',
'resolution_date': fields.Date.context_today(self)
})
message = _("Injury marked as resolved by %s") % self.env.user.name
self.message_post(body=message)
return True
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
for rec in res.sudo():
# Subscribe the patient's partners to this injury
rec.message_subscribe(rec.patient_id.message_partner_ids)
# Manage treatment professional subscriptions
rec._manage_treatment_professional_subscriptions()
# Post a message about the new injury
msg_body = _("A new injury was created for this patient.")
if rec.diagnosis:
msg_body += _(" Diagnosis: %s." % rec.diagnosis)
rec.patient_id.message_post(body=msg_body, message_type="comment")
return res
def write(self, vals):
"""Override write to update subscriptions if treatment professionals change"""
# Store current treatment professional IDs before update
old_treatment_prof_ids = {}
if 'treatment_professional_ids' in vals:
for rec in self:
old_treatment_prof_ids[rec.id] = rec.treatment_professional_ids.ids
res = super().write(vals)
# If treatment professionals changed, update subscriptions
if 'treatment_professional_ids' in vals:
for rec in self:
# Only run subscription manager if treatment professionals actually changed
if rec.id in old_treatment_prof_ids and set(old_treatment_prof_ids[rec.id]) != set(rec.treatment_professional_ids.ids):
rec._manage_treatment_professional_subscriptions()
# Log the change in treatment professionals
new_profs = rec.treatment_professional_ids - self.env['res.users'].browse(old_treatment_prof_ids[rec.id])
removed_profs = self.env['res.users'].browse(old_treatment_prof_ids[rec.id]) - rec.treatment_professional_ids
# Create user-friendly message
msg = ''
if new_profs:
prof_names = ', '.join(new_profs.mapped('name'))
msg += _('Added treatment professional(s): %s. ') % prof_names
if removed_profs:
prof_names = ', '.join(removed_profs.mapped('name'))
msg += _('Removed treatment professional(s): %s.') % prof_names
if msg:
rec.message_post(body=msg)
# Also update subscriptions if internal_notes changes
if 'internal_notes' in vals:
for rec in self:
rec._manage_treatment_professional_subscriptions()
return res
def _manage_treatment_professional_subscriptions(self):
"""Subscribe treatment professionals to both regular and internal note updates
while ensuring non-treatment professionals only subscribe to external updates."""
self.ensure_one()
# Get the message subtypes
external_subtype = self.env.ref('bemade_sports_clinic.subtype_patient_injury_external_update')
internal_subtype = self.env.ref('bemade_sports_clinic.subtype_patient_injury_internal_update')
# Get all followers
followers = self.env['mail.followers'].search([
('res_model', '=', 'sports.patient.injury'),
('res_id', '=', self.id)
])
for follower in followers:
partner = self.env['res.partner'].browse(follower.partner_id.id)
users = self.env['res.users'].search([('partner_id', '=', partner.id)])
# Check if any of the users is a treatment professional
is_treatment_prof = False
for user in users:
if user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
is_treatment_prof = True
break
# Update follower subtypes based on role
if is_treatment_prof:
# Treatment professionals get both types of notifications
follower.write({
'subtype_ids': [(6, 0, [external_subtype.id, internal_subtype.id])]
})
else:
# Regular users only get external notifications
follower.write({
'subtype_ids': [(6, 0, [external_subtype.id])]
})
# Make sure treatment professionals (if any) are subscribed
if self.treatment_professional_ids:
self.message_subscribe(
partner_ids=self.treatment_professional_ids.mapped('partner_id').ids,
subtype_ids=[external_subtype.id, internal_subtype.id]
)
def unlink(self):
for rec in self:
msg_body = _("An injury was deleted.")
if rec.diagnosis:
msg_body += _(" Diagnosis: %s." % rec.diagnosis)
rec.patient_id.message_post(body=msg_body, message_type="comment")
return super().unlink()
def action_view_injury_form(self):
self.ensure_one()
return {
"type": "ir.actions.act_window",
"view_mode": "form",
"res_model": "sports.patient.injury",
"res_id": self.id,
"context": self._context,
}
def _track_subtype(self, init_values):
return self.env.ref("mail.mt_note")
def _track_template(self, changes):
res = super()._track_template(changes)
params = set(changes)
external = bool(external_tracking_fields & params)
if external:
first_external_field = (external_tracking_fields & params).pop()
res[first_external_field] = (
self.env.ref(
"bemade_sports_clinic.mail_template_patient_injury_status_update"
),
{
"auto_delete": False,
"subtype_id": self.env.ref(
"bemade_sports_clinic.subtype_patient_injury_external_update"
).id,
"email_layout_xmlid": "mail.mail_notification_light",
},
)
if "internal_notes" in changes:
res["internal_notes"] = (
self.env.ref(
"bemade_sports_clinic.mail_template_patient_injury_new_internal_note"
),
{
"auto_delete": False,
"subtype_id": self.env.ref(
"bemade_sports_clinic.subtype_patient_injury_internal_update"
).id,
"email_layout_xmlid": "mail.mail_notification_light",
},
)
return res
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
for record in res:
# Check if the current user is a treatment professional or admin
is_treatment_professional = self.env.user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
is_admin = self.env.user.has_group('base.group_system')
if is_treatment_professional or is_admin:
# If created by a treatment professional or admin, set to active
record.write({'stage': 'active'})
else:
# Otherwise, set to unverified
record.write({'stage': 'unverified'})
# Automatically assign therapist when creating an injury
current_user = self.env.user
# If the injury creator is a treatment professional, assign them
if current_user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional'):
record.treatment_professional_ids = [(4, current_user.id)]
# Otherwise, if there's a team_id, find and assign team therapists
elif record.team_id:
# Find all team staff users
team_staff = self.env['sports.team.staff'].search([
('team_id', '=', record.team_id.id)
])
# Filter to only staff users who are treatment professionals
if team_staff:
treatment_professional_group = self.env.ref('bemade_sports_clinic.group_sports_clinic_treatment_professional')
# Get all users from staff and filter them by group
staff_users = team_staff.mapped('user_ids')
therapist_users = staff_users.filtered(
lambda user: user.has_group('bemade_sports_clinic.group_sports_clinic_treatment_professional')
)
if therapist_users:
record.treatment_professional_ids = [(6, 0, therapist_users.ids)]
record.patient_id.recompute_followers()
return res