- 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.
404 lines
16 KiB
Python
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
|