diff --git a/bemade_sports_clinic/controllers/team_staff_portal.py b/bemade_sports_clinic/controllers/team_staff_portal.py index a373f74..ad225ba 100644 --- a/bemade_sports_clinic/controllers/team_staff_portal.py +++ b/bemade_sports_clinic/controllers/team_staff_portal.py @@ -28,14 +28,14 @@ class TeamStaffPortal(CustomerPortal): ('team_ids', 'in', team_ids), ] - @http.route(route=['/my/teams'], type='http', auth='user', website=True) + @http.route(route=['/my/teams', '/my/teams/page/'], type='http', auth='user', website=True) def view_teams(self, page=0, **kw): """ Display the list of teams that a portal user has access to """ Teams = http.request.env['sports.team'] domain = self._prepare_teams_domain() teams_count = Teams.search_count(domain) pgr = pager(url='/my/teams', total=teams_count, - page=page, step=10, scope=10) + page=page, step=10, scope=5) teams = http.request.env['sports.team'].search(self._prepare_teams_domain(), offset=pgr['offset'], limit=teams_count) @@ -47,7 +47,7 @@ class TeamStaffPortal(CustomerPortal): 'page_name': 'my_teams', }) - @http.route(route=['/my/team/'], type='http', auth='user', website=True) + @http.route(route=['/my/team/', '/my/team//page/'], type='http', auth='user', website=True) def view_team(self, team_id, page=0, **kw): """ Display the information for a team including its list of players """ team = http.request.env['sports.team'].browse(team_id) @@ -55,7 +55,7 @@ class TeamStaffPortal(CustomerPortal): raise UserError(_('This team could not be found.')) players_count = team.player_count pgr = pager(url=f'/my/team/{team_id}', total=players_count, page=page, step=10, - scope=10) + scope=5) players = http.request.env['sports.patient'].search([ ('team_ids', 'in', team_id), ], offset=pgr['offset'], limit=players_count) @@ -70,13 +70,13 @@ class TeamStaffPortal(CustomerPortal): } ) - @http.route(route=['/my/players'], type='http', auth='user', website=True) + @http.route(route=['/my/players', '/my/players/page/'], type='http', auth='user', website=True) def view_players(self, page=0, **kw): """ Display the list of players that the portal user has access to """ teams_domain = self._prepare_teams_domain() players_domain = self._prepare_players_domain(teams_domain) players_count = http.request.env['sports.patient'].search_count(players_domain) - pgr = pager(url='/my/players', total=players_count, page=page, step=10, scope=10) + pgr = pager(url='/my/players', total=players_count, page=page, step=10, scope=5) players = http.request.env['sports.patient'].search(players_domain, offset=pgr['offset'], limit=players_count) 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 96af970..c148dea 100644 --- a/bemade_sports_clinic/data/demo/sports_clinic_demo_data.xml +++ b/bemade_sports_clinic/data/demo/sports_clinic_demo_data.xml @@ -130,7 +130,7 @@ yes - 2023-05-17 18:53:00 + 2023-05-17 Fracture du fémur 2024-02-01 @@ -138,7 +138,7 @@ eval="[Command.set([ref('base.user_admin')])]"/> - 2022-03-17 19:13:00 + 2022-03-17 Entorse cheville droite 2022-03-23 @@ -146,7 +146,7 @@ eval="[Command.set([ref('base.user_admin')])]"/> - 2023-10-15 19:55:00 + 2023-10-15 Commotion cérébrale mineure 2023-11-01 diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index 02b41a6..47c99c2 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -1,7 +1,8 @@ from odoo import models, fields, _, api, Command from odoo.exceptions import ValidationError -from datetime import date, datetime +from datetime import date from dateutil.relativedelta import relativedelta +from odoo.addons.phone_validation.tools import phone_validation class Patient(models.Model): @@ -10,20 +11,28 @@ class Patient(models.Model): _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(compute="_compute_name") + 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", tracking=True) - mobile = fields.Char(unaccent=False, - 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.One2many(comodel_name='sports.patient.contact', inverse_name='patient_id', string='Patient Contacts', @@ -56,6 +65,8 @@ class Patient(models.Model): compute='_compute_stage') last_consultation_date = fields.Date() active_injury_count = fields.Integer(compute='_compute_active_injury_count') + allergies = fields.Text() + team_info_notes = fields.Html(string="Notes") def default_get(self, fields_list): res = super().default_get(fields_list) @@ -67,6 +78,15 @@ class Patient(models.Model): 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: @@ -109,16 +129,19 @@ class Patient(models.Model): @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() + rec.name = self._get_name_from_first_and_last(rec.first_name, rec.last_name) - @api.depends('practice_status', 'match_status', 'injury_ids.injury_date_time') + @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_time + rec.injured_since = unresolved_injuries and unresolved_injuries[0].injury_date else: rec.injured_since = False @@ -142,6 +165,28 @@ class Patient(models.Model): '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 + ) + class PatientContact(models.Model): _name = 'sports.patient.contact' @@ -157,6 +202,23 @@ class PatientContact(models.Model): mobile = fields.Char(unaccent=False, required=True) patient_id = fields.Many2one(comodel_name='sports.patient', string='Patient') + @api.onchange('mobile') + def _onchange_mobile_validation(self): + if self.mobile: + self.mobile = self._phone_format(self.mobile, force_format="INTERNATIONAL") + + def _phone_format(self, number, force_format='E164'): + country = self.patient_id.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 + ) + class PatientInjury(models.Model): _name = 'sports.patient.injury' @@ -170,15 +232,15 @@ class PatientInjury(models.Model): 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', required=True, - default=datetime.now()) + injury_date = fields.Date(string='Date and Time of Injury', + default=date.today()) + injury_date_na = fields.Boolean(string="N/A", default=False) internal_notes = fields.Html(tracking=True) external_notes = fields.Html(tracking=True) treatment_professional_ids = fields.Many2many(comodel_name='res.users', relation='patient_injury_treatment_pro_rel', column1='patient_injury_id', - column2='treatment_pro_id', - string='Treatment Professionals', + column2='treatment_pro_id', string='Treatment Professionals', domain=[ ('is_treatment_professional', '=', True)], tracking=True) @@ -187,6 +249,24 @@ class PatientInjury(models.Model): help="The date when the injury was actually resolved.") stage = fields.Selection(selection=[('active', 'Active'), ('resolved', 'Resolved')], compute='_compute_stage') + @api.constrains('injury_date_na', 'injury_date') + def constrain_date_blank_only_if_na(self): + for rec in self: + if not rec.injury_date_na and not rec.injury_date: + raise ValidationError(_("If injury date is not set, the N/A box must be checked.")) + + @api.onchange('injury_date_na') + def _onchange_injury_date_na(self): + for rec in self: + if rec.injury_date_na: + rec.injury_date = None + + @api.onchange('injury_date') + def _onchange_injury_date(self): + for rec in self: + if rec.injury_date: + rec.injury_date_na = False + @api.depends('resolution_date') def _compute_stage(self): for rec in self: diff --git a/bemade_sports_clinic/models/res_partner.py b/bemade_sports_clinic/models/res_partner.py index 68f2e5b..6ff7a0d 100644 --- a/bemade_sports_clinic/models/res_partner.py +++ b/bemade_sports_clinic/models/res_partner.py @@ -1,5 +1,6 @@ from odoo import models, fields, api, _ from odoo.exceptions import UserError +from odoo.exceptions import ValidationError class Partner(models.Model): @@ -14,6 +15,12 @@ class Partner(models.Model): 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') + + 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.")) + return super().write(vals) @api.depends('team_staff_rel_ids.team_id') def _compute_teams_served(self): diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index 46b0fd8..b2f23bf 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -75,6 +75,7 @@ class TeamStaff(models.Model): _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') diff --git a/bemade_sports_clinic/views/sports_patient_views.xml b/bemade_sports_clinic/views/sports_patient_views.xml index 86de288..6157357 100644 --- a/bemade_sports_clinic/views/sports_patient_views.xml +++ b/bemade_sports_clinic/views/sports_patient_views.xml @@ -82,11 +82,11 @@