From d6c40a456e2507b778f449552d09dc286656c407 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Mon, 6 Nov 2023 22:26:30 -0500 Subject: [PATCH] Completes iteration 4 without translations. --- bemade_sports_clinic/__manifest__.py | 2 +- .../controllers/team_staff_portal.py | 10 ++-- bemade_sports_clinic/models/patient.py | 46 +++++++++++++------ bemade_sports_clinic/models/sports_team.py | 29 ++++++++++-- .../security/sports_clinic_rules.xml | 3 +- .../views/sports_clinic_portal_views.xml | 17 ++++++- .../views/sports_patient_views.xml | 4 +- .../views/sports_team_views.xml | 16 ++++++- 8 files changed, 96 insertions(+), 31 deletions(-) diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py index ff37887..fc7addb 100644 --- a/bemade_sports_clinic/__manifest__.py +++ b/bemade_sports_clinic/__manifest__.py @@ -37,7 +37,7 @@ 'author': 'Bemade Inc.', 'website': 'https://www.bemade.org', 'license': 'OPL-1', - 'depends': ['base', 'mail', 'portal'], + 'depends': ['portal', 'contacts'], 'data': [ 'security/sports_clinic_groups.xml', 'security/ir.model.access.csv', diff --git a/bemade_sports_clinic/controllers/team_staff_portal.py b/bemade_sports_clinic/controllers/team_staff_portal.py index dcb5ec2..a373f74 100644 --- a/bemade_sports_clinic/controllers/team_staff_portal.py +++ b/bemade_sports_clinic/controllers/team_staff_portal.py @@ -88,10 +88,12 @@ class TeamStaffPortal(CustomerPortal): 'page_name': 'my_players', }) - @http.route(route=['/my/players/'], type='http', auth='user', website=True) - def view_player(self, player_id, **kw): + @http.route(route=['/my/players/', '/my/players//'], type='http', + auth='user', website=True) + def view_player(self, player_id, team_id=None,**kw): """ Display the active injuries for a given player. """ player = http.request.env['sports.patient'].browse(player_id) + team = team_id and http.request.env['sports.team'].browse(player_id) if not player: raise UserError(_('This player could not be found.')) injuries = player.injury_ids.filtered(lambda r: r.stage == 'active') @@ -100,6 +102,6 @@ class TeamStaffPortal(CustomerPortal): qcontext={ 'player': player, 'injuries': injuries, - 'page_name': 'my_player_injuries', + 'team': team } - ) \ No newline at end of file + ) diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index 1073ad6..fcb515b 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -57,6 +57,18 @@ class Patient(models.Model): last_consultation_date = fields.Date() active_injury_count = fields.Integer(compute='_compute_active_injury_count') + @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: @@ -64,13 +76,17 @@ class Patient(models.Model): @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 == 'yes' and rec.practice_status == 'yes': - rec.stage = 'healthy' - elif rec.match_status == 'no' and rec.practice_status == 'no_contact': - rec.stage = 'practice_ok' - else: - rec.stage = 'no_play' + 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): @@ -91,9 +107,8 @@ class Patient(models.Model): for rec in self: rec.is_injured = rec.practice_status != 'yes' or rec.match_status != 'yes' if rec.is_injured: - rec.injured_since = \ - rec.injury_ids and rec.injury_ids.filtered(lambda r: not r.stage == 'resolved').sorted( - 'injury_date_time')[0].injury_date_time + unresolved_injuries = rec.injury_ids.filtered(lambda r: not r.stage == 'resolved') + rec.injured_since = unresolved_injuries and unresolved_injuries[0].injury_date_time else: rec.injured_since = False @@ -160,14 +175,15 @@ class PatientInjury(models.Model): 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=[('active', 'Active'), ('resolved', 'Resolved')], tracking=True, required=True, - default='active', readonly=False) + stage = fields.Selection(selection=[('active', 'Active'), ('resolved', 'Resolved')], compute='_compute_stage') - @api.constrains('stage') - def constrain_stage_on_resolution_date(self): + @api.depends('resolution_date') + def _compute_stage(self): for rec in self: - if rec.stage == 'resolved' and not rec.resolution_date: - raise ValidationError(_('Cannot set an injury as resolved without setting the resolution date first.')) + if rec.resolution_date and rec.resolution_date <= date.today(): + rec.stage = 'resolved' + else: + rec.stage = 'active' def write(self, vals): super().write(vals) diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index d6c1d3c..44c4d7d 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api, _ +from odoo import models, fields, api, _, Command from odoo.exceptions import ValidationError @@ -56,7 +56,7 @@ class TeamStaff(models.Model): 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) + required=True, domain=[('is_company', '=', False)]) role = fields.Selection(selection=[ ('head_coach', 'Head Coach'), ('head_trainer', 'Head Trainer'), @@ -66,8 +66,11 @@ class TeamStaff(models.Model): ], required=True) phone = fields.Char(related='partner_id.phone', readonly=False) name = fields.Char(related='partner_id.name', readonly=False) - parent_id = fields.Many2one(related='partner_id.parent_id', readonly=False, string="Organization") + parent_id = fields.Many2one(related='partner_id.parent_id', readonly=False, string="Organization", + domain=[('is_company', '=', True)]) email = fields.Char(related='parent_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') _sql_constraints = [('team_staff_unique', 'unique(team_id, partner_id)', 'Each partner can only be related to a given team once.')] @@ -84,4 +87,22 @@ class TeamStaff(models.Model): @api.onchange('phone') def _onchange_phone_validation(self): if self.phone: - self.phone = self.partner_id._phone_format(self.phone, force_format='INTERNATIONAL') \ No newline at end of file + self.phone = self.partner_id._phone_format(self.phone, force_format='INTERNATIONAL') + + @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) + + def action_revoke_portal_access(self): + 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}) + # 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)]}) + return wiz._action_open_modal() diff --git a/bemade_sports_clinic/security/sports_clinic_rules.xml b/bemade_sports_clinic/security/sports_clinic_rules.xml index 5e28e76..41bf382 100644 --- a/bemade_sports_clinic/security/sports_clinic_rules.xml +++ b/bemade_sports_clinic/security/sports_clinic_rules.xml @@ -8,8 +8,7 @@ [('id', 'in', - user.partner_id.team_staff_rel_ids.mapped('team_id').mapped( - 'patient_ids').ids)] + user.partner_id.team_staff_rel_ids.team_id.patient_ids.ids)] diff --git a/bemade_sports_clinic/views/sports_clinic_portal_views.xml b/bemade_sports_clinic/views/sports_clinic_portal_views.xml index 7b5b5e2..27152b7 100644 --- a/bemade_sports_clinic/views/sports_clinic_portal_views.xml +++ b/bemade_sports_clinic/views/sports_clinic_portal_views.xml @@ -63,7 +63,7 @@ - + @@ -191,13 +191,26 @@ t-attf-href="/my/teams?{{ keep_query() }}">Teams Teams -
  • Players Players
  • + + + diff --git a/bemade_sports_clinic/views/sports_patient_views.xml b/bemade_sports_clinic/views/sports_patient_views.xml index 265235d..54676e6 100644 --- a/bemade_sports_clinic/views/sports_patient_views.xml +++ b/bemade_sports_clinic/views/sports_patient_views.xml @@ -68,7 +68,7 @@
    - +
    @@ -129,7 +129,7 @@
    - +
    diff --git a/bemade_sports_clinic/views/sports_team_views.xml b/bemade_sports_clinic/views/sports_team_views.xml index d67a20e..92ab3e2 100644 --- a/bemade_sports_clinic/views/sports_team_views.xml +++ b/bemade_sports_clinic/views/sports_team_views.xml @@ -107,6 +107,20 @@ + + +