diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py index 105f751..b4fcf89 100644 --- a/bemade_sports_clinic/__manifest__.py +++ b/bemade_sports_clinic/__manifest__.py @@ -18,7 +18,7 @@ # { 'name': 'Sports Clinic Management', - 'version': '16.0.1.5.6', + 'version': '16.0.1.5.7', 'summary': 'Manage the patients of a sports medicine clinic.', 'description': """ Adds the notion of sports teams, players (patients), coaches and treatment diff --git a/bemade_sports_clinic/data/sports_clinic_data.xml b/bemade_sports_clinic/data/sports_clinic_data.xml index af83b45..ce38047 100644 --- a/bemade_sports_clinic/data/sports_clinic_data.xml +++ b/bemade_sports_clinic/data/sports_clinic_data.xml @@ -1,23 +1,87 @@ - - Patient File Update - sports.patient + + Patient File Update (External) + sports.patient.injury - Patient's file has been updated. + + + + Patient File Update (Internal) + sports.patient.injury + + + + - - Patient Injury Status Update - sports.patient.injury + + Patient Status Update + + Patient Injury Status Update for {{ object.patient_name }} + +

An update has been posted to the injury record for 's injurywith + the diagnosis of . Click + here to view the injury details. +

+
+
+ + Patient Status Update + + Internal Note Updated for Patient {{ object.patient_name }} + +

A new internal note has been posted to the injury record for 's injurywith + the diagnosis of . +

+
+

New Note:

+
+
+
+
+ + Patient File Update (External) + sports.patient - Patient's injury was updated. + + + + Patient File Update (Internal) + sports.patient + + + + + + + Patient Status Update + + Patient Status Update for {{ object.name}} + +

An update has been posted to the record for 's. Click + here to view the details. +

+
+
+ + Patient Status Update + + Internal Note Updated for Patient {{ object.name}} + +

A new internal note has been posted to the record for 's. +

+
+

New Note:

