added tests and fixed code for patient and injury follower logic
This commit is contained in:
parent
03227bb5f4
commit
eda7f32c7a
6 changed files with 534 additions and 200 deletions
|
|
@ -3,106 +3,113 @@ 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
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
external_tracking_fields = {
|
||||
'last_consultation_date',
|
||||
'match_status',
|
||||
'practice_status',
|
||||
'predicted_return_date',
|
||||
'return_date',
|
||||
"last_consultation_date",
|
||||
"match_status",
|
||||
"practice_status",
|
||||
"predicted_return_date",
|
||||
"return_date",
|
||||
}
|
||||
|
||||
internal_tracking_fields = {
|
||||
'team_info_notes',
|
||||
'age',
|
||||
'date_of_birth',
|
||||
"team_info_notes",
|
||||
"age",
|
||||
"date_of_birth",
|
||||
}
|
||||
|
||||
|
||||
class Patient(models.Model):
|
||||
_name = 'sports.patient'
|
||||
_name = "sports.patient"
|
||||
_description = "Patient"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'last_name, first_name'
|
||||
_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)
|
||||
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)
|
||||
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)
|
||||
tracking=True,
|
||||
)
|
||||
age = fields.Integer(
|
||||
compute='_compute_age',
|
||||
groups="bemade_sports_clinic.group_sports_clinic_treatment_professional"
|
||||
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"
|
||||
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',
|
||||
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
|
||||
match_status = fields.Selection(
|
||||
# Selection rather than bool for easy expansion later
|
||||
selection=[
|
||||
('yes', 'Yes'),
|
||||
('no', 'No'),
|
||||
("yes", "Yes"),
|
||||
("no", "No"),
|
||||
],
|
||||
required=True,
|
||||
default='yes',
|
||||
tracking=True)
|
||||
default="yes",
|
||||
tracking=True,
|
||||
)
|
||||
practice_status = fields.Selection(
|
||||
selection=[
|
||||
('yes', 'Yes'),
|
||||
('no_contact', 'Yes, no contact'),
|
||||
('no', 'No')
|
||||
],
|
||||
selection=[("yes", "Yes"), ("no_contact", "Yes, no contact"), ("no", "No")],
|
||||
tracking=True,
|
||||
required=True,
|
||||
default='yes',
|
||||
default="yes",
|
||||
)
|
||||
injury_ids = fields.One2many(
|
||||
comodel_name='sports.patient.injury',
|
||||
inverse_name='patient_id',
|
||||
string='Injuries',
|
||||
tracking=True,
|
||||
comodel_name="sports.patient.injury",
|
||||
inverse_name="patient_id",
|
||||
string="Injuries",
|
||||
)
|
||||
injured_since = fields.Date(compute='_compute_is_injured')
|
||||
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."
|
||||
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')
|
||||
("no_play", "Injured"),
|
||||
("practice_ok", "Practice OK"),
|
||||
("healthy", "Play OK"),
|
||||
],
|
||||
compute='_compute_stage')
|
||||
compute="_compute_stage",
|
||||
)
|
||||
last_consultation_date = fields.Date(tracking=True)
|
||||
active_injury_count = fields.Integer(compute='_compute_active_injury_count')
|
||||
active_injury_count = fields.Integer(compute="_compute_active_injury_count")
|
||||
allergies = fields.Text()
|
||||
team_info_notes = fields.Html(
|
||||
string="Notes",
|
||||
|
|
@ -111,55 +118,84 @@ class Patient(models.Model):
|
|||
|
||||
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'])
|
||||
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})
|
||||
res.update({"team_ids": team_ids})
|
||||
return res
|
||||
|
||||
def write(self, values):
|
||||
res = super().write(values)
|
||||
if "team_ids" in values:
|
||||
self.recompute_followers()
|
||||
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)
|
||||
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
|
||||
)
|
||||
res = super().create(vals_list)
|
||||
res.recompute_followers()
|
||||
return res
|
||||
|
||||
@api.constrains('match_status', 'practice_status')
|
||||
@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)
|
||||
"""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')]
|
||||
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."))
|
||||
raise ValidationError(
|
||||
_("Invalid combination of match and practice status.")
|
||||
)
|
||||
|
||||
@api.depends('injury_ids.stage')
|
||||
@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'))
|
||||
rec.active_injury_count = len(
|
||||
rec.injury_ids.filtered(lambda r: r.stage == "active")
|
||||
)
|
||||
|
||||
@api.depends('match_status', 'practice_status')
|
||||
@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',
|
||||
("yes", "yes"): "healthy",
|
||||
("no", "yes"): "practice_ok",
|
||||
("no", "no_contact"): "practice_ok",
|
||||
("no", "no"): "no_play",
|
||||
}
|
||||
for rec in self:
|
||||
# not a valid combination, will be caught by constraint if save is attempted
|
||||
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
|
||||
rec.stage = False
|
||||
continue
|
||||
rec.stage = stage_map[(rec.match_status, rec.practice_status)]
|
||||
|
||||
@api.depends('date_of_birth')
|
||||
@api.depends("date_of_birth")
|
||||
def _compute_age(self):
|
||||
for rec in self:
|
||||
if not rec.date_of_birth:
|
||||
|
|
@ -167,7 +203,7 @@ class Patient(models.Model):
|
|||
else:
|
||||
rec.age = relativedelta(date.today(), rec.date_of_birth).years
|
||||
|
||||
@api.depends('first_name', 'last_name')
|
||||
@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)
|
||||
|
|
@ -176,47 +212,51 @@ class Patient(models.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')
|
||||
@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'
|
||||
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
|
||||
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,
|
||||
"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,
|
||||
"view_mode": "form",
|
||||
"res_model": "sports.patient",
|
||||
"context": self._context,
|
||||
"res_id": self.id,
|
||||
}
|
||||
|
||||
@api.onchange('mobile', 'country_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')
|
||||
@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'):
|
||||
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
|
||||
|
|
@ -225,11 +265,11 @@ class Patient(models.Model):
|
|||
country.code if country else None,
|
||||
country.phone_code if country else None,
|
||||
force_format=force_format,
|
||||
raise_exception=False
|
||||
raise_exception=False,
|
||||
)
|
||||
|
||||
def _track_subtype(self, init_values):
|
||||
return self.env.ref('mail.mt_note')
|
||||
return self.env.ref("mail.mt_note")
|
||||
|
||||
def _track_template(self, changes):
|
||||
res = super()._track_template(changes)
|
||||
|
|
@ -238,33 +278,46 @@ class Patient(models.Model):
|
|||
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',
|
||||
}
|
||||
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',
|
||||
}
|
||||
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
|
||||
|
||||
def recompute_followers(self):
|
||||
""" Recompute the followers for this patient (and its injuries) based on the
|
||||
"""Recompute the followers for this patient (and its injuries) based on the
|
||||
changes to a specific team's staff members. Ignoring manually unsubscribed
|
||||
followers, the set of followers should be the set of staff on all teams the
|
||||
patient is part of."""
|
||||
for patient in self:
|
||||
current_followers = patient.message_partner_ids
|
||||
future_followers = patient.team_ids.mapped('staff_ids').mapped('partner_id')
|
||||
future_followers = patient.team_ids.mapped("staff_ids").mapped("partner_id")
|
||||
removed_followers = current_followers - future_followers
|
||||
added_followers = future_followers - current_followers
|
||||
patient.message_unsubscribe(removed_followers.ids)
|
||||
patient.message_subscribe(added_followers.ids)
|
||||
patient.injury_ids.message_unsubscribe(removed_followers.ids)
|
||||
patient.injury_ids.message_subscribe(added_followers.ids)
|
||||
if removed_followers:
|
||||
_logger.debug(f"{self} unsubscribing {removed_followers}")
|
||||
patient.message_unsubscribe(removed_followers.ids)
|
||||
patient.injury_ids.message_unsubscribe(removed_followers.ids)
|
||||
if future_followers:
|
||||
_logger.debug(f"{self} subscribing {future_followers}")
|
||||
patient.message_subscribe(future_followers.ids)
|
||||
patient.injury_ids.message_subscribe(future_followers.ids)
|
||||
|
|
|
|||
|
|
@ -180,3 +180,9 @@ class PatientInjury(models.Model):
|
|||
},
|
||||
)
|
||||
return res
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
res.patient_id.recompute_followers()
|
||||
return res
|
||||
|
|
|
|||
|
|
@ -4,25 +4,35 @@ from odoo.exceptions import ValidationError
|
|||
|
||||
|
||||
class Partner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
_inherit = "res.partner"
|
||||
|
||||
owned_team_ids = fields.One2many(comodel_name='sports.team',
|
||||
inverse_name='parent_id')
|
||||
staff_ids = fields.One2many(comodel_name='sports.team.staff',
|
||||
inverse_name='team_id')
|
||||
team_staff_rel_ids = fields.One2many(comodel_name='sports.team.staff',
|
||||
inverse_name='partner_id',
|
||||
string='Teams Served',
|
||||
help='The teams this person works for.')
|
||||
teams_served_ids = fields.One2many(comodel_name='sports.team', compute='_compute_teams_served')
|
||||
patient_ids = fields.One2many(comodel_name='sports.patient', inverse_name='partner_id')
|
||||
owned_team_ids = fields.One2many(
|
||||
comodel_name="sports.team", inverse_name="parent_id"
|
||||
)
|
||||
staff_ids = fields.One2many(
|
||||
comodel_name="sports.team.staff", inverse_name="team_id"
|
||||
)
|
||||
team_staff_rel_ids = fields.One2many(
|
||||
comodel_name="sports.team.staff",
|
||||
inverse_name="partner_id",
|
||||
string="Employer(s)",
|
||||
help="The teams this person works for.",
|
||||
)
|
||||
teams_served_ids = fields.One2many(
|
||||
comodel_name="sports.team", compute="_compute_teams_served"
|
||||
)
|
||||
patient_ids = fields.One2many(
|
||||
comodel_name="sports.patient", inverse_name="partner_id"
|
||||
)
|
||||
|
||||
def write(self, vals):
|
||||
if self.patient_ids and 'name' in vals:
|
||||
raise ValidationError(_("To change a patient's name, change it from the patient form."))
|
||||
if self.patient_ids and "name" in vals:
|
||||
raise ValidationError(
|
||||
_("To change a patient's name, change it from the patient form.")
|
||||
)
|
||||
return super().write(vals)
|
||||
|
||||
@api.depends('team_staff_rel_ids.team_id')
|
||||
@api.depends("team_staff_rel_ids.team_id")
|
||||
def _compute_teams_served(self):
|
||||
for rec in self:
|
||||
rec.teams_served_ids = rec.team_staff_rel_ids.mapped('team_id')
|
||||
rec.teams_served_ids = rec.team_staff_rel_ids.mapped("team_id")
|
||||
|
|
|
|||
|
|
@ -5,91 +5,107 @@ from odoo.exceptions import ValidationError
|
|||
class SportsTeam(models.Model):
|
||||
_name = "sports.team"
|
||||
_description = "Sports Team"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_inherit = ["mail.thread", "mail.activity.mixin"]
|
||||
|
||||
name = fields.Char()
|
||||
patient_ids = fields.Many2many(
|
||||
comodel_name='sports.patient',
|
||||
relation='sports_team_patient_rel',
|
||||
column1='team_id',
|
||||
column2='patient_id',
|
||||
string='Players',
|
||||
comodel_name="sports.patient",
|
||||
relation="sports_team_patient_rel",
|
||||
column1="team_id",
|
||||
column2="patient_id",
|
||||
string="Players",
|
||||
tracking=True,
|
||||
)
|
||||
player_count = fields.Integer(compute="_compute_player_counts")
|
||||
injured_count = fields.Integer(compute="_compute_player_counts")
|
||||
healthy_count = fields.Integer(compute="_compute_player_counts")
|
||||
parent_id = fields.Many2one(
|
||||
comodel_name='res.partner',
|
||||
string='Parent Organization',
|
||||
ondelete='restrict',
|
||||
comodel_name="res.partner",
|
||||
string="Parent Organization",
|
||||
ondelete="restrict",
|
||||
tracking=True,
|
||||
)
|
||||
staff_ids = fields.One2many(
|
||||
comodel_name='sports.team.staff',
|
||||
inverse_name='team_id',
|
||||
comodel_name="sports.team.staff",
|
||||
inverse_name="team_id",
|
||||
tracking=True,
|
||||
)
|
||||
head_coach_id = fields.Many2one(
|
||||
comodel_name='res.partner',
|
||||
compute='_compute_head_coach',
|
||||
comodel_name="res.partner",
|
||||
compute="_compute_head_coach",
|
||||
store=True,
|
||||
)
|
||||
head_coach_name = fields.Char(related='head_coach_id.name')
|
||||
head_coach_name = fields.Char(
|
||||
related="head_coach_id.name",
|
||||
string="Head Coach Name",
|
||||
)
|
||||
head_therapist_id = fields.Many2one(
|
||||
comodel_name='res.partner',
|
||||
compute='_compute_head_therapist',
|
||||
comodel_name="res.partner",
|
||||
compute="_compute_head_therapist",
|
||||
store=True,
|
||||
string="Head Therapist",
|
||||
)
|
||||
head_therapist_name = fields.Char(
|
||||
related="head_therapist_id.name",
|
||||
string="Head Therapist Name",
|
||||
)
|
||||
head_therapist_name = fields.Char(related='head_therapist_id.name')
|
||||
website = fields.Char()
|
||||
allowed_user_ids = fields.Many2many(
|
||||
comodel_name='res.users',
|
||||
relation='sports_team_res_users_rel',
|
||||
column1='team_id',
|
||||
column2='user_id',
|
||||
string='Allowed Users',
|
||||
domain=lambda self: [['groups_id', 'in', self.env.ref("base.group_user").ids]],
|
||||
comodel_name="res.users",
|
||||
relation="sports_team_res_users_rel",
|
||||
column1="team_id",
|
||||
column2="user_id",
|
||||
string="Allowed Users",
|
||||
domain=lambda self: [["groups_id", "in", self.env.ref("base.group_user").ids]],
|
||||
)
|
||||
|
||||
def write(self, vals):
|
||||
previous_patient_ids = self.patient_ids
|
||||
res = super().write(vals)
|
||||
if 'staff_ids' in vals:
|
||||
if "staff_ids" in vals or "patient_ids" in vals:
|
||||
self._allow_access_for_staff_internal_users()
|
||||
self.patient_ids.recompute_followers()
|
||||
(self.patient_ids | previous_patient_ids).recompute_followers()
|
||||
return res
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
for index, rec in enumerate(res):
|
||||
if 'staff_ids' in vals_list[index]:
|
||||
if "staff_ids" in vals_list[index]:
|
||||
rec._allow_access_for_staff_internal_users()
|
||||
rec.patient_ids.recompute_followers()
|
||||
return res
|
||||
|
||||
@api.depends('patient_ids.is_injured')
|
||||
def unlink(self):
|
||||
to_recompute = self.patient_ids
|
||||
res = super().unlink()
|
||||
to_recompute.recompute_followers()
|
||||
return res
|
||||
|
||||
@api.depends("patient_ids.is_injured")
|
||||
def _compute_player_counts(self):
|
||||
for rec in self:
|
||||
rec.player_count = len(rec.patient_ids)
|
||||
rec.injured_count = len(rec.patient_ids.filtered(lambda p: p.is_injured))
|
||||
rec.healthy_count = rec.player_count - rec.injured_count
|
||||
|
||||
@api.depends('staff_ids.role')
|
||||
@api.depends("staff_ids.role")
|
||||
def _compute_head_coach(self):
|
||||
for rec in self:
|
||||
staff = rec.staff_ids.filtered(lambda r: r.role == 'head_coach')
|
||||
staff = rec.staff_ids.filtered(lambda r: r.role == "head_coach")
|
||||
rec.head_coach_id = staff.partner_id if staff else False
|
||||
|
||||
@api.depends('staff_ids.role')
|
||||
@api.depends("staff_ids.role")
|
||||
def _compute_head_therapist(self):
|
||||
for rec in self:
|
||||
staff = rec.staff_ids.filtered(lambda r: r.role == 'head_therapist')
|
||||
staff = rec.staff_ids.filtered(lambda r: r.role == "head_therapist")
|
||||
rec.head_therapist_id = staff.partner_id if staff else False
|
||||
|
||||
def _allow_access_for_staff_internal_users(self):
|
||||
for rec in self:
|
||||
rec.allowed_user_ids |= rec.staff_ids.user_ids.filtered(lambda user: user.has_group("base.group_user"))
|
||||
rec.allowed_user_ids |= rec.staff_ids.user_ids.filtered(
|
||||
lambda user: user.has_group("base.group_user")
|
||||
)
|
||||
|
||||
|
||||
class TeamStaff(models.Model):
|
||||
|
|
@ -97,56 +113,116 @@ class TeamStaff(models.Model):
|
|||
_description = "Sports Team Staff"
|
||||
|
||||
sequence = fields.Integer()
|
||||
team_id = fields.Many2one(comodel_name='sports.team', string='Team', required=True)
|
||||
partner_id = fields.Many2one(comodel_name='res.partner', string='Staff Member',
|
||||
required=True, domain=[('is_company', '=', False)])
|
||||
role = fields.Selection(selection=[
|
||||
('head_coach', 'Head Coach'),
|
||||
('head_therapist', 'Head Therapist'),
|
||||
('coach', 'Coach'),
|
||||
('therapist', 'Therapist'),
|
||||
('doctor', 'Doctor'),
|
||||
('other', 'Other')
|
||||
], required=True)
|
||||
mobile = fields.Char(related='partner_id.mobile', readonly=False)
|
||||
name = fields.Char(related='partner_id.name', readonly=False)
|
||||
parent_id = fields.Many2one(related='partner_id.parent_id', readonly=False, string="Organization",
|
||||
domain=[('is_company', '=', True)])
|
||||
email = fields.Char(related='partner_id.email', readonly=False)
|
||||
user_ids = fields.One2many(related='partner_id.user_ids', readonly=True)
|
||||
has_portal_access = fields.Boolean(compute='_compute_has_portal_access', compute_sudo=True)
|
||||
team_id = fields.Many2one(
|
||||
comodel_name="sports.team",
|
||||
string="Team",
|
||||
required=True,
|
||||
ondelete="cascade",
|
||||
)
|
||||
partner_id = fields.Many2one(
|
||||
comodel_name="res.partner",
|
||||
string="Staff Member",
|
||||
required=True,
|
||||
domain=[("is_company", "=", False)],
|
||||
ondelete="cascade",
|
||||
)
|
||||
role = fields.Selection(
|
||||
selection=[
|
||||
("head_coach", "Head Coach"),
|
||||
("head_therapist", "Head Therapist"),
|
||||
("coach", "Coach"),
|
||||
("therapist", "Therapist"),
|
||||
("doctor", "Doctor"),
|
||||
("other", "Other"),
|
||||
],
|
||||
required=True,
|
||||
)
|
||||
mobile = fields.Char(related="partner_id.mobile", readonly=False)
|
||||
name = fields.Char(related="partner_id.name", readonly=False)
|
||||
parent_id = fields.Many2one(
|
||||
related="partner_id.parent_id",
|
||||
readonly=False,
|
||||
string="Organization",
|
||||
domain=[("is_company", "=", True)],
|
||||
)
|
||||
email = fields.Char(related="partner_id.email", readonly=False)
|
||||
user_ids = fields.One2many(related="partner_id.user_ids", readonly=True)
|
||||
has_portal_access = fields.Boolean(
|
||||
compute="_compute_has_portal_access", compute_sudo=True
|
||||
)
|
||||
|
||||
_sql_constraints = [('team_staff_unique', 'unique(team_id, partner_id)',
|
||||
'Each partner can only be related to a given team once.')]
|
||||
_sql_constraints = [
|
||||
(
|
||||
"team_staff_unique",
|
||||
"unique(team_id, partner_id)",
|
||||
"Each partner can only be related to a given team once.",
|
||||
)
|
||||
]
|
||||
|
||||
@api.constrains('role')
|
||||
@api.constrains("role")
|
||||
def _constrain_role(self):
|
||||
teams = self.mapped('team_id')
|
||||
teams = self.mapped("team_id")
|
||||
for team in teams:
|
||||
if len(team.staff_ids.filtered(lambda r: r.role == 'head_coach')) > 1:
|
||||
if len(team.staff_ids.filtered(lambda r: r.role == "head_coach")) > 1:
|
||||
raise ValidationError(_("A team can have only one head coach."))
|
||||
if len(team.staff_ids.filtered(lambda r: r.role == 'head_therapist')) > 1:
|
||||
if len(team.staff_ids.filtered(lambda r: r.role == "head_therapist")) > 1:
|
||||
raise ValidationError(_("A team can have only one head therapist."))
|
||||
|
||||
@api.onchange('mobile')
|
||||
@api.onchange("mobile")
|
||||
def _onchange_mobile_validation(self):
|
||||
if self.mobile:
|
||||
self.mobile = self.partner_id._phone_format(self.mobile, force_format='INTERNATIONAL')
|
||||
self.mobile = self.partner_id._phone_format(
|
||||
self.mobile, force_format="INTERNATIONAL"
|
||||
)
|
||||
|
||||
@api.depends('user_ids', 'user_ids.groups_id')
|
||||
@api.depends("user_ids", "user_ids.groups_id")
|
||||
def _compute_has_portal_access(self):
|
||||
for rec in self:
|
||||
rec.has_portal_access = bool(rec.user_ids.filtered(lambda r: r.has_group('base.group_portal'))) or bool(
|
||||
rec.user_ids.filtered(lambda r: r.has_group('base.group_user'))) or bool(rec.partner_id.signup_token)
|
||||
rec.has_portal_access = (
|
||||
bool(rec.user_ids.filtered(lambda r: r.has_group("base.group_portal")))
|
||||
or bool(rec.user_ids.filtered(lambda r: r.has_group("base.group_user")))
|
||||
or bool(rec.partner_id.signup_token)
|
||||
)
|
||||
|
||||
def action_revoke_portal_access(self):
|
||||
group_portal = self.env.ref('base.group_portal')
|
||||
group_public = self.env.ref('base.group_public')
|
||||
group_portal = self.env.ref("base.group_portal")
|
||||
group_public = self.env.ref("base.group_public")
|
||||
self.user_ids.write(
|
||||
{'groups_id': [Command.unlink(group_portal.id), Command.link(group_public.id)], 'active': False})
|
||||
{
|
||||
"groups_id": [
|
||||
Command.unlink(group_portal.id),
|
||||
Command.link(group_public.id),
|
||||
],
|
||||
"active": False,
|
||||
}
|
||||
)
|
||||
# Remove the signup token, so it cannot be used
|
||||
self.partner_id.sudo().signup_token = False
|
||||
|
||||
def action_grant_portal_access(self):
|
||||
wiz = self.env['portal.wizard'].create({'partner_ids': [(4, self.partner_id.id)]})
|
||||
wiz = self.env["portal.wizard"].create(
|
||||
{"partner_ids": [(4, self.partner_id.id)]}
|
||||
)
|
||||
return wiz._action_open_modal()
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
res.team_id.mapped("patient_ids").recompute_followers()
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
patients = self.team_id.mapped("patient_ids")
|
||||
super().unlink()
|
||||
patients.recompute_followers()
|
||||
|
||||
def write(self, values):
|
||||
if "team_id" in values:
|
||||
to_recompute = self.env["sports.patient"]
|
||||
for rec in self:
|
||||
if rec.team_id.id != values["team_id"]:
|
||||
to_recompute |= rec.team_id.patient_ids
|
||||
res = super().write(values)
|
||||
to_recompute.recompute_followers()
|
||||
return res
|
||||
return super().write(values)
|
||||
|
|
|
|||
1
bemade_sports_clinic/tests/__init__.py
Normal file
1
bemade_sports_clinic/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import test_patient
|
||||
188
bemade_sports_clinic/tests/test_patient.py
Normal file
188
bemade_sports_clinic/tests/test_patient.py
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
from odoo.tests import TransactionCase, tagged
|
||||
from odoo import fields, Command
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
@tagged("-at_install", "post_install")
|
||||
class TestPatient(TransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
organization = cls.env["res.partner"].create(
|
||||
{"name": "Test Org"},
|
||||
)
|
||||
team1 = cls.env["sports.team"].create(
|
||||
{
|
||||
"name": "Test team",
|
||||
"parent_id": organization.id,
|
||||
}
|
||||
)
|
||||
coach = cls.env["sports.team.staff"].create(
|
||||
{
|
||||
"partner_id": cls.env["res.partner"]
|
||||
.create(
|
||||
{
|
||||
"name": "Test Coach",
|
||||
}
|
||||
)
|
||||
.id,
|
||||
"team_id": team1.id,
|
||||
"role": "head_coach",
|
||||
}
|
||||
)
|
||||
patient1 = cls.env["sports.patient"].create(
|
||||
{
|
||||
"first_name": "Test",
|
||||
"last_name": "Patient 1",
|
||||
"team_ids": [Command.set(team1.ids)],
|
||||
"date_of_birth": fields.Date.today() - timedelta(days=18 * 365),
|
||||
}
|
||||
)
|
||||
patient1_injury = cls.env["sports.patient.injury"].create(
|
||||
{
|
||||
"patient_id": patient1.id,
|
||||
}
|
||||
)
|
||||
patient2 = cls.env["sports.patient"].create(
|
||||
{
|
||||
"first_name": "Test",
|
||||
"last_name": "Patient2",
|
||||
"team_ids": [Command.set(team1.ids)],
|
||||
"date_of_birth": fields.Date.today() - timedelta(days=21 * 365),
|
||||
}
|
||||
)
|
||||
patient2_injury = cls.env["sports.patient.injury"].create(
|
||||
{
|
||||
"patient_id": patient2.id,
|
||||
}
|
||||
)
|
||||
(
|
||||
cls.organization,
|
||||
cls.team1,
|
||||
cls.coach,
|
||||
cls.patient1,
|
||||
cls.patient1_injury,
|
||||
cls.patient2,
|
||||
cls.patient2_injury,
|
||||
) = (
|
||||
organization,
|
||||
team1,
|
||||
coach,
|
||||
patient1,
|
||||
patient1_injury,
|
||||
patient2,
|
||||
patient2_injury,
|
||||
)
|
||||
|
||||
def test_adding_staff_adds_follower_to_patient_and_injury(self):
|
||||
therapist = self.env["sports.team.staff"].create(
|
||||
{
|
||||
"team_id": self.team1.id,
|
||||
"partner_id": self.env["res.partner"]
|
||||
.create(
|
||||
{"name": "Tester"},
|
||||
)
|
||||
.id,
|
||||
"role": "therapist",
|
||||
}
|
||||
)
|
||||
|
||||
therapist = therapist.partner_id
|
||||
self.assertIn(therapist, self.patient1.message_partner_ids)
|
||||
self.assertIn(therapist, self.patient2.message_partner_ids)
|
||||
self.assertIn(therapist, self.patient1_injury.message_partner_ids)
|
||||
self.assertIn(therapist, self.patient2_injury.message_partner_ids)
|
||||
|
||||
def test_removing_staff_removes_follower_from_patient_and_injury(self):
|
||||
coach = self.coach.partner_id
|
||||
self.team1.staff_ids = False
|
||||
self.assertNotIn(coach, self.patient1.message_partner_ids)
|
||||
self.assertNotIn(coach, self.patient1_injury.message_partner_ids)
|
||||
self.assertNotIn(coach, self.patient2.message_partner_ids)
|
||||
self.assertNotIn(coach, self.patient2_injury.message_partner_ids)
|
||||
|
||||
def test_deleting_team_removes_follower_from_patient_and_injury(self):
|
||||
coach = self.coach.partner_id
|
||||
self.team1.unlink()
|
||||
self.assertNotIn(coach, self.patient1.message_partner_ids)
|
||||
self.assertNotIn(coach, self.patient1_injury.message_partner_ids)
|
||||
self.assertNotIn(coach, self.patient2.message_partner_ids)
|
||||
self.assertNotIn(coach, self.patient2_injury.message_partner_ids)
|
||||
|
||||
def test_adding_second_team_subscribes_new_staff(self):
|
||||
team2, therapist, coach = self._generate_second_team_and_staff()
|
||||
|
||||
team2.patient_ids = self.patient1
|
||||
|
||||
self.assertIn(therapist, self.patient1.message_partner_ids)
|
||||
self.assertIn(coach, self.patient1.message_partner_ids)
|
||||
self.assertIn(therapist, self.patient1_injury.message_partner_ids)
|
||||
self.assertIn(coach, self.patient1_injury.message_partner_ids)
|
||||
self.assertEqual(len(self.patient1_injury.message_partner_ids), 2)
|
||||
self.assertEqual(len(self.patient1.message_partner_ids), 2)
|
||||
|
||||
def test_creating_patient_in_team_assigns_followers(self):
|
||||
patient = self.env["sports.patient"].create(
|
||||
{
|
||||
"first_name": "Test",
|
||||
"last_name": "Patient",
|
||||
"date_of_birth": fields.Date.today() - timedelta(days=365 * 20),
|
||||
"team_ids": [(6, 0, self.team1.ids)],
|
||||
}
|
||||
)
|
||||
|
||||
cp_id = self.coach.partner_id
|
||||
self.assertIn(cp_id, patient.message_partner_ids)
|
||||
|
||||
injury = self.env["sports.patient.injury"].create(
|
||||
{
|
||||
"patient_id": patient.id,
|
||||
"diagnosis": "Something",
|
||||
}
|
||||
)
|
||||
self.assertIn(cp_id, injury.message_partner_ids)
|
||||
|
||||
def test_removing_second_team_correctly_adjusts_staff(self):
|
||||
team2, therapist, coach = self._generate_second_team_and_staff()
|
||||
self.patient1.write({"team_ids": [Command.link(team2.id)]})
|
||||
self.assertIn(self.patient1, team2.patient_ids)
|
||||
self.assertIn(therapist, self.patient1.message_partner_ids)
|
||||
team2.write({"patient_ids": [Command.unlink(self.patient1.id)]})
|
||||
self.assertNotIn(self.patient1, team2.patient_ids)
|
||||
self.assertEqual(self.patient1.message_partner_ids, coach)
|
||||
self.assertEqual(self.patient1_injury.message_partner_ids, coach)
|
||||
|
||||
def _generate_second_team_and_staff(self):
|
||||
team2 = self.env["sports.team"].create(
|
||||
{
|
||||
"parent_id": self.organization.id,
|
||||
"name": "Test team 2",
|
||||
}
|
||||
)
|
||||
therapist = (
|
||||
self.env["sports.team.staff"]
|
||||
.create(
|
||||
{
|
||||
"team_id": team2.id,
|
||||
"partner_id": self.env["res.partner"]
|
||||
.create(
|
||||
{"name": "Tester"},
|
||||
)
|
||||
.id,
|
||||
"role": "therapist",
|
||||
}
|
||||
)
|
||||
.partner_id
|
||||
)
|
||||
coach = (
|
||||
self.env["sports.team.staff"]
|
||||
.create(
|
||||
{
|
||||
"team_id": team2.id,
|
||||
"partner_id": self.coach.partner_id.id,
|
||||
"role": "coach",
|
||||
}
|
||||
)
|
||||
.partner_id
|
||||
)
|
||||
return team2, therapist, coach
|
||||
Loading…
Reference in a new issue