bemade-addons/bemade_sports_clinic/models/patient.py

254 lines
9.7 KiB
Python

from odoo import models, fields, _, api, Command
from odoo.exceptions import ValidationError
from datetime import date
from dateutil.relativedelta import relativedelta
from odoo.addons.phone_validation.tools import phone_validation
from typing import Set, Tuple
external_tracking_fields = {
'last_consultation_date',
'match_status',
'practice_status',
'predicted_return_date',
'return_date',
}
internal_tracking_fields = {
'team_info_notes',
'age',
'date_of_birth',
}
class Patient(models.Model):
_name = 'sports.patient'
_description = "Patient"
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'last_name, first_name'
# res.partner fields
partner_id = fields.Many2one(comodel_name='res.partner', string='Contact', ondelete='restrict', compute_sudo=True)
first_name = fields.Char(required=True, tracking=True)
last_name = fields.Char(required=True, tracking=True)
name = fields.Char(related='partner_id.name', compute="_compute_name", compute_sudo=True)
phone = fields.Char(related='partner_id.phone', readonly=False)
mobile = fields.Char(related='partner_id.mobile', readonly=False)
street = fields.Char(related='partner_id.street', readonly=False)
street2 = fields.Char(related='partner_id.street2', readonly=False)
city = fields.Char(related='partner_id.city', readonly=False)
state_id = fields.Many2one(related='partner_id.state_id', readonly=False)
zip = fields.Char(related='partner_id.zip', readonly=False)
country_id = fields.Many2one(related='partner_id.country_id', readonly=False)
email = fields.Char(related='partner_id.email', readonly=False)
# Patient fields
date_of_birth = fields.Date(
groups="bemade_sports_clinic.group_sports_clinic_treatment_professional",
tracking=True)
age = fields.Integer(
compute='_compute_age',
groups="bemade_sports_clinic.group_sports_clinic_treatment_professional"
)
contact_ids = fields.One2many(
comodel_name='sports.patient.contact',
inverse_name='patient_id',
string='Patient Contacts',
groups="bemade_sports_clinic.group_sports_clinic_user"
)
team_ids = fields.Many2many(
comodel_name='sports.team',
relation='sports_team_patient_rel',
column1='patient_id',
column2='team_id',
string='Teams',
)
match_status = fields.Selection( # Selection rather than bool for easy expansion later
selection=[
('yes', 'Yes'),
('no', 'No'),
],
required=True,
default='yes',
tracking=True)
practice_status = fields.Selection(
selection=[
('yes', 'Yes'),
('no_contact', 'Yes, no contact'),
('no', 'No')
],
tracking=True,
required=True,
default='yes',
)
injury_ids = fields.One2many(
comodel_name='sports.patient.injury',
inverse_name='patient_id',
string='Injuries',
)
injured_since = fields.Date(compute='_compute_is_injured')
predicted_return_date = fields.Date(tracking=True)
return_date = fields.Date(
tracking=True,
help="When the player was cleared by medical staff to "
"return to match play."
)
is_injured = fields.Boolean(compute="_compute_is_injured")
stage = fields.Selection(
selection=[
('no_play', 'Injured'),
('practice_ok', 'Practice OK'),
('healthy', 'Play OK')
],
compute='_compute_stage')
last_consultation_date = fields.Date(tracking=True)
active_injury_count = fields.Integer(compute='_compute_active_injury_count')
allergies = fields.Text()
team_info_notes = fields.Html(
string="Notes",
tracking=True,
)
def default_get(self, fields_list):
res = super().default_get(fields_list)
if 'team_ids' in fields_list and 'params' in self.env.context \
and self.env.context.get('params')['model'] == 'sports.team':
team = self.env['sports.team'].browse(self.env.context.get('params')['id'])
team_ids = [Command.set([team.id])]
if team_ids:
res.update({'team_ids': team_ids})
return res
@api.model_create_multi
def create(self, vals_list):
for row in vals_list:
if 'partner_id' not in row:
row['partner_id'] = self.env['res.partner'].create({
'name': self._get_name_from_first_and_last(row['first_name'], row['last_name'])
}).id
return super().create(vals_list)
@api.constrains('match_status', 'practice_status')
def constrain_match_and_practice_status(self):
""" Avoid invalid combinations of match and practice status:
- Yes (match), No (practice)
- Yes (match), No Contact (practice)
"""
# combinations of (match_status, practice_status) that are valid
valid_combinations = [('yes', 'yes'), ('no', 'yes'), ('no', 'no_contact'), ('no', 'no')]
for rec in self:
if (rec.match_status, rec.practice_status) not in valid_combinations:
raise ValidationError(_("Invalid combination of match and practice status."))
@api.depends('injury_ids.stage')
def _compute_active_injury_count(self):
for rec in self:
rec.active_injury_count = len(rec.injury_ids.filtered(lambda r: r.stage == 'active'))
@api.depends('match_status', 'practice_status')
def _compute_stage(self):
stage_map = {
('yes', 'yes'): 'healthy',
('no', 'yes'): 'practice_ok',
('no', 'no_contact'): 'practice_ok',
('no', 'no'): 'no_play',
}
for rec in self:
if (rec.match_status, rec.practice_status) not in stage_map:
rec.stage = False # not a valid combination, will be caught by constraint if save is attempted
continue
rec.stage = stage_map[(rec.match_status, rec.practice_status)]
@api.depends('date_of_birth')
def _compute_age(self):
for rec in self:
if not rec.date_of_birth:
rec.age = False
else:
rec.age = relativedelta(date.today(), rec.date_of_birth).years
@api.depends('first_name', 'last_name')
def _compute_name(self):
for rec in self:
rec.name = self._get_name_from_first_and_last(rec.first_name, rec.last_name)
@api.model
def _get_name_from_first_and_last(self, first_name, last_name):
return ((first_name or "") + " " + (last_name or "")).strip()
@api.depends('practice_status', 'match_status', 'injury_ids.injury_date')
def _compute_is_injured(self):
for rec in self:
rec.is_injured = rec.practice_status != 'yes' or rec.match_status != 'yes'
if rec.is_injured:
unresolved_injuries = rec.injury_ids.filtered(lambda r: not r.stage == 'resolved')
rec.injured_since = unresolved_injuries and unresolved_injuries[0].injury_date
else:
rec.injured_since = False
def action_view_patient_form(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sports.patient',
'res_id': self.id,
'context': self._context,
}
def action_consulted_today(self):
self.ensure_one() # should just be called from form view
self.last_consultation_date = date.today()
return {
'view_mode': 'form',
'res_model': 'sports.patient',
'context': self._context,
'res_id': self.id,
}
@api.onchange('mobile', 'country_id')
def _onchange_mobile_validation(self):
if self.mobile:
self.mobile = self._phone_format(self.mobile, force_format="INTERNATIONAL")
@api.onchange('phone', 'country_id')
def _onchange_phone_validation(self):
if self.phone:
self.phone = self._phone_format(self.phone, force_format="INTERNATIONAL")
def _phone_format(self, number, force_format='E164'):
country = self.country_id or self.env.company.country_id
if not country or not number:
return number
return phone_validation.phone_format(
number,
country.code if country else None,
country.phone_code if country else None,
force_format=force_format,
raise_exception=False
)
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_status_update'), {
'auto_delete_message': False,
'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_external_update').id,
'email_layout_xmlid': 'mail.mail_notification_light',
}
)
if 'team_info_notes' in changes:
res['team_info_notes'] = (
self.env.ref('bemade_sports_clinic.mail_template_patient_new_internal_note'), {
'auto_delete_message': False,
'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_internal_update').id,
'email_layout_xmlid': 'mail.mail_notification_light',
}
)
return res