From d92c124f50c0afe11a14b51f49ca64f0cb30e4e8 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Sat, 21 Oct 2023 21:32:26 -0400 Subject: [PATCH] bemade_sports_clinic: Lots of work on models and views for second iteration. --- bemade_sports_clinic/__manifest__.py | 2 +- .../controllers/team_staff_portal.py | 1 - .../data/demo/sports_clinic_demo_data.xml | 92 ++++++++++--- bemade_sports_clinic/models/__init__.py | 1 + bemade_sports_clinic/models/patient.py | 114 +++++++---------- bemade_sports_clinic/models/res_partner.py | 53 ++------ bemade_sports_clinic/models/sports_team.py | 76 +++++++++++ .../security/ir.model.access.csv | 7 +- bemade_sports_clinic/views/patient_views.xml | 30 ++++- .../views/res_partner_views.xml | 39 ------ .../views/sports_clinic_menus.xml | 27 +++- .../views/sports_team_views.xml | 121 ++++++++++++++++++ 12 files changed, 383 insertions(+), 180 deletions(-) create mode 100644 bemade_sports_clinic/models/sports_team.py delete mode 100644 bemade_sports_clinic/views/res_partner_views.xml create mode 100644 bemade_sports_clinic/views/sports_team_views.xml diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py index c8ed6e3..39dd9f2 100644 --- a/bemade_sports_clinic/__manifest__.py +++ b/bemade_sports_clinic/__manifest__.py @@ -42,7 +42,7 @@ 'security/sports_clinic_groups.xml', 'security/ir.model.access.csv', 'security/sports_clinic_rules.xml', - 'views/res_partner_views.xml', + 'views/sports_team_views.xml', 'views/sports_clinic_menus.xml', 'views/patient_views.xml', 'views/sports_clinic_portal_views.xml', diff --git a/bemade_sports_clinic/controllers/team_staff_portal.py b/bemade_sports_clinic/controllers/team_staff_portal.py index 733de27..566fc0f 100644 --- a/bemade_sports_clinic/controllers/team_staff_portal.py +++ b/bemade_sports_clinic/controllers/team_staff_portal.py @@ -19,7 +19,6 @@ class TeamStaffPortal(CustomerPortal): partner = http.request.env.user.partner_id return [ ('staff_partner_ids', 'in', partner.id), - ('type', '=', 'team'), ] @classmethod diff --git a/bemade_sports_clinic/data/demo/sports_clinic_demo_data.xml b/bemade_sports_clinic/data/demo/sports_clinic_demo_data.xml index 48acbc1..c65e93d 100644 --- a/bemade_sports_clinic/data/demo/sports_clinic_demo_data.xml +++ b/bemade_sports_clinic/data/demo/sports_clinic_demo_data.xml @@ -19,19 +19,39 @@ +1 (514) 343-6111 https://www.umontreal.ca - + Carabins (Football masculin) - team https://carabins.umontreal.ca/football + + Jean U. DeMontreal + jean.u.demontreal@carabins.umontreal.ca + + + + + + head_coach + + + Stretch Carabinier + streth.carabinier@carabins.umontreal.ca + + + + + + head_trainer + Jean Carabin 2001-08-23 jean.carabin@edudiants.umontreal.ca - match + no + no Rémi @@ -39,21 +59,32 @@ 2002-05-17 remi.lafracture@edudiants.umontreal.ca - practice + yes + yes - + Carabins (Hockey féminin) - team https://carabins.umontreal.ca/football + + + + head_coach + + + + + head_trainer + Jeanne Carabine 2001-08-23 jeanne.carabine@edudiants.umontreal.ca - match + no + no_contact Rimée @@ -61,7 +92,8 @@ 2004-11-22 rimee.lafracture@edudiants.umontreal.ca - practice + yes + yes Université Concordia @@ -72,9 +104,8 @@ H3G 1M8 https://www.concordia.ca - + Stingers (Hockey masculin) - team https://stingers.ca/mhockey/ @@ -84,7 +115,8 @@ 2003-11-02 jo_stinger@encs.concordia.ca - match + yes + yes Jimmy @@ -92,12 +124,13 @@ 2001-01-05 jim_bee@jmsb.concordia.ca - match + yes + yes 2023-05-17 18:53:00 Fracture du fémur - 2024-02-01 + 2024-02-01 @@ -105,7 +138,7 @@ 2022-03-17 19:13:00 Entorse cheville droite - 2022-03-23 + 2022-03-23 @@ -113,15 +146,36 @@ 2023-10-15 19:55:00 Commotion cérébrale mineure - 2023-11-01 + 2023-11-01 - - - Coach - + + + + head_coach + + + + trainer + trainer + + + + + coach + coach + + + + + + + + + + \ No newline at end of file diff --git a/bemade_sports_clinic/models/__init__.py b/bemade_sports_clinic/models/__init__.py index 06a6a51..150c09b 100644 --- a/bemade_sports_clinic/models/__init__.py +++ b/bemade_sports_clinic/models/__init__.py @@ -1,2 +1,3 @@ from . import patient from . import res_partner +from . import sports_team diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index 0949a2e..1eec551 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -1,6 +1,6 @@ from odoo import models, fields, _, api from odoo.exceptions import ValidationError -from datetime import date +from datetime import date, datetime from dateutil.relativedelta import relativedelta @@ -9,46 +9,46 @@ class Patient(models.Model): _description = "Patient at a sports medicine clinic." _inherit = ['mail.thread', 'mail.activity.mixin'] - first_name = fields.Char(required=True) - last_name = fields.Char(required=True) + first_name = fields.Char(required=True, tracking=True) + last_name = fields.Char(required=True, tracking=True) name = fields.Char(compute="_compute_name") date_of_birth = fields.Date( - groups="bemade_sports_clinic.group_sports_clinic_treatment_professional") + 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") + groups="bemade_sports_clinic.group_sports_clinic_treatment_professional", + tracking=True) phone = fields.Char(unaccent=False, - groups="bemade_sports_clinic.group_sports_clinic_user") - email = fields.Char(groups="bemade_sports_clinic.group_sports_clinic_user") - 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='res.partner', + groups="bemade_sports_clinic.group_sports_clinic_user", + tracking=True) + email = fields.Char(groups="bemade_sports_clinic.group_sports_clinic_user", + tracking=True) + contact_ids = fields.Many2many(comodel_name='sports.patient.contact', + relation="patient_contact_rel", + column1="patient_id", + column2="contact_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', - domain=[('type', '=', 'team')]) - player_status = fields.Selection([ - ('practice', 'Practice'), - ('match', 'Match'), - ]) + 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', ) - predicted_return_date = fields.Date(compute='_compute_predicted_return_date', ) - is_injured = fields.Boolean(compute='_compute_is_injured') injured_since = fields.Date(compute='_compute_is_injured') - - # authorized_users = fields.One2many(compute='_compute_authorized_users', store=True) - # - # @api.depends('injury_ids.treatment_professional_ids', 'team_ids.staff_team_ids', - # 'message_follower_ids') - # def _compute_authorized_users(self): - # for rec in self: - # auth_users = self.env['res.users'].search(['|', ('partner_id', 'in', - # rec.injury_ids.mapped('treatment_professional_ids').ids), - # '|', ('partner_id', 'in', rec.team_ids.mapped('staff_team_ids').ids), - # ('partner_id', 'in', rec.message_follower_ids.ids)]) + predicted_return_date = fields.Date() + is_injured = fields.Boolean(compute="_compute_is_injured") @api.depends('date_of_birth') def _compute_age(self): @@ -58,35 +58,16 @@ class Patient(models.Model): else: rec.age = relativedelta(date.today(), rec.date_of_birth).years - @api.depends('injury_ids.predicted_return_date') - def _compute_predicted_return_date(self): - for rec in self: - ongoing_injuries = rec.injury_ids.filtered( - lambda r: r.predicted_return_date > date.today()).sorted( - 'predicted_return_date') - if ongoing_injuries: - rec.predicted_return_date = ongoing_injuries[-1]. \ - predicted_return_date - else: - rec.predicted_return_date = False - @api.depends('first_name', 'last_name') def _compute_name(self): for rec in self: rec.name = ((rec.first_name or "") + " " + (rec.last_name or "")).strip() - @api.depends('injury_ids.is_resolved') + @api.depends('practice_status', 'match_status') def _compute_is_injured(self): - injured = self.filtered( - lambda r: r.injury_ids.filtered(lambda i: not i.is_resolved)) - for rec in injured: - rec.is_injured = True - rec.injured_since = rec.injury_ids.sorted('injury_date_time')[ - -1].injury_date_time.date() - for rec in self - injured: - rec.is_injured = False - rec.injured_since = False + for rec in self: + rec.is_injured = rec.practice_status != 'yes' or rec.match_status != 'yes' class PatientContact(models.Model): @@ -99,9 +80,13 @@ class PatientContact(models.Model): ('Mother', 'mother'), ('Father', 'father'), ('other', 'Other'), - ]) - phone = fields.Char(unaccent=False) - patient_id = fields.Many2one(comodel_name='sports.patient', string="Patient") + ], required=True) + phone = fields.Char(unaccent=False, required=True) + patient_id = fields.Many2many(comodel_name='sports.patient', string="Patient", + relation="patient_contact_rel", + column1="contact_id", column2="patient_id", + groups="bemade_sports_clinic.group_sports_clinic_user", + required=True) class PatientInjury(models.Model): @@ -111,10 +96,12 @@ class PatientInjury(models.Model): patient_id = fields.Many2one(comodel_name='sports.patient', string="Patient", - readonly=True) + readonly=True, + required=True) patient_name = fields.Char(related="patient_id.name") diagnosis = fields.Char(tracking=True) - injury_date_time = fields.Datetime(string='Date and Time of Injury') + injury_date_time = fields.Datetime(string='Date and Time of Injury', required=True, + default=datetime.now()) internal_notes = fields.Html(tracking=True) treatment_professional_ids = fields.Many2many(comodel_name='res.partner', relation='patient_injury_treatment_pro_rel', @@ -123,9 +110,9 @@ class PatientInjury(models.Model): string='Treatment Professionals', domain=[ ('is_treatment_professional', '=', - True)], ) - predicted_return_date = fields.Date(tracking=True) - is_resolved = fields.Boolean(compute="_compute_is_resolved") + True)], tracking=True) + predicted_resolution_date = fields.Date(tracking=True) + is_resolved = fields.Boolean(tracking=True, required=True, default=False) def write(self, vals): super().write(vals) @@ -142,10 +129,3 @@ class PatientInjury(models.Model): - rec.message_follower_ids.mapped('partner_id')) rec.message_subscribe(to_subscribe.ids) return res - - @api.depends('predicted_return_date') - def _compute_is_resolved(self): - to_compute = self.filtered(lambda r: r.predicted_return_date) - for rec in to_compute: - rec.is_resolved = rec.predicted_return_date < date.today() - (self - to_compute).is_resolved = False diff --git a/bemade_sports_clinic/models/res_partner.py b/bemade_sports_clinic/models/res_partner.py index 239af87..1481b4a 100644 --- a/bemade_sports_clinic/models/res_partner.py +++ b/bemade_sports_clinic/models/res_partner.py @@ -5,45 +5,16 @@ from odoo.exceptions import UserError class Partner(models.Model): _inherit = 'res.partner' - patient_ids = fields.Many2many(comodel_name='sports.patient', - relation='sports_team_patient_rel', - column1='team_id', - column2='patient_id', - string='Players') - player_count = fields.Integer(compute="_compute_player_counts") - injured_count = fields.Integer(compute="_compute_player_counts") - type = fields.Selection(selection_add=[('team', 'Sports Team'), ]) - staff_partner_ids = fields.Many2many(comodel_name='res.partner', - relation='sports_team_staff_partner_rel', - column1='team_id', - column2='staff_partner_id', - string='Staff') - staff_team_ids = fields.Many2many(comodel_name='res.partner', - relation='sports_team_staff_partner_rel', - column1='staff_partner_id', - column2='team_id', - string='Teams') - is_treatment_professional = fields.Boolean( - compute="_compute_is_treatment_professional",) - - def write(self, vals): - teams = self.filtered(lambda r: r.type == 'team') - if teams and 'type' in vals and vals['type'] != 'team': - if teams.mapped('staff_partner_ids') or teams.mapped('patient_ids'): - raise UserError(_('Sports team is related to patients and/or staff. ' - 'Its type cannot be changed until these relations are ' - 'removed.')) - return super().write(vals) - - def unlink(self): - teams = self.filtered(lambda r: r.type == 'team') - if teams: - if teams.mapped('staff_partner_ids') or teams.mapped('patient_ids'): - raise UserError(_('Sports team is related to patients and/or staff. ' - 'Its type cannot be deleted until these relations are ' - 'removed. You may try archiving it instead.')) - return super().unlink() + compute="_compute_is_treatment_professional", ) + 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.') @api.depends('user_ids.groups_id') def _compute_is_treatment_professional(self): @@ -54,9 +25,3 @@ class Partner(models.Model): 'bemade_sports_clinic.group_sports_clinic_treatment_professional') for rec in user_partners: rec.is_treatment_professional = group in rec.user_ids.groups_id - - @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)) diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py new file mode 100644 index 0000000..a929006 --- /dev/null +++ b/bemade_sports_clinic/models/sports_team.py @@ -0,0 +1,76 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError + + +class SportsTeam(models.Model): + _name = "sports.team" + _description = "Sports Team" + + name = fields.Char() + patient_ids = fields.Many2many(comodel_name='sports.patient', + relation='sports_team_patient_rel', + column1='team_id', + column2='patient_id', + string='Players') + 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') + staff_ids = fields.One2many(comodel_name='sports.team.staff', + inverse_name='team_id') + head_coach_id = fields.Many2one(comodel_name='res.partner', + compute='_compute_head_coach') + head_coach_name = fields.Char(related='head_coach_id.name') + head_trainer_id = fields.Many2one(comodel_name='res.partner', + compute='_compute_head_trainer') + head_trainer_name = fields.Char(related='head_trainer_id.name') + website = fields.Char() + + @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') + def _compute_head_coach(self): + for rec in self: + 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') + def _compute_head_trainer(self): + for rec in self: + staff = rec.staff_ids.filtered(lambda r: r.role == 'head_trainer') + rec.head_trainer_id = staff.partner_id if staff else False + + +class TeamStaff(models.Model): + _name = "sports.team.staff" + _description = "Relationship between staff members and their teams." + + 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) + role = fields.Selection(selection=[ + ('head_coach', 'Head Coach'), + ('head_trainer', 'Head Trainer'), + ('coach', 'Coach'), + ('trainer', 'Trainer'), + ('other', 'Other') + ], required=True) + + _sql_constraints = [('team_staff_unique', 'unique(team_id, partner_id)', + 'Each partner can only be related to a given team once.')] + + @api.constrains('role') + def _constrain_role(self): + teams = self.mapped('team_id') + for team in teams: + 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_trainer')) > 1: + raise ValidationError(_("A team can have only one head trainer.")) diff --git a/bemade_sports_clinic/security/ir.model.access.csv b/bemade_sports_clinic/security/ir.model.access.csv index 2b76d10..793f2d9 100644 --- a/bemade_sports_clinic/security/ir.model.access.csv +++ b/bemade_sports_clinic/security/ir.model.access.csv @@ -5,4 +5,9 @@ access_patient_admin,Admin Access for Patients,model_sports_patient,group_sports access_patient_portal,Portal Access for Patients,model_sports_patient,base.group_portal,1,1,1,0 access_patient_contact_user,User Access for Patient Contacts,model_sports_patient_contact,group_sports_clinic_user,1,1,1,1 access_injury_treatment_pro,Treatment Professional Access for Injuries,model_sports_patient_injury,group_sports_clinic_treatment_professional,1,1,1,1 -access_injury_portal,Portal Access for Injuries,model_sports_patient_injury,base.group_portal,1,0,0,0 \ No newline at end of file +access_injury_portal,Portal Access for Injuries,model_sports_patient_injury,base.group_portal,1,0,0,0 +access_team_user,User Access for Teams,model_sports_team,group_sports_clinic_user,1,1,1,0 +access_team_admin,Admin Access for Teams,model_sports_team,group_sports_clinic_admin,1,1,1,1 +access_team_portal,Portal Access for Teams,model_sports_team,base.group_portal,1,0,0,0 +access_team_staff_user,User Access for Team Staff,model_sports_team_staff,group_sports_clinic_user,1,1,1,1 +access_team_staff_portal,Portal Access for Team Staff,model_sports_team_staff,base.group_portal,1,0,0,0 diff --git a/bemade_sports_clinic/views/patient_views.xml b/bemade_sports_clinic/views/patient_views.xml index b237c28..763cf25 100644 --- a/bemade_sports_clinic/views/patient_views.xml +++ b/bemade_sports_clinic/views/patient_views.xml @@ -26,6 +26,8 @@ + + @@ -50,16 +52,33 @@ - + + + + + + + + + + + + +
+ + + +
@@ -80,9 +99,14 @@ - + +
+ + + +
@@ -93,7 +117,7 @@ - + diff --git a/bemade_sports_clinic/views/res_partner_views.xml b/bemade_sports_clinic/views/res_partner_views.xml deleted file mode 100644 index 58a8952..0000000 --- a/bemade_sports_clinic/views/res_partner_views.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - teams.view.search - res.partner - - - - - - - - - - res.partner.view.form.inherit - - res.partner - - - - - - - - - - - - - - - - - - - - diff --git a/bemade_sports_clinic/views/sports_clinic_menus.xml b/bemade_sports_clinic/views/sports_clinic_menus.xml index 8162657..e369bdc 100644 --- a/bemade_sports_clinic/views/sports_clinic_menus.xml +++ b/bemade_sports_clinic/views/sports_clinic_menus.xml @@ -3,21 +3,38 @@ Sports Teams - res.partner - [('type', '=', 'team')] - list,form + sports.team + kanban,tree,form + + + kanban + + + + + + tree + + + + + + form + + + Organizations res.partner - list,kanban,form + tree,kanban,form [('is_company','=', True)] Patients sports.patient - list,kanban,form + tree,kanban,form + + + + teams.view.search + sports.team + + + + + + + + + + + + + + sports.team.view.kanban + sports.team + + + + + + + + + + +
+
+ + + +
+
+ + + +
+
+ Head coach: +
+
+ Head trainer: +
+
+
+ Healthy: + + +
+
+ + Injured: + + +
+
+
+
+
+
+
+
+ + sports.team.view.list + sports.team + + + + + + + + + + + + + sports.team.view.form + sports.team + +
+
+ +
+

+ + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+