+
+
+
diff --git a/bemade_sports_clinic/migrations/16.0.1.5.7/post-migrate.py b/bemade_sports_clinic/migrations/16.0.1.5.7/post-migrate.py new file mode 100644 index 0000000..df76a53 --- /dev/null +++ b/bemade_sports_clinic/migrations/16.0.1.5.7/post-migrate.py @@ -0,0 +1,22 @@ +from odoo import api, SUPERUSER_ID, Command + + +def migrate(cr, version): + env = api.Environment(cr, SUPERUSER_ID, {}) + + patient_followers = env['mail.followers'].search([('res_model', '=', + 'sports.patient')]) + injury_followers = env['mail.followers'].search([('res_model', '=', + 'sports.patient.injury')]) + for f in patient_followers: + if f.partner_id.user_id.has_group('base.group_user'): + subtype = env.ref('bemade_sports_clinic.subtype_patient_internal_update').id + else: + subtype = env.ref('bemade_sports_clinic.subtype_patient_external_update').id + f.write({'subtype_ids': [Command.set([subtype])]}) + for f in injury_followers: + if f.partner_id.user_id.has_group('base.group_user'): + subtype = env.ref('bemade_sports_clinic.subtype_patient_injury_internal_update').id + else: + subtype = env.ref('bemade_sports_clinic.subtype_patient_injury_external_update').id + f.write({'subtype_ids': [Command.set([subtype])]}) diff --git a/bemade_sports_clinic/models/__init__.py b/bemade_sports_clinic/models/__init__.py index f71cd05..4664679 100644 --- a/bemade_sports_clinic/models/__init__.py +++ b/bemade_sports_clinic/models/__init__.py @@ -1,3 +1,4 @@ +from . import mail_followers from . import patient from . import patient_injury from . import patient_contact diff --git a/bemade_sports_clinic/models/mail_followers.py b/bemade_sports_clinic/models/mail_followers.py new file mode 100644 index 0000000..800cb67 --- /dev/null +++ b/bemade_sports_clinic/models/mail_followers.py @@ -0,0 +1,46 @@ +from odoo import models, fields, api, _ + + +class MailFollowers(models.Model): + _inherit = "mail.followers" + + # This is a user quality of life modification, to avoid duplicated message subtype subscription for internal + # and external notifications (since both subtypes are default=True). + + def write(self, vals): + super().write(vals) + subtypes_map = self.__subtypes_map() + if self.res_model in subtypes_map and 'subtype_ids' in vals: + internal_subtype, external_subtype = subtypes_map.get(self.res_model) + if external_subtype in self.subtype_ids and internal_subtype in self.subtype_ids: + self.subtype_ids = self.subtype_ids - external_subtype + + @api.model_create_multi + def create(self, vals_list): + recs = super().create(vals_list) + subtypes_map = self.__subtypes_map() + to_fix = { + model: + recs.filtered( + lambda rec: rec.res_model == model + and subtypes[0] in rec.subtype_ids + and subtypes[1] in rec.subtype_ids + ) for model, subtypes in subtypes_map.items() + } + for model, records in to_fix.items(): + for rec in records: + rec.subtype_ids = rec.subtype_ids - subtypes_map[model][1] + return recs + + def __subtypes_map(self): + xml_id_fmt_string = "bemade_sports_clinic.subtype_{0}_{1}_update" + return { + 'sports.patient': ( + self.env.ref(xml_id_fmt_string.format('patient', 'internal')), + self.env.ref(xml_id_fmt_string.format('patient', 'external')), + ), + 'sports.patient.injury': ( + self.env.ref(xml_id_fmt_string.format('patient_injury', 'internal')), + self.env.ref(xml_id_fmt_string.format('patient_injury', 'external')), + ), + } diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index acc3566..93e98de 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -3,6 +3,21 @@ 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): @@ -30,43 +45,68 @@ class Patient(models.Model): 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", - tracking=True) - 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 for easy expansion later - ('yes', 'Yes'), - ('no', 'No'), - ], required=True, default='yes', tracking=True) - practice_status = fields.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', ) + 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.") + 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')], + 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") + team_info_notes = fields.Html( + string="Notes", + tracking=True, + ) def default_get(self, fields_list): res = super().default_get(fields_list) @@ -187,20 +227,40 @@ class Patient(models.Model): raise_exception=False ) + @api.model + def __get_track_internal_external(self, params: Set[str]) -> Tuple[bool, bool]: + """ Based on the fields being changed, determine if the notification being sent is meant for external + or internal followers, or neither.""" + external = bool(external_tracking_fields & params) + internal = external or bool(internal_tracking_fields & params) + return external, internal + def _track_subtype(self, init_values): - # List of fields that should result in team staff notification - external_values = [ - "last_consultation_date", - "match_status", - "practice_status", - "predicted_return_date", - "return_date", - "external_notes", - ] - if any([v in init_values for v in external_values]): - return self.env.ref("bemade_sports_clinic.subtype_patient_update") + external, internal = self.__get_track_internal_external({key for key in init_values.keys()}) + + if external: + return self.env.ref('bemade_sports_clinic.subtype_patient_external_update') + elif internal: + return self.env.ref('bemade_sports_clinic.subtype_patient_internal_update') else: return self.env.ref('mail.mt_note') - - + def _track_template(self, changes): + res = super()._track_template(changes) + external, internal = self.__get_track_internal_external({change for change in changes}) + if external: + first_external_field = (external_tracking_fields & set(changes)).pop() + res[first_external_field] = ( + self.env.ref('bemade_sports_clinic.mail_template_patient_status_update'), { + 'auto_delete_message': False, + 'email_layout_xmlid': 'mail.mail_notification_light', + } + ) + if 'internal_notes' in changes: + res['team_info_notes'] = ( + self.env.ref('bemade_sports_clinic.mail_template_patient_new_internal_note'), { + 'auto_delete_message': False, + 'email_layout_xmlid': 'mail.mail_notification_light', + } + ) + return res diff --git a/bemade_sports_clinic/models/patient_injury.py b/bemade_sports_clinic/models/patient_injury.py index c1d32b2..c760dc1 100644 --- a/bemade_sports_clinic/models/patient_injury.py +++ b/bemade_sports_clinic/models/patient_injury.py @@ -2,6 +2,20 @@ from odoo import models, fields, api, _ from datetime import datetime, date import pytz from odoo.exceptions import ValidationError +from typing import Set, Tuple + +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): @@ -112,9 +126,40 @@ class PatientInjury(models.Model): 'context': self._context, } + @api.model + def __get_track_internal_external(self, params: Set[str]) -> Tuple[bool, bool]: + """ Based on the fields being changed, determine if the notification being sent is meant for external + or internal followers, or neither.""" + external = bool(external_tracking_fields & params) + internal = external or bool(internal_tracking_fields & params) + return external, internal + def _track_subtype(self, init_values): - if 'treatment_professional_ids' in init_values \ - and len(init_values) == 1: - return self.env.ref('mail.mt_note') + external, internal = self.__get_track_internal_external({key for key in init_values.keys()}) + + if external: + return self.env.ref('bemade_sports_clinic.subtype_patient_injury_external_update') + elif internal: + return self.env.ref('bemade_sports_clinic.subtype_patient_injury_internal_update') else: - return self.env.ref('bemade_sports_clinic.subtype_patient_injury_update') + return self.env.ref('mail.mt_note') + + def _track_template(self, changes): + res = super()._track_template(changes) + external, internal = self.__get_track_internal_external({change for change in changes}) + if external: + first_external_field = (external_tracking_fields & set(changes)).pop() + res[first_external_field] = ( + self.env.ref('bemade_sports_clinic.mail_template_patient_injury_status_update'), { + 'auto_delete_message': False, + '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_message': False, + 'email_layout_xmlid': 'mail.mail_notification_light', + } + ) + return res