From b0060ad56b68c5c8f62b4c74cf4add324e22ec3c Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Wed, 17 Apr 2024 16:43:01 -0400 Subject: [PATCH 01/21] bemade_sports_clinic: limit access rights --- bemade_sports_clinic/__manifest__.py | 3 +- .../migrations/16.0.1.5.8/post-migrate.py | 7 + .../models/patient_contact.py | 4 +- bemade_sports_clinic/models/res_users.py | 8 ++ bemade_sports_clinic/models/sports_team.py | 26 ++++ .../security/sports_clinic_rules.xml | 133 +++++++++++++++++- .../views/res_users_views.xml | 16 +++ .../views/sports_team_views.xml | 14 +- 8 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 bemade_sports_clinic/migrations/16.0.1.5.8/post-migrate.py create mode 100644 bemade_sports_clinic/views/res_users_views.xml diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py index b4fcf89..0f7aed8 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.7', + 'version': '16.0.1.5.8', 'summary': 'Manage the patients of a sports medicine clinic.', 'description': """ Adds the notion of sports teams, players (patients), coaches and treatment @@ -49,6 +49,7 @@ 'views/sports_patient_views.xml', 'views/sports_clinic_portal_views.xml', 'views/res_partner_views.xml', + 'views/res_users_views.xml', ], 'demo': ['data/demo/sports_clinic_demo_data.xml'], 'installable': True, diff --git a/bemade_sports_clinic/migrations/16.0.1.5.8/post-migrate.py b/bemade_sports_clinic/migrations/16.0.1.5.8/post-migrate.py new file mode 100644 index 0000000..818ad3b --- /dev/null +++ b/bemade_sports_clinic/migrations/16.0.1.5.8/post-migrate.py @@ -0,0 +1,7 @@ +from odoo import api, SUPERUSER_ID + + +def migrate(cr, version): + env = api.Environment(cr, SUPERUSER_ID, {}) + env['sports.team'].search([])._allow_access_for_staff_internal_users() + cr.execute('CREATE EXTENSION IF NOT EXISTS "unaccent"') diff --git a/bemade_sports_clinic/models/patient_contact.py b/bemade_sports_clinic/models/patient_contact.py index 36e70cc..90420b4 100644 --- a/bemade_sports_clinic/models/patient_contact.py +++ b/bemade_sports_clinic/models/patient_contact.py @@ -7,13 +7,13 @@ class PatientContact(models.Model): _description = "Emergency or other contacts for a patient." sequence = fields.Integer(required=True, default=0) - name = fields.Char(unaccent=False) + name = fields.Char(unaccent=True) contact_type = fields.Selection(selection=[ ('mother', 'Mother'), ('father', 'Father'), ('other', 'Other'), ], required=True) - mobile = fields.Char(unaccent=False, required=True) + mobile = fields.Char(unaccent=False) # TODO: add email here and on views patient_id = fields.Many2one(comodel_name='sports.patient', string='Patient') diff --git a/bemade_sports_clinic/models/res_users.py b/bemade_sports_clinic/models/res_users.py index 1dcd258..e522245 100644 --- a/bemade_sports_clinic/models/res_users.py +++ b/bemade_sports_clinic/models/res_users.py @@ -7,6 +7,14 @@ class User(models.Model): is_treatment_professional = fields.Boolean( compute="_compute_is_treatment_professional", store=True) + accessible_team_ids = fields.Many2many( + comodel_name="sports.team", + relation="sports_team_res_users_rel", + column1="user_id", + column2="team_id", + string="Accessible Sports Teams", + ) + @api.depends('groups_id') def _compute_is_treatment_professional(self): for rec in self: diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index 6f7d589..9856b72 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -28,6 +28,28 @@ class SportsTeam(models.Model): store=True) 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 ]] + ) + + def write(self, vals): + res = super().write(vals) + if 'staff_ids' in vals: + self._allow_access_for_staff_internal_users() + 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]: + rec._allow_access_for_staff_internal_users() + return res @api.depends('patient_ids.is_injured') def _compute_player_counts(self): @@ -48,6 +70,10 @@ class SportsTeam(models.Model): 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")) + class TeamStaff(models.Model): _name = "sports.team.staff" diff --git a/bemade_sports_clinic/security/sports_clinic_rules.xml b/bemade_sports_clinic/security/sports_clinic_rules.xml index 41bf382..78a97b1 100644 --- a/bemade_sports_clinic/security/sports_clinic_rules.xml +++ b/bemade_sports_clinic/security/sports_clinic_rules.xml @@ -2,13 +2,140 @@ - + Restrict Team Staff Access to Their Players Only + + + + - [('id', 'in', - user.partner_id.team_staff_rel_ids.team_id.patient_ids.ids)] + [('team_ids.staff_ids.user_ids', 'in', user.id)] + + + + Restrict Team Staff Access to Their Teams Only + + + + + + + + [('staff_ids.user_ids', 'in', user.id)] + + + + Restrict Team Staff Access to Their Teams Only + + + + + + + + [('patient_id.team_ids.staff_ids.user_ids', 'in', user.id)] + + + + Restrict Team Access to Allowed Internal Users + + + + + + + + [('allowed_user_ids', 'in', user.id)] + + + + Restrict Patient Access to Allowed Internal Users + + + + + + + + [('team_ids.allowed_user_ids', 'in', user.id)] + + + + Restrict Team Access to Allowed Internal Users + + + + + + + + [('patient_id.team_ids.allowed_user_ids', 'in', user.id)] + + + + Restrict Patient Contact Access to Allowed Internal Users + + + + + + + + [('patient_id.team_ids.allowed_user_ids', 'in', user.id)] + + + + Allow Team Access to Administrators + + + + + + + + [(1, '=', 1)] + + + + Allow Patient Access to Administrators + + + + + + + + [(1, '=', 1)] + + + + Allow Injury Access to Administrators + + + + + + + + [(1, '=', 1)] + + + + Allow Sports Patient Contact Access to Administrators + + + + + + + + [(1, '=', 1)] diff --git a/bemade_sports_clinic/views/res_users_views.xml b/bemade_sports_clinic/views/res_users_views.xml new file mode 100644 index 0000000..cbef072 --- /dev/null +++ b/bemade_sports_clinic/views/res_users_views.xml @@ -0,0 +1,16 @@ + + + + view.users.form.inherit + + res.users + + + + + + + + + \ No newline at end of file diff --git a/bemade_sports_clinic/views/sports_team_views.xml b/bemade_sports_clinic/views/sports_team_views.xml index 50ccca2..988fe2c 100644 --- a/bemade_sports_clinic/views/sports_team_views.xml +++ b/bemade_sports_clinic/views/sports_team_views.xml @@ -125,9 +125,17 @@ - - + + + + + + + + From 17b971f410b929cba4c6fa018d4942e36ac958cc Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 30 Apr 2024 11:32:49 -0400 Subject: [PATCH 02/21] fix issues with access rules on teams in controller and portal views --- bemade_sports_clinic/__manifest__.py | 2 +- bemade_sports_clinic/controllers/team_staff_portal.py | 3 +-- bemade_sports_clinic/views/sports_clinic_portal_views.xml | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py index 0f7aed8..bcaa960 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.8', + 'version': '16.0.1.5.9', '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/controllers/team_staff_portal.py b/bemade_sports_clinic/controllers/team_staff_portal.py index 0487317..fc3797e 100644 --- a/bemade_sports_clinic/controllers/team_staff_portal.py +++ b/bemade_sports_clinic/controllers/team_staff_portal.py @@ -16,9 +16,8 @@ class TeamStaffPortal(CustomerPortal): @classmethod def _prepare_teams_domain(cls): user = http.request.env.user - partner = http.request.env.user.partner_id return [ - ('staff_ids', 'in', partner.team_staff_rel_ids.ids), + ('staff_ids.user_ids', 'in', user.id), ] @classmethod diff --git a/bemade_sports_clinic/views/sports_clinic_portal_views.xml b/bemade_sports_clinic/views/sports_clinic_portal_views.xml index 63d0cb7..cca1435 100644 --- a/bemade_sports_clinic/views/sports_clinic_portal_views.xml +++ b/bemade_sports_clinic/views/sports_clinic_portal_views.xml @@ -128,7 +128,7 @@
    - +
  • From 9fb7606695e53f81bffc521ca0affe8ee16650d1 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Mon, 3 Jun 2024 19:16:13 -0400 Subject: [PATCH 03/21] add more tracking to sports.team --- bemade_sports_clinic/__manifest__.py | 2 +- bemade_sports_clinic/models/sports_team.py | 47 ++++++++++++++-------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py index bcaa960..42eb42b 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.9', + 'version': '16.0.1.5.10', '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/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index 9856b72..11e7225 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -5,27 +5,42 @@ from odoo.exceptions import ValidationError class SportsTeam(models.Model): _name = "sports.team" _description = "Sports Team" + _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') + patient_ids = fields.Many2many( + 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') - 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', - store=True) + parent_id = fields.Many2one( + comodel_name='res.partner', + string='Parent Organization', + ondelete='restrict', + tracking=True, + ) + staff_ids = fields.One2many( + comodel_name='sports.team.staff', + inverse_name='team_id', + tracking=True, + ) + head_coach_id = fields.Many2one( + comodel_name='res.partner', + compute='_compute_head_coach', + store=True, + ) head_coach_name = fields.Char(related='head_coach_id.name') - head_therapist_id = fields.Many2one(comodel_name='res.partner', - compute='_compute_head_therapist', - store=True) + head_therapist_id = fields.Many2one( + comodel_name='res.partner', + compute='_compute_head_therapist', + store=True, + ) head_therapist_name = fields.Char(related='head_therapist_id.name') website = fields.Char() allowed_user_ids = fields.Many2many( @@ -34,7 +49,7 @@ class SportsTeam(models.Model): column1='team_id', column2='user_id', string='Allowed Users', - domain=lambda self: [['groups_id', 'in', self.env.ref("base.group_user").ids ]] + domain=lambda self: [['groups_id', 'in', self.env.ref("base.group_user").ids]], ) def write(self, vals): From 6912a6ef8a54db4670fd81cc78f3ccdd2796c135 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Mon, 3 Jun 2024 19:17:52 -0400 Subject: [PATCH 04/21] add more tracking to sports.team --- bemade_sports_clinic/views/sports_team_views.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bemade_sports_clinic/views/sports_team_views.xml b/bemade_sports_clinic/views/sports_team_views.xml index 988fe2c..d5c5cd5 100644 --- a/bemade_sports_clinic/views/sports_team_views.xml +++ b/bemade_sports_clinic/views/sports_team_views.xml @@ -158,6 +158,11 @@ +
    + + + +
    From 418d03d3a904ddfd405416597d4ac071fb24470e Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Mon, 3 Jun 2024 19:19:55 -0400 Subject: [PATCH 05/21] add more tracking to sports.team --- bemade_sports_clinic/views/sports_team_views.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bemade_sports_clinic/views/sports_team_views.xml b/bemade_sports_clinic/views/sports_team_views.xml index d5c5cd5..94800c5 100644 --- a/bemade_sports_clinic/views/sports_team_views.xml +++ b/bemade_sports_clinic/views/sports_team_views.xml @@ -137,6 +137,11 @@ +
    + + + +
    @@ -158,11 +163,6 @@ -
    - - - -
    From cc862c56cbff1a092dfadb1d5a5c7baabbe82c3a Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Mon, 3 Jun 2024 20:14:24 -0400 Subject: [PATCH 06/21] add some debug logging to sports.patient.injury --- bemade_sports_clinic/models/patient_injury.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bemade_sports_clinic/models/patient_injury.py b/bemade_sports_clinic/models/patient_injury.py index f3c20c5..c05e5bf 100644 --- a/bemade_sports_clinic/models/patient_injury.py +++ b/bemade_sports_clinic/models/patient_injury.py @@ -2,7 +2,9 @@ from odoo import models, fields, api, _ from datetime import datetime, date import pytz from odoo.exceptions import ValidationError -from typing import Set, Tuple +import logging + +_logger = logging.getLogger(__name__) external_tracking_fields = { 'diagnosis', @@ -113,7 +115,9 @@ class PatientInjury(models.Model): for rec in res: to_subscribe = (rec.treatment_professional_ids.mapped('partner_id') - rec.message_follower_ids.mapped('partner_id')) + _logger.debug(f"Created injury {res.id}: {res.diagnosis}. Subscribing followers {to_subscribe}") rec.message_subscribe(to_subscribe.ids) + _logger.debug(f"Injury {res.id} now has followers {res.message_partner_ids}") return res def action_view_injury_form(self): From e3198168c18269bd96b520b58d7b2fc1255cd11e Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 5 Sep 2024 14:24:49 -0400 Subject: [PATCH 07/21] bemade_sports_clinic: add tracking for injury deletion and creation. Update translations. --- bemade_sports_clinic/i18n/fr_CA.po | 231 +++++++++++++++--- bemade_sports_clinic/models/patient.py | 1 + bemade_sports_clinic/models/patient_injury.py | 149 ++++++----- 3 files changed, 291 insertions(+), 90 deletions(-) diff --git a/bemade_sports_clinic/i18n/fr_CA.po b/bemade_sports_clinic/i18n/fr_CA.po index 6608832..a7fe0ed 100644 --- a/bemade_sports_clinic/i18n/fr_CA.po +++ b/bemade_sports_clinic/i18n/fr_CA.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-21 00:52+0000\n" -"PO-Revision-Date: 2024-02-21 00:52+0000\n" +"POT-Creation-Date: 2024-09-05 18:11+0000\n" +"PO-Revision-Date: 2024-09-05 18:11+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -15,15 +15,98 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: bemade_sports_clinic +#. odoo-python +#: code:addons/bemade_sports_clinic/models/patient_injury.py:0 +#: code:addons/bemade_sports_clinic/models/patient_injury.py:0 +#, python-format +msgid " Diagnosis: %s." +msgstr " Diagnostic : %s." + #. module: bemade_sports_clinic #: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_home msgid "/my/players" -msgstr "" +msgstr "/my/players" #. module: bemade_sports_clinic #: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_home msgid "/my/teams" +msgstr "/my/teams" + +#. module: bemade_sports_clinic +#: model:mail.template,body_html:bemade_sports_clinic.mail_template_patient_new_internal_note +msgid "" +"

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

    \n" +"
    \n" +"

    New Note:

    \n" +"
    \n" +"
    \n" +" " msgstr "" +"

    Une nouvelle note interne a été ajouté au dossier pour ." +"

    \n" +"
    \n" +"

    Nouvelle note :

    \n" +"
    \n" +"
    \n" +" " + +#. module: bemade_sports_clinic +#: model:mail.template,body_html:bemade_sports_clinic.mail_template_patient_injury_new_internal_note +msgid "" +"

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

    \n" +"
    \n" +"

    New Note:

    \n" +"
    \n" +"
    \n" +" " +msgstr "" +"

    Une nouvelle note interne a été publiée dans le dossier du patient avec\n" +" pour la blessure .\n" +"

    \n" +"
    \n" +"

    Nouvelle note :

    \n" +"
    \n" +"
    \n" +" " + +#. module: bemade_sports_clinic +#: model:mail.template,body_html:bemade_sports_clinic.mail_template_patient_status_update +msgid "" +"

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

    \n" +" " +msgstr "" +"

    Une mise à jour a été publiée dans le dossier de 's. Cliquez\n" +" ici pour les détails.\n" +"

    \n" +" " + +#. module: bemade_sports_clinic +#: model:mail.template,body_html:bemade_sports_clinic.mail_template_patient_injury_status_update +msgid "" +"

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

    \n" +" " +msgstr "" +"

    Une mise à jour a été publiée par rapport à la blessure de avec\n" +" avec le diagnostic . Cliquez\n" +" ici pour voir les détails de la blessure.\n" +"

    \n" +" " + +#. module: bemade_sports_clinic +#. odoo-python +#: code:addons/bemade_sports_clinic/models/patient_injury.py:0 +#, python-format +msgid "A new injury was created for this patient." +msgstr "Une nouvelle blessure a été créée pour ce patient." #. module: bemade_sports_clinic #. odoo-python @@ -39,9 +122,15 @@ msgstr "Une équipe ne peut avoir qu'un seul entraineur chef." msgid "A team can have only one head therapist." msgstr "Une équipe ne peut avoir qu'un seul thérapeute en chef." +#. module: bemade_sports_clinic +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_res_users__accessible_team_ids +msgid "Accessible Sports Teams" +msgstr "Équipes accessibles" + #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_needaction #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_needaction +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_needaction msgid "Action Needed" msgstr "Action requise" @@ -69,24 +158,28 @@ msgstr "Nombre de blessures actives" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__activity_ids #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__activity_ids +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_ids msgid "Activities" msgstr "Activités" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__activity_exception_decoration #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__activity_exception_decoration +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_exception_decoration msgid "Activity Exception Decoration" msgstr "Décoration d'activité en exception" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__activity_state #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__activity_state +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_state msgid "Activity State" msgstr "État de l'activité" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__activity_type_icon #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__activity_type_icon +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_type_icon msgid "Activity Type Icon" msgstr "Icône du type d'activité" @@ -105,9 +198,22 @@ msgstr "Âge" msgid "Allergies" msgstr "" +#. module: bemade_sports_clinic +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__allowed_user_ids +msgid "Allowed Users" +msgstr "Utilisateurs autorisés" + +#. module: bemade_sports_clinic +#. odoo-python +#: code:addons/bemade_sports_clinic/models/patient_injury.py:0 +#, python-format +msgid "An injury was deleted." +msgstr "Une blessure a été supprimée." + #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_attachment_count #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_attachment_count +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_attachment_count msgid "Attachment Count" msgstr "Nombre de pièces jointes" @@ -121,6 +227,11 @@ msgstr "Ville" msgid "Coach" msgstr "Entraîneur" +#. module: bemade_sports_clinic +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__parental_consent +msgid "Consent for Disclosure to Parent" +msgstr "Consentement pour divulgation au parent" + #. module: bemade_sports_clinic #: model:ir.model,name:bemade_sports_clinic.model_res_partner #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__partner_id @@ -200,6 +311,11 @@ msgstr "Nom pour affichage" msgid "Doctor" msgstr "Docteur" +#. module: bemade_sports_clinic +#: model:ir.model,name:bemade_sports_clinic.model_mail_followers +msgid "Document Followers" +msgstr "Abonnés au document" + #. module: bemade_sports_clinic #: model:ir.model.constraint,message:bemade_sports_clinic.constraint_sports_team_staff_team_staff_unique msgid "Each partner can only be related to a given team once." @@ -242,18 +358,21 @@ msgstr "Prénom" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_follower_ids #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_follower_ids +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_follower_ids msgid "Followers" msgstr "Abonnés" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_partner_ids #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_partner_ids +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_partner_ids msgid "Followers (Partners)" msgstr "Abonnés (partenaires)" #. module: bemade_sports_clinic #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__activity_type_icon #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__activity_type_icon +#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__activity_type_icon msgid "Font awesome icon e.g. fa-tasks" msgstr "Icône Font Awesome ex. fa-tasks" @@ -265,6 +384,7 @@ msgstr "Donner accès au portail" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__has_message #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__has_message +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__has_message msgid "Has Message" msgstr "A un message" @@ -317,18 +437,21 @@ msgstr "" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__activity_exception_icon #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__activity_exception_icon +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_exception_icon msgid "Icon" msgstr "Icône" #. module: bemade_sports_clinic #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__activity_exception_icon #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__activity_exception_icon +#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__activity_exception_icon msgid "Icon to indicate an exception activity." msgstr "Icône pour indiquer une activité en exception." #. module: bemade_sports_clinic #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__message_needaction #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__message_needaction +#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__message_needaction msgid "If checked, new messages require your attention." msgstr "Si coché, des nouveaux messages nécessitent votre attention." @@ -337,6 +460,8 @@ msgstr "Si coché, des nouveaux messages nécessitent votre attention." #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__message_has_sms_error #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__message_has_error #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__message_has_sms_error +#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__message_has_error +#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__message_has_sms_error msgid "If checked, some messages have a delivery error." msgstr "Si coché, certains messages ont une erreur de livraison." @@ -345,7 +470,8 @@ msgstr "Si coché, certains messages ont une erreur de livraison." #: code:addons/bemade_sports_clinic/models/patient_injury.py:0 #, python-format msgid "If injury date is not set, the N/A box must be checked." -msgstr "Sa la date de la blessure n'est pas remplie, la case S/O doit être cochée." +msgstr "" +"Sa la date de la blessure n'est pas remplie, la case S/O doit être cochée." #. module: bemade_sports_clinic #: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__stage__no_play @@ -384,6 +510,16 @@ msgstr "Détails des blessures pour" msgid "Injury Record for" msgstr "Historique des blessures pour" +#. module: bemade_sports_clinic +#: model:mail.template,subject:bemade_sports_clinic.mail_template_patient_new_internal_note +msgid "Internal Note Updated for Patient {{ object.name}}" +msgstr "" + +#. module: bemade_sports_clinic +#: model:mail.template,subject:bemade_sports_clinic.mail_template_patient_injury_new_internal_note +msgid "Internal Note Updated for Patient {{ object.patient_name }}" +msgstr "Note interne mise à jour pour le patient {{ object.patient_name }}" + #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__internal_notes msgid "Internal Notes" @@ -404,6 +540,7 @@ msgstr "Combinaison invalide des statuts de match et de pratique." #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_is_follower #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_is_follower +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_is_follower msgid "Is Follower" msgstr "Est abonné" @@ -461,6 +598,7 @@ msgstr "Dernière mise à jour le" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_main_attachment_id #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_main_attachment_id +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_main_attachment_id msgid "Main Attachment" msgstr "Pièce jointe principale" @@ -474,12 +612,14 @@ msgstr "Statut match" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_has_error #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_has_error +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_has_error msgid "Message Delivery error" msgstr "Erreur de livraison du message" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_ids #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_ids +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_ids msgid "Messages" msgstr "" @@ -498,6 +638,7 @@ msgstr "Mère" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__my_activity_date_deadline #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__my_activity_date_deadline +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__my_activity_date_deadline msgid "My Activity Deadline" msgstr "Date limite de mon activité" @@ -521,18 +662,21 @@ msgstr "Nom" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__activity_date_deadline #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__activity_date_deadline +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_date_deadline msgid "Next Activity Deadline" msgstr "Échéance de la prochaine activité" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__activity_summary #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__activity_summary +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_summary msgid "Next Activity Summary" msgstr "Résumé de la prochaine activité" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__activity_type_id #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__activity_type_id +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_type_id msgid "Next Activity Type" msgstr "Type de la prochaine activité" @@ -552,24 +696,28 @@ msgstr "" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_needaction_counter #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_needaction_counter +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_needaction_counter msgid "Number of Actions" msgstr "Nombre d'actions" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_has_error_counter #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_has_error_counter +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_has_error_counter msgid "Number of errors" msgstr "Nombre d'erreurs" #. module: bemade_sports_clinic #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__message_needaction_counter #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__message_needaction_counter +#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__message_needaction_counter msgid "Number of messages requiring action" msgstr "Nombre de messages nécessitant une action" #. module: bemade_sports_clinic #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__message_has_error_counter #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__message_has_error_counter +#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__message_has_error_counter msgid "Number of messages with delivery error" msgstr "Nombre de messages avec une erreur de livraison" @@ -608,11 +756,6 @@ msgstr "Équipe détenue" msgid "Parent Organization" msgstr "Organisation parente" -#. module: bemade_sports_clinic -#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__parental_consent -msgid "Consent for Disclosure to Parent" -msgstr "Consentement pour divulgation au parent" - #. module: bemade_sports_clinic #: model:ir.model,name:bemade_sports_clinic.model_sports_patient #: model:ir.model.fields,field_description:bemade_sports_clinic.field_res_partner__patient_ids @@ -633,9 +776,16 @@ msgid "Patient Contacts" msgstr "Contacts du patient" #. module: bemade_sports_clinic -#: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_update -msgid "Patient File Update" -msgstr "Mise à jour du dossier du patient" +#: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_external_update +#: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_injury_external_update +msgid "Patient File Update (External)" +msgstr "Mise à jour du dossier patient (externe)" + +#. module: bemade_sports_clinic +#: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_injury_internal_update +#: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_internal_update +msgid "Patient File Update (Internal)" +msgstr "Mise à jour du dossier patient (interne)" #. module: bemade_sports_clinic #: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form @@ -653,9 +803,9 @@ msgid "Patient Injury History" msgstr "Historique des blessures du patient" #. module: bemade_sports_clinic -#: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_injury_update -msgid "Patient Injury Status Update" -msgstr "Mise à jour du statut de la blessure d'un patient" +#: model:mail.template,subject:bemade_sports_clinic.mail_template_patient_injury_status_update +msgid "Patient Injury Status Update for {{ object.patient_name }}" +msgstr "Mise à jour du dossier de la blessure de {{ object.patient_name }}" #. module: bemade_sports_clinic #: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form @@ -667,21 +817,24 @@ msgstr "Téléphone et courriel du patient" msgid "Patient Record" msgstr "Dossier du patient" +#. module: bemade_sports_clinic +#: model:mail.template,name:bemade_sports_clinic.mail_template_patient_injury_new_internal_note +#: model:mail.template,name:bemade_sports_clinic.mail_template_patient_injury_status_update +#: model:mail.template,name:bemade_sports_clinic.mail_template_patient_new_internal_note +#: model:mail.template,name:bemade_sports_clinic.mail_template_patient_status_update +msgid "Patient Status Update" +msgstr "Mise à jour sur le patient" + +#. module: bemade_sports_clinic +#: model:mail.template,subject:bemade_sports_clinic.mail_template_patient_status_update +msgid "Patient Status Update for {{ object.name}}" +msgstr "Mise à jour pour le patient {{ object.name }}" + #. module: bemade_sports_clinic #: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form msgid "Patient's Contacts" msgstr "Contacts du patient" -#. module: bemade_sports_clinic -#: model:mail.message.subtype,description:bemade_sports_clinic.subtype_patient_update -msgid "Patient's file has been updated." -msgstr "Le dossier du patient a été mis à jour." - -#. module: bemade_sports_clinic -#: model:mail.message.subtype,description:bemade_sports_clinic.subtype_patient_injury_update -msgid "Patient's injury was updated." -msgstr "Le dossier de la blessure du patient a été mis à jour." - #. module: bemade_sports_clinic #: model:ir.actions.act_window,name:bemade_sports_clinic.action_view_patient #: model:ir.ui.menu,name:bemade_sports_clinic.sports_clinic_patients @@ -749,6 +902,7 @@ msgstr "Résolu" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__activity_user_id #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__activity_user_id +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_user_id msgid "Responsible User" msgstr "Utilisateur responsable" @@ -770,6 +924,7 @@ msgstr "Rôle" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_has_sms_error #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_has_sms_error +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_has_sms_error msgid "SMS Delivery error" msgstr "Erreur de livraison SMS" @@ -836,6 +991,7 @@ msgstr "État" #. module: bemade_sports_clinic #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__activity_state #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__activity_state +#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__activity_state msgid "" "Status based on activities\n" "Overdue: Due date is already passed\n" @@ -932,7 +1088,8 @@ msgstr "Cette équipe n'a pu être trouvée." #: code:addons/bemade_sports_clinic/models/res_partner.py:0 #, python-format msgid "To change a patient's name, change it from the patient form." -msgstr "Pour changer le nom d'un patient, changez le à partir du formulaire patient." +msgstr "" +"Pour changer le nom d'un patient, changez le à partir du formulaire patient." #. module: bemade_sports_clinic #: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_teams @@ -952,6 +1109,7 @@ msgstr "Professionnels de la santé" #. module: bemade_sports_clinic #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__activity_exception_decoration #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__activity_exception_decoration +#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__activity_exception_decoration msgid "Type of the exception activity on record." msgstr "Type d'exception de l'activité enregistrée." @@ -960,6 +1118,11 @@ msgstr "Type d'exception de l'activité enregistrée." msgid "User" msgstr "Utilisateur" +#. module: bemade_sports_clinic +#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_team_view_form +msgid "User Access" +msgstr "Accès utilisateur" + #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team_staff__user_ids msgid "Users" @@ -973,12 +1136,14 @@ msgstr "Site web" #. module: bemade_sports_clinic #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__website_message_ids #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__website_message_ids +#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__website_message_ids msgid "Website Messages" msgstr "Messages du site web" #. module: bemade_sports_clinic #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__website_message_ids #: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__website_message_ids +#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__website_message_ids msgid "Website communication history" msgstr "Historique de communication du site web" @@ -994,8 +1159,8 @@ msgid "" "Whether the patient has given their consent to share injury details with " "their parents." msgstr "" -"Indique si le patient a donné son consentement pour que les détails de la blessure soient partagés avec " -"ses parents." +"Indique si le patient a donné son consentement pour que les détails de la " +"blessure soient partagés avec ses parents." #. module: bemade_sports_clinic #: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__match_status__yes @@ -1023,3 +1188,13 @@ msgstr "Vos équipes" #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__zip msgid "Zip" msgstr "Code postal" + +#. module: bemade_sports_clinic +#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_home +msgid "players_count" +msgstr "players_count" + +#. module: bemade_sports_clinic +#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_home +msgid "teams_count" +msgstr "teams_count" diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index f2296eb..ad5cc28 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -84,6 +84,7 @@ class Patient(models.Model): comodel_name='sports.patient.injury', inverse_name='patient_id', string='Injuries', + tracking=True, ) injured_since = fields.Date(compute='_compute_is_injured') predicted_return_date = fields.Date(tracking=True) diff --git a/bemade_sports_clinic/models/patient_injury.py b/bemade_sports_clinic/models/patient_injury.py index c05e5bf..7a9fa2a 100644 --- a/bemade_sports_clinic/models/patient_injury.py +++ b/bemade_sports_clinic/models/patient_injury.py @@ -7,131 +7,146 @@ import logging _logger = logging.getLogger(__name__) external_tracking_fields = { - 'diagnosis', - 'predicted_resolution_date', - 'resolution_date', - 'external_notes', + "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', + "internal_notes", + "parental_consent", } class PatientInjury(models.Model): - _name = 'sports.patient.injury' + _name = "sports.patient.injury" _description = "Patient Injury" - _inherit = ['mail.thread', 'mail.activity.mixin'] - _rec_name = 'diagnosis' + _inherit = ["mail.thread", "mail.activity.mixin"] + _rec_name = "diagnosis" @api.model def _today(self): """Get the current date in the user's time zone.""" - return datetime.now(pytz.timezone(self.env.user.tz or 'GMT')) + return datetime.now(pytz.timezone(self.env.user.tz or "GMT")) # TODO: Find a way to improve notifications send about tracking injury details # TODO: Add field consentement_parental = fields.Selection(oui, non, non-applicable) patient_id = fields.Many2one( - comodel_name='sports.patient', - string="Patient", - readonly=True, - required=True + comodel_name="sports.patient", string="Patient", readonly=True, required=True ) patient_name = fields.Char(related="patient_id.name") diagnosis = fields.Char(tracking=True) injury_date = fields.Date( - string='Date of Injury', + string="Date of Injury", default=_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', - domain=[ - ('is_treatment_professional', '=', - True)], tracking=True + comodel_name="res.users", + relation="patient_injury_treatment_pro_rel", + column1="patient_injury_id", + column2="treatment_pro_id", + string="Treatment Professionals", + domain=[("is_treatment_professional", "=", True)], + tracking=True, ) predicted_resolution_date = fields.Date(tracking=True) resolution_date = fields.Date( - tracking=True, - help="The date when the injury was actually resolved." + tracking=True, help="The date when the injury was actually resolved." + ) + stage = fields.Selection( + selection=[("active", "Active"), ("resolved", "Resolved")], + compute="_compute_stage", ) - stage = fields.Selection(selection=[('active', 'Active'), ('resolved', 'Resolved')], compute='_compute_stage') parental_consent = fields.Selection( string="Consent for Disclosure to Parent", - selection=[ - ('yes', 'Yes'), - ('no', 'No'), - ('na', 'N/A') - ], + selection=[("yes", "Yes"), ("no", "No"), ("na", "N/A")], help="Whether the patient has given their consent to share injury details with their parents.", tracking=True, ) - @api.constrains('injury_date_na', 'injury_date') + @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.")) + raise ValidationError( + _("If injury date is not set, the N/A box must be checked.") + ) - @api.onchange('injury_date_na') + @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') + @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') + @api.depends("resolution_date") def _compute_stage(self): for rec in self: if rec.resolution_date and rec.resolution_date <= date.today(): - rec.stage = 'resolved' + rec.stage = "resolved" else: - rec.stage = 'active' + rec.stage = "active" def write(self, vals): super().write(vals) - if 'treatment_professional_ids' in vals: - to_subscribe = (self.treatment_professional_ids.mapped('partner_id') - - self.message_follower_ids.mapped('partner_id')) + if "treatment_professional_ids" in vals: + to_subscribe = self.treatment_professional_ids.mapped( + "partner_id" + ) - self.message_follower_ids.mapped("partner_id") self.message_subscribe(to_subscribe.ids) @api.model_create_multi def create(self, vals_list): res = super().create(vals_list) for rec in res: - to_subscribe = (rec.treatment_professional_ids.mapped('partner_id') - - rec.message_follower_ids.mapped('partner_id')) - _logger.debug(f"Created injury {res.id}: {res.diagnosis}. Subscribing followers {to_subscribe}") + to_subscribe = rec.treatment_professional_ids.mapped( + "partner_id" + ) - rec.message_follower_ids.mapped("partner_id") + _logger.debug( + f"Created injury {res.id}: {res.diagnosis}. Subscribing followers {to_subscribe}" + ) rec.message_subscribe(to_subscribe.ids) - _logger.debug(f"Injury {res.id} now has followers {res.message_partner_ids}") + _logger.debug( + f"Injury {res.id} now has followers {res.message_partner_ids}" + ) + msg_body = _("A new injury was created for this patient.") + if rec.diagnosis: + msg_body += _(" Diagnosis: %s." % rec.diagnosis) + rec.patient_id.message_post(body=msg_body, message_type="comment") return res + def unlink(self): + for rec in self: + msg_body = _("An injury was deleted.") + if rec.diagnosis: + msg_body += _(" Diagnosis: %s." % rec.diagnosis) + rec.patient_id.message_post(body=msg_body, message_type="comment") + return super().unlink() + def action_view_injury_form(self): self.ensure_one() return { - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'sports.patient.injury', - 'res_id': self.id, - 'context': self._context, + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "sports.patient.injury", + "res_id": self.id, + "context": self._context, } 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) @@ -140,18 +155,28 @@ class PatientInjury(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_injury_status_update'), { - 'auto_delete_message': False, - 'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_injury_external_update').id, - 'email_layout_xmlid': 'mail.mail_notification_light', - } + self.env.ref( + "bemade_sports_clinic.mail_template_patient_injury_status_update" + ), + { + "auto_delete_message": False, + "subtype_id": self.env.ref( + "bemade_sports_clinic.subtype_patient_injury_external_update" + ).id, + "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, - 'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_injury_internal_update').id, - '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, + "subtype_id": self.env.ref( + "bemade_sports_clinic.subtype_patient_injury_internal_update" + ).id, + "email_layout_xmlid": "mail.mail_notification_light", + }, ) return res From 03227bb5f4d7f7705948ce902b653c01c3aefad8 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 27 Aug 2024 13:25:40 -0400 Subject: [PATCH 08/21] add logic for computing followers when team staff changes --- bemade_sports_clinic/models/patient.py | 15 +++++++++++++++ bemade_sports_clinic/models/sports_team.py | 2 ++ 2 files changed, 17 insertions(+) diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index ad5cc28..2916ad8 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -253,3 +253,18 @@ class Patient(models.Model): } ) return res + + def recompute_followers(self): + """ 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') + 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) \ No newline at end of file diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index 11e7225..452bd9e 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -56,6 +56,7 @@ class SportsTeam(models.Model): res = super().write(vals) if 'staff_ids' in vals: self._allow_access_for_staff_internal_users() + self.patient_ids.recompute_followers() return res @api.model_create_multi @@ -64,6 +65,7 @@ class SportsTeam(models.Model): for index, rec in enumerate(res): 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') From eda7f32c7aa11c025f227c7b516fae44e05ea605 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 27 Aug 2024 16:56:14 -0400 Subject: [PATCH 09/21] added tests and fixed code for patient and injury follower logic --- bemade_sports_clinic/models/patient.py | 293 +++++++++++------- bemade_sports_clinic/models/patient_injury.py | 6 + bemade_sports_clinic/models/res_partner.py | 40 ++- bemade_sports_clinic/models/sports_team.py | 206 ++++++++---- bemade_sports_clinic/tests/__init__.py | 1 + bemade_sports_clinic/tests/test_patient.py | 188 +++++++++++ 6 files changed, 534 insertions(+), 200 deletions(-) create mode 100644 bemade_sports_clinic/tests/__init__.py create mode 100644 bemade_sports_clinic/tests/test_patient.py diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index 2916ad8..c247e06 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -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) \ No newline at end of file + 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) diff --git a/bemade_sports_clinic/models/patient_injury.py b/bemade_sports_clinic/models/patient_injury.py index 7a9fa2a..712833a 100644 --- a/bemade_sports_clinic/models/patient_injury.py +++ b/bemade_sports_clinic/models/patient_injury.py @@ -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 diff --git a/bemade_sports_clinic/models/res_partner.py b/bemade_sports_clinic/models/res_partner.py index 6ff7a0d..2d7f4d1 100644 --- a/bemade_sports_clinic/models/res_partner.py +++ b/bemade_sports_clinic/models/res_partner.py @@ -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") diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index 452bd9e..8a0cb53 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -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) diff --git a/bemade_sports_clinic/tests/__init__.py b/bemade_sports_clinic/tests/__init__.py new file mode 100644 index 0000000..145e1df --- /dev/null +++ b/bemade_sports_clinic/tests/__init__.py @@ -0,0 +1 @@ +from . import test_patient diff --git a/bemade_sports_clinic/tests/test_patient.py b/bemade_sports_clinic/tests/test_patient.py new file mode 100644 index 0000000..67cbc41 --- /dev/null +++ b/bemade_sports_clinic/tests/test_patient.py @@ -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 From c9690d46b1c1c1079d423af9ad91efb2725f09f1 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 5 Sep 2024 07:47:48 -0400 Subject: [PATCH 10/21] updates for followers --- bemade_sports_clinic/__manifest__.py | 50 +++++++++---------- .../migrations/16.0.1.6.0/post-migrate.py | 6 +++ bemade_sports_clinic/models/patient_injury.py | 6 ++- bemade_sports_clinic/tests/test_patient.py | 24 +++++++++ 4 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 bemade_sports_clinic/migrations/16.0.1.6.0/post-migrate.py diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py index 42eb42b..4a66b75 100644 --- a/bemade_sports_clinic/__manifest__.py +++ b/bemade_sports_clinic/__manifest__.py @@ -17,10 +17,10 @@ # DEALINGS IN THE SOFTWARE. # { - 'name': 'Sports Clinic Management', - 'version': '16.0.1.5.10', - 'summary': 'Manage the patients of a sports medicine clinic.', - 'description': """ + "name": "Sports Clinic Management", + "version": "16.0.1.6.0", + "summary": "Manage the patients of a sports medicine clinic.", + "description": """ Adds the notion of sports teams, players (patients), coaches and treatment professionals. The core purpose of this module is to keep track of the treatment history of players and to make it appropriately accessible to the various @@ -33,26 +33,26 @@ External users (portal users) can be added to give coaches and other team personnel access to limited player data such as estimated return-to-play dates. """, - 'category': 'Services/Medical', - 'author': 'Bemade Inc.', - 'website': 'https://www.bemade.org', - 'license': 'OPL-1', - 'depends': ['portal', 'contacts'], - 'data': [ - 'security/sports_clinic_groups.xml', - 'security/ir.model.access.csv', - 'security/sports_clinic_rules.xml', - 'data/sports_clinic_data.xml', - 'views/sports_team_views.xml', - 'views/sports_clinic_menus.xml', - 'views/sports_patient_injury_views.xml', - 'views/sports_patient_views.xml', - 'views/sports_clinic_portal_views.xml', - 'views/res_partner_views.xml', - 'views/res_users_views.xml', + "category": "Services/Medical", + "author": "Bemade Inc.", + "website": "https://www.bemade.org", + "license": "OPL-1", + "depends": ["portal", "contacts"], + "data": [ + "security/sports_clinic_groups.xml", + "security/ir.model.access.csv", + "security/sports_clinic_rules.xml", + "data/sports_clinic_data.xml", + "views/sports_team_views.xml", + "views/sports_clinic_menus.xml", + "views/sports_patient_injury_views.xml", + "views/sports_patient_views.xml", + "views/sports_clinic_portal_views.xml", + "views/res_partner_views.xml", + "views/res_users_views.xml", ], - 'demo': ['data/demo/sports_clinic_demo_data.xml'], - 'installable': True, - 'auto_install': False, - 'application': True, + "demo": ["data/demo/sports_clinic_demo_data.xml"], + "installable": True, + "auto_install": False, + "application": True, } diff --git a/bemade_sports_clinic/migrations/16.0.1.6.0/post-migrate.py b/bemade_sports_clinic/migrations/16.0.1.6.0/post-migrate.py new file mode 100644 index 0000000..29f6e35 --- /dev/null +++ b/bemade_sports_clinic/migrations/16.0.1.6.0/post-migrate.py @@ -0,0 +1,6 @@ +from odoo import api, SUPERUSER_ID + + +def migrate(cr, version): + env = api.Environment(cr, SUPERUSER_ID, {}) + env["sports.patient"].search([]).recompute_followers() diff --git a/bemade_sports_clinic/models/patient_injury.py b/bemade_sports_clinic/models/patient_injury.py index 712833a..6ecf50d 100644 --- a/bemade_sports_clinic/models/patient_injury.py +++ b/bemade_sports_clinic/models/patient_injury.py @@ -35,7 +35,11 @@ class PatientInjury(models.Model): # TODO: Add field consentement_parental = fields.Selection(oui, non, non-applicable) patient_id = fields.Many2one( - comodel_name="sports.patient", string="Patient", readonly=True, required=True + comodel_name="sports.patient", + string="Patient", + readonly=True, + required=True, + ondelete="cascade", ) patient_name = fields.Char(related="patient_id.name") diagnosis = fields.Char(tracking=True) diff --git a/bemade_sports_clinic/tests/test_patient.py b/bemade_sports_clinic/tests/test_patient.py index 67cbc41..8d012cb 100644 --- a/bemade_sports_clinic/tests/test_patient.py +++ b/bemade_sports_clinic/tests/test_patient.py @@ -143,15 +143,39 @@ class TestPatient(TransactionCase): self.assertIn(cp_id, injury.message_partner_ids) def test_removing_second_team_correctly_adjusts_staff(self): + """Tests both removing from the team side and from the patient side.""" 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) + self.patient1.write({"team_ids": [Command.link(team2.id)]}) + + self.assertIn(self.patient1, team2.patient_ids) + self.assertIn(therapist, self.patient1.message_partner_ids) + + self.patient1.write({"team_ids": [Command.unlink(team2.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 test_adding_patient_injury_sets_followers(self): + injury2 = self.env["sports.patient.injury"].create( + { + "patient_id": self.patient1.id, + "diagnosis": "some other injury", + } + ) + + self.assertEqual(injury2.message_partner_ids, self.coach.partner_id) + def _generate_second_team_and_staff(self): team2 = self.env["sports.team"].create( { From 75e986a4a22d9987d15055d4e60a31e5ac5f5046 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 5 Sep 2024 08:24:56 -0400 Subject: [PATCH 11/21] bemade_sport_clinic: improve facility for deactivating team staff --- bemade_sports_clinic/models/res_partner.py | 17 ++++++++++++++++- bemade_sports_clinic/models/sports_team.py | 1 + .../views/res_partner_views.xml | 11 ++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/bemade_sports_clinic/models/res_partner.py b/bemade_sports_clinic/models/res_partner.py index 2d7f4d1..99841ce 100644 --- a/bemade_sports_clinic/models/res_partner.py +++ b/bemade_sports_clinic/models/res_partner.py @@ -19,7 +19,9 @@ class Partner(models.Model): help="The teams this person works for.", ) teams_served_ids = fields.One2many( - comodel_name="sports.team", compute="_compute_teams_served" + comodel_name="sports.team", + compute="_compute_teams_served", + inverse="_inverse_teams_served", ) patient_ids = fields.One2many( comodel_name="sports.patient", inverse_name="partner_id" @@ -36,3 +38,16 @@ class Partner(models.Model): def _compute_teams_served(self): for rec in self: rec.teams_served_ids = rec.team_staff_rel_ids.mapped("team_id") + + @api.depends("team_staff_rel_ids.team_id") + def _inverse_teams_served(self): + for rec in self: + for staff in rec.team_staff_rel_ids: + if staff.team_id not in rec.teams_served_ids: + staff.unlink() + served_teams = rec.team_staff_rel_ids.mapped("team_id") + for team in rec.teams_served_ids: + if team not in served_teams: + raise UserError( + "To add a staff member to a team, use the team view." + ) diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index 8a0cb53..2fd671d 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -126,6 +126,7 @@ class TeamStaff(models.Model): domain=[("is_company", "=", False)], ondelete="cascade", ) + active = fields.Boolean(related="partner_id.active") role = fields.Selection( selection=[ ("head_coach", "Head Coach"), diff --git a/bemade_sports_clinic/views/res_partner_views.xml b/bemade_sports_clinic/views/res_partner_views.xml index 5c810f2..961adea 100644 --- a/bemade_sports_clinic/views/res_partner_views.xml +++ b/bemade_sports_clinic/views/res_partner_views.xml @@ -12,7 +12,16 @@ string="Teams"/> + string="Teams Served"> + + + + + + + + + From 9ed035a938f9262820d5ae4f9112b4c52f3a4b12 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Wed, 18 Sep 2024 14:18:31 -0400 Subject: [PATCH 12/21] attempted fix for access rights issues. --- bemade_sports_clinic/__manifest__.py | 7 +- .../migrations/16.0.1.7.0/post-migrate.py | 6 + .../migrations/16.0.1.7.0/pre-migrate.py | 10 ++ bemade_sports_clinic/models/res_users.py | 44 +++++-- bemade_sports_clinic/models/sports_team.py | 30 +++-- .../security/sports_clinic_rules.xml | 55 ++------- bemade_sports_clinic/tests/__init__.py | 1 + bemade_sports_clinic/tests/test_rights.py | 111 ++++++++++++++++++ 8 files changed, 203 insertions(+), 61 deletions(-) create mode 100644 bemade_sports_clinic/migrations/16.0.1.7.0/post-migrate.py create mode 100644 bemade_sports_clinic/migrations/16.0.1.7.0/pre-migrate.py create mode 100644 bemade_sports_clinic/tests/test_rights.py diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py index 4a66b75..bf12626 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.6.0", + "version": "16.0.1.7.0", "summary": "Manage the patients of a sports medicine clinic.", "description": """ Adds the notion of sports teams, players (patients), coaches and treatment @@ -38,6 +38,11 @@ "website": "https://www.bemade.org", "license": "OPL-1", "depends": ["portal", "contacts"], + "external_dependencies": { + "python": [ + "openupgradelib", + ], + }, "data": [ "security/sports_clinic_groups.xml", "security/ir.model.access.csv", diff --git a/bemade_sports_clinic/migrations/16.0.1.7.0/post-migrate.py b/bemade_sports_clinic/migrations/16.0.1.7.0/post-migrate.py new file mode 100644 index 0000000..f96358f --- /dev/null +++ b/bemade_sports_clinic/migrations/16.0.1.7.0/post-migrate.py @@ -0,0 +1,6 @@ +""" Patient access revised to make the team staff relationship central to +access. Everything is calculated from there. Inverse functions deal with +sports.team.staff records instead of having a separate table for storing +access rights.""" +def migrate(cr, version): + cr.execute("DROP TABLE sports_team_res_users_rel") \ No newline at end of file diff --git a/bemade_sports_clinic/migrations/16.0.1.7.0/pre-migrate.py b/bemade_sports_clinic/migrations/16.0.1.7.0/pre-migrate.py new file mode 100644 index 0000000..49ef708 --- /dev/null +++ b/bemade_sports_clinic/migrations/16.0.1.7.0/pre-migrate.py @@ -0,0 +1,10 @@ +import openupgradelib.openupgrade as ou +from odoo import SUPERUSER_ID, api + +def migrate(cr, version): + env = api.Environment(cr, SUPERUSER_ID, {}) + ou.delete_records_safely_by_xml_id(env, [ + "bemade_sports_clinic.restrict_team_access_to_allowed_internal_users", + "bemade_sports_clinic.restrict_patient_access_to_allowed_internal_users", + "bemade_sports_clinic.restrict_injury_access_to_allowed_internal_users", + ]) \ No newline at end of file diff --git a/bemade_sports_clinic/models/res_users.py b/bemade_sports_clinic/models/res_users.py index e522245..d39f45a 100644 --- a/bemade_sports_clinic/models/res_users.py +++ b/bemade_sports_clinic/models/res_users.py @@ -1,22 +1,48 @@ -from odoo import models, fields, api, _ +from odoo import models, fields, api, _, Command class User(models.Model): - _inherit = 'res.users' + _inherit = "res.users" is_treatment_professional = fields.Boolean( - compute="_compute_is_treatment_professional", store=True) + compute="_compute_is_treatment_professional", store=True + ) accessible_team_ids = fields.Many2many( comodel_name="sports.team", - relation="sports_team_res_users_rel", - column1="user_id", - column2="team_id", - string="Accessible Sports Teams", + compute="_compute_accessible_team_ids", + inverse="_inverse_accessible_team_ids", ) - @api.depends('groups_id') + @api.depends("groups_id") def _compute_is_treatment_professional(self): for rec in self: rec.is_treatment_professional = rec.has_group( - 'bemade_sports_clinic.group_sports_clinic_treatment_professional') + "bemade_sports_clinic.group_sports_clinic_treatment_professional" + ) + + def _compute_accessible_team_ids(self): + for rec in self: + rec.accessible_team_ids = rec.partner_id.staff_ids.mapped("team_id") + + def _inverse_accessible_team_ids(self): + for rec in self: + removed_teams = ( + rec.partner_id.staff_ids.mapped("team_id") - rec.accessible_team_ids + ) + added_teams = rec.accessible_team_ids - rec.partner_id.staff_ids.mapped( + "team_id" + ) + rec.partner_id.staff_ids.filtered( + lambda team: team in removed_teams + ).unlink() + self.env["sports.team.staff"].create( + [ + { + "team_id": team.id, + "partner_id": [Command.set([rec.id])], + "role": "other", + } + for team in added_teams + ] + ) diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index 2fd671d..78c6dbf 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -52,18 +52,14 @@ class SportsTeam(models.Model): 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]], + compute="_compute_allowed_user_ids", + inverse="_inverse_allowed_user_ids", ) def write(self, vals): previous_patient_ids = self.patient_ids res = super().write(vals) if "staff_ids" in vals or "patient_ids" in vals: - self._allow_access_for_staff_internal_users() (self.patient_ids | previous_patient_ids).recompute_followers() return res @@ -101,10 +97,26 @@ class SportsTeam(models.Model): 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): + def _compute_allowed_user_ids(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 + + def _inverse_allowed_user_ids(self): + for rec in self: + removed_staff = rec.staff_ids.filtered( + lambda staff: staff.user_ids not in rec.allowed_user_ids + ) + added_users = rec.allowed_user_ids - rec.staff_ids.user_ids + removed_staff.unlink() + self.env["sports.team.staff"].create( + [ + { + "team_id": rec.id, + "partner_id": user.partner_id.id, + "role": "other", + } + for user in added_users + ] ) diff --git a/bemade_sports_clinic/security/sports_clinic_rules.xml b/bemade_sports_clinic/security/sports_clinic_rules.xml index 78a97b1..9cfe05b 100644 --- a/bemade_sports_clinic/security/sports_clinic_rules.xml +++ b/bemade_sports_clinic/security/sports_clinic_rules.xml @@ -5,7 +5,8 @@ Restrict Team Staff Access to Their Players Only - + @@ -17,7 +18,8 @@ Restrict Team Staff Access to Their Teams Only - + @@ -29,7 +31,8 @@ Restrict Team Staff Access to Their Teams Only - + @@ -38,44 +41,11 @@ [('patient_id.team_ids.staff_ids.user_ids', 'in', user.id)] - - Restrict Team Access to Allowed Internal Users - - - - - - - - [('allowed_user_ids', 'in', user.id)] + + Restrict Patient Contact Access to Allowed Internal + Users - - - Restrict Patient Access to Allowed Internal Users - - - - - - - - [('team_ids.allowed_user_ids', 'in', user.id)] - - - - Restrict Team Access to Allowed Internal Users - - - - - - - - [('patient_id.team_ids.allowed_user_ids', 'in', user.id)] - - - - Restrict Patient Contact Access to Allowed Internal Users @@ -83,7 +53,7 @@ - [('patient_id.team_ids.allowed_user_ids', 'in', user.id)] + [('patient_id.team_ids.staff_ids.user_ids', 'in', user.id)] @@ -126,7 +96,8 @@ - Allow Sports Patient Contact Access to Administrators + Allow Sports Patient Contact Access to Administrators + diff --git a/bemade_sports_clinic/tests/__init__.py b/bemade_sports_clinic/tests/__init__.py index 145e1df..976c651 100644 --- a/bemade_sports_clinic/tests/__init__.py +++ b/bemade_sports_clinic/tests/__init__.py @@ -1 +1,2 @@ from . import test_patient +from . import test_rights diff --git a/bemade_sports_clinic/tests/test_rights.py b/bemade_sports_clinic/tests/test_rights.py new file mode 100644 index 0000000..b3e306a --- /dev/null +++ b/bemade_sports_clinic/tests/test_rights.py @@ -0,0 +1,111 @@ +from odoo.tests import TransactionCase, Form +from odoo.fields import Date +from datetime import timedelta +from odoo.exceptions import AccessError + + +class TestRights(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Create one admin user + cls.admin_user = cls.env["res.users"].create( + { + "name": "Admin User", + "login": "admin", + "password": "admin", + "groups_id": [ + ( + 6, + 0, + [ + cls.env.ref( + "bemade_sports_clinic.group_sports_clinic_admin" + ), + ], + ) + ], + } + ) + # Create one treatment professional user + cls.treatment_professional_user = cls.env["res.users"].create( + { + "name": "Treatment Professional User", + "login": "treatment_professional", + "password": "treatment_professional", + "groups_id": [ + ( + 6, + 0, + [ + cls.env.ref( + "bemade_sports_clinic.group_sports_clinic_treatment_professional" + ).id + ], + ) + ], + } + ) + + def test_treatment_pro_has_access_only_to_staffed_teams(self): + """A treatment professional should only have access to teams and, + by extension, patients for which they are a team staff member.""" + team, patients = self._generate_team_with_patient(self.admin_user) + with self.assertRaises(AccessError): + Form( + self.env["sports.team"] + .with_user(self.treatment_professional_user) + .browse(team.id) + ) + with self.assertRaises(AccessError): + Form( + self.env["sports.patient"] + .with_user(self.treatment_professional_user) + .browse(patients.ids) + ) + + def test_treatment_pro_can_remove_patient_from_team(self): + team, patients = self._generate_team_with_patient(self.admin_user) + self.env['sports.team.staff'].create({ + "team_id": team.id, + "partner_id": self.treatment_professional_user.id, + "role": "head_therapist", + }) + # Test removing the patient since we are team staff + # Should not throw an error... + with Form(team.with_user(self.treatment_professional_user)) as team: + team.patient_ids.remove(index=0) + self.assertEqual(len(team.patient_ids), 1) + + def _generate_team_with_patient(self, user=None): + user = user or self.env.user + team = ( + self.env["sports.team"] + .with_user(self.user) + .create( + { + "name": "Test Team", + } + ) + ) + patients = ( + self.env["sports.patient"] + .with_user(self.user) + .create( + [ + { + "first_name": "Test", + "last_name": "Patient One", + "date_of_birth": Date.today() - timedelta(days=-365 * 18), + "team_ids": [6, 0, team.ids], + }, + { + "first_name": "Test", + "last_name": "Patient Two", + "date_of_birth": Date.today() - timedelta(days=-365 * 18), + "team_ids": [6, 0, team.ids], + }, + ] + ) + ) + return team, patients \ No newline at end of file From 64db9a1176b895b54ce6b4b29a70480abab5795d Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Wed, 18 Sep 2024 14:35:51 -0400 Subject: [PATCH 13/21] fix patient injury rights --- bemade_sports_clinic/models/patient.py | 4 ++-- bemade_sports_clinic/models/patient_injury.py | 21 ++----------------- bemade_sports_clinic/models/sports_team.py | 5 +++-- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index c247e06..bafc132 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -132,7 +132,7 @@ class Patient(models.Model): def write(self, values): res = super().write(values) if "team_ids" in values: - self.recompute_followers() + self.sudo().recompute_followers() return res @api.model_create_multi @@ -151,7 +151,7 @@ class Patient(models.Model): .id ) res = super().create(vals_list) - res.recompute_followers() + res.sudo().recompute_followers() return res @api.constrains("match_status", "practice_status") diff --git a/bemade_sports_clinic/models/patient_injury.py b/bemade_sports_clinic/models/patient_injury.py index 6ecf50d..06fa51e 100644 --- a/bemade_sports_clinic/models/patient_injury.py +++ b/bemade_sports_clinic/models/patient_injury.py @@ -103,28 +103,11 @@ class PatientInjury(models.Model): else: rec.stage = "active" - def write(self, vals): - super().write(vals) - if "treatment_professional_ids" in vals: - to_subscribe = self.treatment_professional_ids.mapped( - "partner_id" - ) - self.message_follower_ids.mapped("partner_id") - self.message_subscribe(to_subscribe.ids) - @api.model_create_multi def create(self, vals_list): res = super().create(vals_list) - for rec in res: - to_subscribe = rec.treatment_professional_ids.mapped( - "partner_id" - ) - rec.message_follower_ids.mapped("partner_id") - _logger.debug( - f"Created injury {res.id}: {res.diagnosis}. Subscribing followers {to_subscribe}" - ) - rec.message_subscribe(to_subscribe.ids) - _logger.debug( - f"Injury {res.id} now has followers {res.message_partner_ids}" - ) + for rec in res.sudo(): + rec.message_subscribe(rec.patient_id.message_partner_ids) msg_body = _("A new injury was created for this patient.") if rec.diagnosis: msg_body += _(" Diagnosis: %s." % rec.diagnosis) diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index 2fd671d..e355be1 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -63,6 +63,7 @@ class SportsTeam(models.Model): previous_patient_ids = self.patient_ids res = super().write(vals) if "staff_ids" in vals or "patient_ids" in vals: + self = self.sudo() self._allow_access_for_staff_internal_users() (self.patient_ids | previous_patient_ids).recompute_followers() return res @@ -72,8 +73,8 @@ class SportsTeam(models.Model): res = super().create(vals_list) for index, rec in enumerate(res): if "staff_ids" in vals_list[index]: - rec._allow_access_for_staff_internal_users() - rec.patient_ids.recompute_followers() + rec.sudo()._allow_access_for_staff_internal_users() + rec.sudo().patient_ids.recompute_followers() return res def unlink(self): From 75ed4642c613c4b3075b6d72ee8375f48c4dbcfc Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Wed, 18 Sep 2024 15:40:45 -0400 Subject: [PATCH 14/21] fix tests, security and post-migration --- .../migrations/16.0.1.7.0/post-migrate.py | 19 +++++- .../security/sports_clinic_groups.xml | 10 ++- bemade_sports_clinic/tests/test_patient.py | 1 + bemade_sports_clinic/tests/test_rights.py | 66 ++++++++++--------- 4 files changed, 62 insertions(+), 34 deletions(-) diff --git a/bemade_sports_clinic/migrations/16.0.1.7.0/post-migrate.py b/bemade_sports_clinic/migrations/16.0.1.7.0/post-migrate.py index f96358f..140cc3d 100644 --- a/bemade_sports_clinic/migrations/16.0.1.7.0/post-migrate.py +++ b/bemade_sports_clinic/migrations/16.0.1.7.0/post-migrate.py @@ -2,5 +2,22 @@ access. Everything is calculated from there. Inverse functions deal with sports.team.staff records instead of having a separate table for storing access rights.""" + +from odoo import api, SUPERUSER_ID, Command + + def migrate(cr, version): - cr.execute("DROP TABLE sports_team_res_users_rel") \ No newline at end of file + env = api.Environment(cr, SUPERUSER_ID, {}) + rules = ( + env.ref("bemade_sports_clinic.restrict_staff_access_to_teams") + + env.ref("bemade_sports_clinic.restrict_staff_access_to_team_injuries") + + env.ref("bemade_sports_clinic.restrict_staff_access_to_team_players") + ) + rules.write({ + "groups": [Command.link(env.ref("base.group_user").id)] + }) + env.ref("bemade_sports_clinic.group_sports_clinic_user").write({ + "implied_ids": [Command.link(env.ref("base.group_partner_manager").id)] + }) + cr.execute("DROP TABLE sports_team_res_users_rel") + # Check the groups are OK diff --git a/bemade_sports_clinic/security/sports_clinic_groups.xml b/bemade_sports_clinic/security/sports_clinic_groups.xml index 13a58f2..455b2fe 100644 --- a/bemade_sports_clinic/security/sports_clinic_groups.xml +++ b/bemade_sports_clinic/security/sports_clinic_groups.xml @@ -1,12 +1,20 @@ - + Sports Medicine Clinic Management Internal User + Treatment Professional diff --git a/bemade_sports_clinic/tests/test_patient.py b/bemade_sports_clinic/tests/test_patient.py index 8d012cb..ece8524 100644 --- a/bemade_sports_clinic/tests/test_patient.py +++ b/bemade_sports_clinic/tests/test_patient.py @@ -3,6 +3,7 @@ from odoo import fields, Command from datetime import timedelta + @tagged("-at_install", "post_install") class TestPatient(TransactionCase): @classmethod diff --git a/bemade_sports_clinic/tests/test_rights.py b/bemade_sports_clinic/tests/test_rights.py index b3e306a..13207c8 100644 --- a/bemade_sports_clinic/tests/test_rights.py +++ b/bemade_sports_clinic/tests/test_rights.py @@ -1,9 +1,14 @@ -from odoo.tests import TransactionCase, Form +from odoo.tests import TransactionCase, Form, tagged from odoo.fields import Date from datetime import timedelta from odoo.exceptions import AccessError +from odoo import Command +import logging + +_logger = logging.getLogger(__name__) +@tagged("-at_install", "post_install") class TestRights(TransactionCase): @classmethod def setUpClass(cls): @@ -12,18 +17,14 @@ class TestRights(TransactionCase): cls.admin_user = cls.env["res.users"].create( { "name": "Admin User", - "login": "admin", - "password": "admin", + "login": "sports_admin", + "password": "sports_admin", "groups_id": [ - ( - 6, - 0, - [ - cls.env.ref( - "bemade_sports_clinic.group_sports_clinic_admin" - ), - ], - ) + Command.set( + cls.env.ref( + "bemade_sports_clinic.group_sports_clinic_admin" + ).id, + ), ], } ) @@ -34,18 +35,17 @@ class TestRights(TransactionCase): "login": "treatment_professional", "password": "treatment_professional", "groups_id": [ - ( - 6, - 0, - [ - cls.env.ref( - "bemade_sports_clinic.group_sports_clinic_treatment_professional" - ).id - ], - ) + Command.set( + cls.env.ref( + "bemade_sports_clinic.group_sports_clinic_treatment_professional" + ).id, + ), ], } ) + _logger.info( + f"Treatment Pro Groups: {cls.treatment_professional_user.groups_id.name}" + ) def test_treatment_pro_has_access_only_to_staffed_teams(self): """A treatment professional should only have access to teams and, @@ -61,16 +61,18 @@ class TestRights(TransactionCase): Form( self.env["sports.patient"] .with_user(self.treatment_professional_user) - .browse(patients.ids) + .browse(patients[0].id) ) def test_treatment_pro_can_remove_patient_from_team(self): team, patients = self._generate_team_with_patient(self.admin_user) - self.env['sports.team.staff'].create({ - "team_id": team.id, - "partner_id": self.treatment_professional_user.id, - "role": "head_therapist", - }) + self.env["sports.team.staff"].with_user(self.admin_user).create( + { + "team_id": team.id, + "partner_id": self.treatment_professional_user.partner_id.id, + "role": "head_therapist", + } + ) # Test removing the patient since we are team staff # Should not throw an error... with Form(team.with_user(self.treatment_professional_user)) as team: @@ -81,7 +83,7 @@ class TestRights(TransactionCase): user = user or self.env.user team = ( self.env["sports.team"] - .with_user(self.user) + .with_user(user) .create( { "name": "Test Team", @@ -90,22 +92,22 @@ class TestRights(TransactionCase): ) patients = ( self.env["sports.patient"] - .with_user(self.user) + .with_user(user) .create( [ { "first_name": "Test", "last_name": "Patient One", "date_of_birth": Date.today() - timedelta(days=-365 * 18), - "team_ids": [6, 0, team.ids], + "team_ids": [(6, 0, team.ids)], }, { "first_name": "Test", "last_name": "Patient Two", "date_of_birth": Date.today() - timedelta(days=-365 * 18), - "team_ids": [6, 0, team.ids], + "team_ids": [(6, 0, team.ids)], }, ] ) ) - return team, patients \ No newline at end of file + return team, patients From 4e091d3f63b516d9ad56c27508ca4f2539ec0f16 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 24 Sep 2024 09:47:26 -0400 Subject: [PATCH 15/21] fix for compute_accessible_team_ids on users --- bemade_sports_clinic/models/res_users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bemade_sports_clinic/models/res_users.py b/bemade_sports_clinic/models/res_users.py index d39f45a..be8c135 100644 --- a/bemade_sports_clinic/models/res_users.py +++ b/bemade_sports_clinic/models/res_users.py @@ -23,7 +23,7 @@ class User(models.Model): def _compute_accessible_team_ids(self): for rec in self: - rec.accessible_team_ids = rec.partner_id.staff_ids.mapped("team_id") + rec.accessible_team_ids = rec.partner_id.teams_served_ids def _inverse_accessible_team_ids(self): for rec in self: From 82c214b6443d1abd48d73e6a7cabb6a6205ba25f Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Fri, 27 Sep 2024 13:32:04 -0400 Subject: [PATCH 16/21] fix error in res.users._inverse_accessible_team_ids --- bemade_sports_clinic/models/res_users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bemade_sports_clinic/models/res_users.py b/bemade_sports_clinic/models/res_users.py index be8c135..39fdf05 100644 --- a/bemade_sports_clinic/models/res_users.py +++ b/bemade_sports_clinic/models/res_users.py @@ -40,7 +40,7 @@ class User(models.Model): [ { "team_id": team.id, - "partner_id": [Command.set([rec.id])], + "partner_id": rec.id, "role": "other", } for team in added_teams From 705c920b4db6849ddcd351a4658152f6728e126d Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Fri, 27 Sep 2024 14:33:20 -0400 Subject: [PATCH 17/21] [FIX] bemade_sports_clinic: add team access to user --- bemade_sports_clinic/models/patient.py | 1 + bemade_sports_clinic/models/res_users.py | 2 +- bemade_sports_clinic/tests/__init__.py | 1 + bemade_sports_clinic/tests/test_rights.py | 11 +++---- bemade_sports_clinic/tests/test_users.py | 35 +++++++++++++++++++++++ 5 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 bemade_sports_clinic/tests/test_users.py diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index bafc132..123add1 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -310,6 +310,7 @@ class Patient(models.Model): followers, the set of followers should be the set of staff on all teams the patient is part of.""" for patient in self: + patient = patient.sudo() current_followers = patient.message_partner_ids future_followers = patient.team_ids.mapped("staff_ids").mapped("partner_id") removed_followers = current_followers - future_followers diff --git a/bemade_sports_clinic/models/res_users.py b/bemade_sports_clinic/models/res_users.py index 39fdf05..afecd60 100644 --- a/bemade_sports_clinic/models/res_users.py +++ b/bemade_sports_clinic/models/res_users.py @@ -40,7 +40,7 @@ class User(models.Model): [ { "team_id": team.id, - "partner_id": rec.id, + "partner_id": rec.partner_id.id, "role": "other", } for team in added_teams diff --git a/bemade_sports_clinic/tests/__init__.py b/bemade_sports_clinic/tests/__init__.py index 976c651..e5e9a1e 100644 --- a/bemade_sports_clinic/tests/__init__.py +++ b/bemade_sports_clinic/tests/__init__.py @@ -1,2 +1,3 @@ from . import test_patient from . import test_rights +from . import test_users diff --git a/bemade_sports_clinic/tests/test_rights.py b/bemade_sports_clinic/tests/test_rights.py index 13207c8..d27783b 100644 --- a/bemade_sports_clinic/tests/test_rights.py +++ b/bemade_sports_clinic/tests/test_rights.py @@ -23,7 +23,7 @@ class TestRights(TransactionCase): Command.set( cls.env.ref( "bemade_sports_clinic.group_sports_clinic_admin" - ).id, + ).ids, ), ], } @@ -38,14 +38,15 @@ class TestRights(TransactionCase): Command.set( cls.env.ref( "bemade_sports_clinic.group_sports_clinic_treatment_professional" - ).id, + ).ids, ), ], } ) - _logger.info( - f"Treatment Pro Groups: {cls.treatment_professional_user.groups_id.name}" - ) + # _logger.info( + # f"Treatment Pro Groups: " + # f"{cls.treatment_professional_user.groups_id.mapped('name')}" + # ) def test_treatment_pro_has_access_only_to_staffed_teams(self): """A treatment professional should only have access to teams and, diff --git a/bemade_sports_clinic/tests/test_users.py b/bemade_sports_clinic/tests/test_users.py new file mode 100644 index 0000000..3f57c50 --- /dev/null +++ b/bemade_sports_clinic/tests/test_users.py @@ -0,0 +1,35 @@ +from odoo.tests import TransactionCase, tagged, Form +from odoo import Command + + +@tagged("-at_install", "post_install") +class TestUsers(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + def test_add_team_access_to_user(self): + user = self.env["res.users"].create( + { + "name": "test", + "login": "test", + "password": "test", + "groups_id": [ + Command.set( + self.env.ref( + "bemade_sports_clinic.group_sports_clinic_treatment_professional" + ).ids + ) + ], + } + ) + team = self.env["sports.team"].create( + { + "name": "Test", + } + ) + + self.assertNotIn(user, team.staff_ids.user_ids) + user.write({"accessible_team_ids": [Command.link(team.id)]}) + # user._inverse_accessible_team_ids() + self.assertIn(user, team.staff_ids.user_ids) From 6857df18bf5ae061ac92364bd935a862477ee083 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Fri, 27 Sep 2024 14:50:03 -0400 Subject: [PATCH 18/21] [FIX] bemade_sports_clinic: add team access to user --- bemade_sports_clinic/models/res_users.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bemade_sports_clinic/models/res_users.py b/bemade_sports_clinic/models/res_users.py index afecd60..fa825ae 100644 --- a/bemade_sports_clinic/models/res_users.py +++ b/bemade_sports_clinic/models/res_users.py @@ -27,13 +27,9 @@ class User(models.Model): def _inverse_accessible_team_ids(self): for rec in self: - removed_teams = ( - rec.partner_id.staff_ids.mapped("team_id") - rec.accessible_team_ids - ) - added_teams = rec.accessible_team_ids - rec.partner_id.staff_ids.mapped( - "team_id" - ) - rec.partner_id.staff_ids.filtered( + removed_teams = rec.partner_id.teams_served_ids - rec.accessible_team_ids + added_teams = rec.accessible_team_ids - rec.partner_id.teams_served_ids + rec.partner_id.teams_served_ids.filtered( lambda team: team in removed_teams ).unlink() self.env["sports.team.staff"].create( From 2c4e197c2bc4695651cc56b6c5a7cf36c3ce27bc Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Sun, 29 Sep 2024 15:49:52 -0400 Subject: [PATCH 19/21] fix for call to no longer existing function on creation of sports team --- .eslintrc.yml | 188 ++++++++++++++++++ .pre-commit-config.yaml | 96 +++++++++ .prettierrc.yml | 9 + .pylintrc | 110 ++++++++++ .pylintrc-mandatory | 110 ++++++++++ .../migrations/16.0.1.5.8/post-migrate.py | 2 +- bemade_sports_clinic/models/sports_team.py | 4 +- 7 files changed, 516 insertions(+), 3 deletions(-) create mode 100644 .eslintrc.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .prettierrc.yml create mode 100644 .pylintrc create mode 100644 .pylintrc-mandatory diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..fed88d7 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,188 @@ +env: + browser: true + es6: true + +# See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449 +parserOptions: + ecmaVersion: 2019 + +overrides: + - files: + - "**/*.esm.js" + parserOptions: + sourceType: module + +# Globals available in Odoo that shouldn't produce errorings +globals: + _: readonly + $: readonly + fuzzy: readonly + jQuery: readonly + moment: readonly + odoo: readonly + openerp: readonly + owl: readonly + luxon: readonly + +# Styling is handled by Prettier, so we only need to enable AST rules; +# see https://github.com/OCA/maintainer-quality-tools/pull/618#issuecomment-558576890 +rules: + accessor-pairs: warn + array-callback-return: warn + callback-return: warn + capitalized-comments: + - warn + - always + - ignoreConsecutiveComments: true + ignoreInlineComments: true + complexity: + - warn + - 15 + constructor-super: warn + dot-notation: warn + eqeqeq: warn + global-require: warn + handle-callback-err: warn + id-blacklist: warn + id-match: warn + init-declarations: error + max-depth: warn + max-nested-callbacks: warn + max-statements-per-line: warn + no-alert: warn + no-array-constructor: warn + no-caller: warn + no-case-declarations: warn + no-class-assign: warn + no-cond-assign: error + no-const-assign: error + no-constant-condition: warn + no-control-regex: warn + no-debugger: error + no-delete-var: warn + no-div-regex: warn + no-dupe-args: error + no-dupe-class-members: error + no-dupe-keys: error + no-duplicate-case: error + no-duplicate-imports: error + no-else-return: warn + no-empty-character-class: warn + no-empty-function: error + no-empty-pattern: error + no-empty: warn + no-eq-null: error + no-eval: error + no-ex-assign: error + no-extend-native: warn + no-extra-bind: warn + no-extra-boolean-cast: warn + no-extra-label: warn + no-fallthrough: warn + no-func-assign: error + no-global-assign: error + no-implicit-coercion: + - warn + - allow: ["~"] + no-implicit-globals: warn + no-implied-eval: warn + no-inline-comments: warn + no-inner-declarations: warn + no-invalid-regexp: warn + no-irregular-whitespace: warn + no-iterator: warn + no-label-var: warn + no-labels: warn + no-lone-blocks: warn + no-lonely-if: error + no-mixed-requires: error + no-multi-str: warn + no-native-reassign: error + no-negated-condition: warn + no-negated-in-lhs: error + no-new-func: warn + no-new-object: warn + no-new-require: warn + no-new-symbol: warn + no-new-wrappers: warn + no-new: warn + no-obj-calls: warn + no-octal-escape: warn + no-octal: warn + no-param-reassign: warn + no-path-concat: warn + no-process-env: warn + no-process-exit: warn + no-proto: warn + no-prototype-builtins: warn + no-redeclare: warn + no-regex-spaces: warn + no-restricted-globals: warn + no-restricted-imports: warn + no-restricted-modules: warn + no-restricted-syntax: warn + no-return-assign: error + no-script-url: warn + no-self-assign: warn + no-self-compare: warn + no-sequences: warn + no-shadow-restricted-names: warn + no-shadow: warn + no-sparse-arrays: warn + no-sync: warn + no-this-before-super: warn + no-throw-literal: warn + no-undef-init: warn + no-undef: error + no-unmodified-loop-condition: warn + no-unneeded-ternary: error + no-unreachable: error + no-unsafe-finally: error + no-unused-expressions: error + no-unused-labels: error + no-unused-vars: error + no-use-before-define: error + no-useless-call: warn + no-useless-computed-key: warn + no-useless-concat: warn + no-useless-constructor: warn + no-useless-escape: warn + no-useless-rename: warn + no-void: warn + no-with: warn + operator-assignment: [error, always] + prefer-const: warn + radix: warn + require-yield: warn + sort-imports: warn + spaced-comment: [error, always] + strict: [error, function] + use-isnan: error + valid-jsdoc: + - warn + - prefer: + arg: param + argument: param + augments: extends + constructor: class + exception: throws + func: function + method: function + prop: property + return: returns + virtual: abstract + yield: yields + preferType: + array: Array + bool: Boolean + boolean: Boolean + number: Number + object: Object + str: String + string: String + requireParamDescription: false + requireReturn: false + requireReturnDescription: false + requireReturnType: false + valid-typeof: warn + yoda: warn diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b0970bf --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,96 @@ +exclude: | + (?x) + # NOT INSTALLABLE ADDONS + # END NOT INSTALLABLE ADDONS + # Files and folders generated by bots, to avoid loops + ^setup/|/static/description/index\.html$| + # We don't want to mess with tool-generated files + .svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/| + # Maybe reactivate this when all README files include prettier ignore tags? + ^README\.md$| + # Library files can have extraneous formatting (even minimized) + /static/(src/)?lib/| + # Repos using Sphinx to generate docs don't need prettying + ^docs/_templates/.*\.html$| + # Don't bother non-technical authors with formatting issues in docs + readme/.*\.(rst|md)$| + # Ignore build and dist directories in addons + /build/|/dist/| + # You don't usually want a bot to modify your legal texts + (LICENSE.*|COPYING.*) +default_language_version: + python: python3 + node: "16.17.0" +repos: + - repo: local + hooks: + # These files are most likely copier diff rejection junks; if found, + # review them manually, fix the problem (if needed) and remove them + - id: forbidden-files + name: forbidden files + entry: found forbidden files; remove them + language: fail + files: "\\.rej$" + - id: en-po-files + name: en.po files cannot exist + entry: found a en.po file + language: fail + files: '[a-zA-Z0-9_]*/i18n/en\.po$' + - repo: https://github.com/OCA/odoo-pre-commit-hooks + rev: v0.0.25 + hooks: + - id: oca-checks-odoo-module + - id: oca-checks-po + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.7.1 + hooks: + - id: prettier + name: prettier (with plugin-xml) + additional_dependencies: + - "prettier@2.7.1" + - "@prettier/plugin-xml@2.2.0" + args: + - --plugin=@prettier/plugin-xml + files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$ + - repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.24.0 + hooks: + - id: eslint + verbose: true + args: + - --color + - --fix + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + # exclude autogenerated files + exclude: /README\.rst$|\.pot?$ + - id: end-of-file-fixer + # exclude autogenerated files + exclude: /README\.rst$|\.pot?$ + - id: debug-statements + - id: fix-encoding-pragma + args: ["--remove"] + - id: check-case-conflict + - id: check-docstring-first + - id: check-executables-have-shebangs + - id: check-merge-conflict + # exclude files where underlines are not distinguishable from merge conflicts + exclude: /README\.rst$|^docs/.*\.rst$ + - id: check-symlinks + - id: check-xml + - id: mixed-line-ending + args: ["--fix=lf"] + - repo: https://github.com/OCA/pylint-odoo + rev: v9.1.2 + hooks: + - id: pylint_odoo + name: pylint with optional checks + args: + - --rcfile=.pylintrc + - --exit-zero + verbose: true + - id: pylint_odoo + args: + - --rcfile=.pylintrc-mandatory diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..69f1400 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,9 @@ +# Defaults for all prettier-supported languages. +# Prettier will complete this with settings from .editorconfig file. +bracketSpacing: false +printWidth: 88 +proseWrap: always +semi: true +trailingComma: "es5" +xmlWhitespaceSensitivity: "strict" +bracketSameLine: false diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..ab3a58c --- /dev/null +++ b/.pylintrc @@ -0,0 +1,110 @@ +[MASTER] +load-plugins=pylint_odoo +score=n + +[MESSAGES CONTROL] +disable=all + +# This .pylintrc contains optional AND mandatory checks and is meant to be +# loaded in an IDE to have it check everything, in the hope this will make +# optional checks more visible to contributors who otherwise never look at a +# green travis to see optional checks that failed. +# .pylintrc-mandatory containing only mandatory checks is used the pre-commit +# config as a blocking check. + +enable=anomalous-backslash-in-string, + api-one-deprecated, + api-one-multi-together, + assignment-from-none, + attribute-deprecated, + class-camelcase, + dangerous-default-value, + dangerous-view-replace-wo-priority, + development-status-allowed, + duplicate-id-csv, + duplicate-key, + duplicate-xml-fields, + duplicate-xml-record-id, + eval-referenced, + eval-used, + incoherent-interpreter-exec-perm, + license-allowed, + manifest-author-string, + manifest-deprecated-key, + manifest-required-key, + manifest-version-format, + method-compute, + method-inverse, + method-required-super, + method-search, + openerp-exception-warning, + pointless-statement, + pointless-string-statement, + print-used, + redundant-keyword-arg, + redundant-modulename-xml, + reimported, + relative-import, + return-in-init, + rst-syntax-error, + sql-injection, + too-few-format-args, + translation-field, + translation-required, + unreachable, + use-vim-comment, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + attribute-string-redundant, + character-not-valid-in-resource-link, + consider-merging-classes-inherited, + context-overridden, + create-user-wo-reset-password, + dangerous-filter-wo-user, + dangerous-qweb-replace-wo-priority, + deprecated-data-xml-node, + deprecated-openerp-xml-node, + duplicate-po-message-definition, + except-pass, + file-not-used, + invalid-commit, + manifest-maintainers-list, + missing-newline-extrafiles, + missing-return, + odoo-addons-relative-import, + old-api7-method-defined, + po-msgstr-variables, + po-syntax-error, + renamed-field-parameter, + resource-not-exist, + str-format-used, + test-folder-imported, + translation-contains-variable, + translation-positional-used, + unnecessary-utf8-coding-comment, + website-manifest-key-not-valid-uri, + xml-attribute-translatable, + xml-deprecated-qweb-directive, + xml-deprecated-tree-attribute, + external-request-timeout, + # messages that do not cause the lint step to fail + consider-merging-classes-inherited, + create-user-wo-reset-password, + dangerous-filter-wo-user, + deprecated-module, + file-not-used, + invalid-commit, + missing-manifest-dependency, + missing-newline-extrafiles, + no-utf8-coding-comment, + odoo-addons-relative-import, + old-api7-method-defined, + redefined-builtin, + too-complex, + unnecessary-utf8-coding-comment + + +[REPORTS] +msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} +output-format=colorized +reports=no diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory new file mode 100644 index 0000000..ab3a58c --- /dev/null +++ b/.pylintrc-mandatory @@ -0,0 +1,110 @@ +[MASTER] +load-plugins=pylint_odoo +score=n + +[MESSAGES CONTROL] +disable=all + +# This .pylintrc contains optional AND mandatory checks and is meant to be +# loaded in an IDE to have it check everything, in the hope this will make +# optional checks more visible to contributors who otherwise never look at a +# green travis to see optional checks that failed. +# .pylintrc-mandatory containing only mandatory checks is used the pre-commit +# config as a blocking check. + +enable=anomalous-backslash-in-string, + api-one-deprecated, + api-one-multi-together, + assignment-from-none, + attribute-deprecated, + class-camelcase, + dangerous-default-value, + dangerous-view-replace-wo-priority, + development-status-allowed, + duplicate-id-csv, + duplicate-key, + duplicate-xml-fields, + duplicate-xml-record-id, + eval-referenced, + eval-used, + incoherent-interpreter-exec-perm, + license-allowed, + manifest-author-string, + manifest-deprecated-key, + manifest-required-key, + manifest-version-format, + method-compute, + method-inverse, + method-required-super, + method-search, + openerp-exception-warning, + pointless-statement, + pointless-string-statement, + print-used, + redundant-keyword-arg, + redundant-modulename-xml, + reimported, + relative-import, + return-in-init, + rst-syntax-error, + sql-injection, + too-few-format-args, + translation-field, + translation-required, + unreachable, + use-vim-comment, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + attribute-string-redundant, + character-not-valid-in-resource-link, + consider-merging-classes-inherited, + context-overridden, + create-user-wo-reset-password, + dangerous-filter-wo-user, + dangerous-qweb-replace-wo-priority, + deprecated-data-xml-node, + deprecated-openerp-xml-node, + duplicate-po-message-definition, + except-pass, + file-not-used, + invalid-commit, + manifest-maintainers-list, + missing-newline-extrafiles, + missing-return, + odoo-addons-relative-import, + old-api7-method-defined, + po-msgstr-variables, + po-syntax-error, + renamed-field-parameter, + resource-not-exist, + str-format-used, + test-folder-imported, + translation-contains-variable, + translation-positional-used, + unnecessary-utf8-coding-comment, + website-manifest-key-not-valid-uri, + xml-attribute-translatable, + xml-deprecated-qweb-directive, + xml-deprecated-tree-attribute, + external-request-timeout, + # messages that do not cause the lint step to fail + consider-merging-classes-inherited, + create-user-wo-reset-password, + dangerous-filter-wo-user, + deprecated-module, + file-not-used, + invalid-commit, + missing-manifest-dependency, + missing-newline-extrafiles, + no-utf8-coding-comment, + odoo-addons-relative-import, + old-api7-method-defined, + redefined-builtin, + too-complex, + unnecessary-utf8-coding-comment + + +[REPORTS] +msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} +output-format=colorized +reports=no diff --git a/bemade_sports_clinic/migrations/16.0.1.5.8/post-migrate.py b/bemade_sports_clinic/migrations/16.0.1.5.8/post-migrate.py index 818ad3b..62a8c9c 100644 --- a/bemade_sports_clinic/migrations/16.0.1.5.8/post-migrate.py +++ b/bemade_sports_clinic/migrations/16.0.1.5.8/post-migrate.py @@ -3,5 +3,5 @@ from odoo import api, SUPERUSER_ID def migrate(cr, version): env = api.Environment(cr, SUPERUSER_ID, {}) - env['sports.team'].search([])._allow_access_for_staff_internal_users() + env["sports.team"].search([])._allow_access_for_staff_internal_users() cr.execute('CREATE EXTENSION IF NOT EXISTS "unaccent"') diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index cf32557..c8ff2f6 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -68,7 +68,6 @@ class SportsTeam(models.Model): res = super().create(vals_list) for index, rec in enumerate(res): if "staff_ids" in vals_list[index]: - rec.sudo()._allow_access_for_staff_internal_users() rec.sudo().patient_ids.recompute_followers() return res @@ -226,8 +225,9 @@ class TeamStaff(models.Model): def unlink(self): patients = self.team_id.mapped("patient_ids") - super().unlink() + res = super().unlink() patients.recompute_followers() + return res def write(self, values): if "team_id" in values: From 4eb7f14505586498bfc0b1bc3d3a6afee613dbda Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Sun, 29 Sep 2024 16:18:00 -0400 Subject: [PATCH 20/21] [FIX] sports_clinic: patient name updates on partner. --- bemade_sports_clinic/models/patient.py | 22 +++++++++++++++------- bemade_sports_clinic/models/res_partner.py | 8 ++++++-- bemade_sports_clinic/tests/test_patient.py | 13 +++++++++++-- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index 123add1..c28ccb3 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -38,7 +38,7 @@ class Patient(models.Model): 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 + related="partner_id.name", ) phone = fields.Char(related="partner_id.phone", readonly=False) mobile = fields.Char(related="partner_id.mobile", readonly=False) @@ -133,8 +133,16 @@ class Patient(models.Model): res = super().write(values) if "team_ids" in values: self.sudo().recompute_followers() + if "first_name" in values or "last_name" in values: + self._recompute_name() return res + def _recompute_name(self): + for rec in self: + rec.partner_id.with_context(patient_update=True).name = ( + rec._get_name_from_first_and_last(rec.first_name, rec.last_name) + ) + @api.model_create_multi def create(self, vals_list): for row in vals_list: @@ -203,14 +211,14 @@ class Patient(models.Model): else: rec.age = relativedelta(date.today(), rec.date_of_birth).years - @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) - @api.model def _get_name_from_first_and_last(self, first_name, last_name): - return ((first_name or "") + " " + (last_name or "")).strip() + names = [] + if first_name: + names.append(first_name) + if last_name: + names.append(last_name) + return " ".join(names) @api.depends("practice_status", "match_status", "injury_ids.injury_date") def _compute_is_injured(self): diff --git a/bemade_sports_clinic/models/res_partner.py b/bemade_sports_clinic/models/res_partner.py index 99841ce..7698b7d 100644 --- a/bemade_sports_clinic/models/res_partner.py +++ b/bemade_sports_clinic/models/res_partner.py @@ -28,7 +28,11 @@ class Partner(models.Model): ) def write(self, vals): - if self.patient_ids and "name" in vals: + if ( + self.patient_ids + and "name" in vals + and not self._context.get("patient_update") + ): raise ValidationError( _("To change a patient's name, change it from the patient form.") ) @@ -49,5 +53,5 @@ class Partner(models.Model): for team in rec.teams_served_ids: if team not in served_teams: raise UserError( - "To add a staff member to a team, use the team view." + _("To add a staff member to a team, use the team view.") ) diff --git a/bemade_sports_clinic/tests/test_patient.py b/bemade_sports_clinic/tests/test_patient.py index ece8524..69c3c52 100644 --- a/bemade_sports_clinic/tests/test_patient.py +++ b/bemade_sports_clinic/tests/test_patient.py @@ -1,9 +1,8 @@ -from odoo.tests import TransactionCase, tagged +from odoo.tests import TransactionCase, tagged, Form from odoo import fields, Command from datetime import timedelta - @tagged("-at_install", "post_install") class TestPatient(TransactionCase): @classmethod @@ -211,3 +210,13 @@ class TestPatient(TransactionCase): .partner_id ) return team2, therapist, coach + + def test_changing_patient_name_changes_on_partner(self): + new_last_name = "New last name" + new_first_name = "New first name" + with Form(self.patient1) as patient: + patient.last_name = new_last_name + patient.first_name = new_first_name + self.assertEqual( + self.patient1.partner_id.name, " ".join([new_first_name, new_last_name]) + ) From 671555dcc8c03568fc8b53581c6d094d942ede4d Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Sun, 29 Sep 2024 16:32:27 -0400 Subject: [PATCH 21/21] [FIX] sports_clinic: removing access unlinks teams --- bemade_sports_clinic/models/res_users.py | 5 +++-- bemade_sports_clinic/models/sports_team.py | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bemade_sports_clinic/models/res_users.py b/bemade_sports_clinic/models/res_users.py index fa825ae..0fa392b 100644 --- a/bemade_sports_clinic/models/res_users.py +++ b/bemade_sports_clinic/models/res_users.py @@ -29,9 +29,10 @@ class User(models.Model): for rec in self: removed_teams = rec.partner_id.teams_served_ids - rec.accessible_team_ids added_teams = rec.accessible_team_ids - rec.partner_id.teams_served_ids - rec.partner_id.teams_served_ids.filtered( + removed_teams = rec.partner_id.teams_served_ids.filtered( lambda team: team in removed_teams - ).unlink() + ) + removed_teams.remove_access(self) self.env["sports.team.staff"].create( [ { diff --git a/bemade_sports_clinic/models/sports_team.py b/bemade_sports_clinic/models/sports_team.py index c8ff2f6..91300bd 100644 --- a/bemade_sports_clinic/models/sports_team.py +++ b/bemade_sports_clinic/models/sports_team.py @@ -118,6 +118,9 @@ class SportsTeam(models.Model): ] ) + def remove_access(self, user): + self.staff_ids.filtered(lambda staff: user in staff.user_ids).unlink() + class TeamStaff(models.Model): _name = "sports.team.staff"