diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py
index 96f75f6..6cc1415 100644
--- a/bemade_sports_clinic/__manifest__.py
+++ b/bemade_sports_clinic/__manifest__.py
@@ -18,7 +18,7 @@
#
{
'name': 'Sports Clinic Management',
- 'version': '17.0.1.5.2',
+ 'version': '17.0.1.6.2',
'summary': 'Manage the patients of a sports medicine clinic.',
'description': """
Adds the notion of sports teams, players (patients), coaches and treatment
@@ -33,25 +33,31 @@
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',
+ "category": "Services/Medical",
+ "author": "Bemade Inc.",
+ "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",
+ "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/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/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/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..62a8c9c
--- /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/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/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..140cc3d
--- /dev/null
+++ b/bemade_sports_clinic/migrations/16.0.1.7.0/post-migrate.py
@@ -0,0 +1,23 @@
+""" 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."""
+
+from odoo import api, SUPERUSER_ID, Command
+
+
+def migrate(cr, version):
+ 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/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/patient.py b/bemade_sports_clinic/models/patient.py
index f2296eb..c28ccb3 100644
--- a/bemade_sports_clinic/models/patient.py
+++ b/bemade_sports_clinic/models/patient.py
@@ -3,105 +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",
+ )
+ 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',
+ 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",
@@ -110,55 +118,92 @@ 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.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:
- 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.sudo().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:
@@ -166,56 +211,60 @@ 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')
+ @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
@@ -224,11 +273,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)
@@ -237,18 +286,47 @@ 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
+ 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:
+ 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
+ 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_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/patient_injury.py b/bemade_sports_clinic/models/patient_injury.py
index f3c20c5..06fa51e 100644
--- a/bemade_sports_clinic/models/patient_injury.py
+++ b/bemade_sports_clinic/models/patient_injury.py
@@ -2,132 +2,138 @@ 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',
- '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',
+ comodel_name="sports.patient",
string="Patient",
readonly=True,
- required=True
+ required=True,
+ ondelete="cascade",
)
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'
-
- 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)
+ rec.stage = "active"
@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'))
- rec.message_subscribe(to_subscribe.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)
+ 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)
@@ -136,18 +142,34 @@ 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
+
+ @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..7698b7d 100644
--- a/bemade_sports_clinic/models/res_partner.py
+++ b/bemade_sports_clinic/models/res_partner.py
@@ -4,25 +4,54 @@ 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",
+ inverse="_inverse_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
+ and not self._context.get("patient_update")
+ ):
+ 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")
+
+ @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/res_users.py b/bemade_sports_clinic/models/res_users.py
index 1dcd258..0fa392b 100644
--- a/bemade_sports_clinic/models/res_users.py
+++ b/bemade_sports_clinic/models/res_users.py
@@ -1,14 +1,45 @@
-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
+ )
- @api.depends('groups_id')
+ accessible_team_ids = fields.Many2many(
+ comodel_name="sports.team",
+ compute="_compute_accessible_team_ids",
+ inverse="_inverse_accessible_team_ids",
+ )
+
+ @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.teams_served_ids
+
+ def _inverse_accessible_team_ids(self):
+ 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
+ removed_teams = rec.partner_id.teams_served_ids.filtered(
+ lambda team: team in removed_teams
+ )
+ removed_teams.remove_access(self)
+ self.env["sports.team.staff"].create(
+ [
+ {
+ "team_id": team.id,
+ "partner_id": rec.partner_id.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 6f7d589..91300bd 100644
--- a/bemade_sports_clinic/models/sports_team.py
+++ b/bemade_sports_clinic/models/sports_team.py
@@ -5,105 +5,240 @@ 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)
- 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_name = fields.Char(related='head_therapist_id.name')
+ 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",
+ string="Head Coach Name",
+ )
+ head_therapist_id = fields.Many2one(
+ 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",
+ )
website = fields.Char()
+ allowed_user_ids = fields.Many2many(
+ comodel_name="res.users",
+ compute="_compute_allowed_user_ids",
+ inverse="_inverse_allowed_user_ids",
+ )
- @api.depends('patient_ids.is_injured')
+ def write(self, vals):
+ previous_patient_ids = self.sudo().patient_ids
+ res = super().write(vals)
+ if "staff_ids" in vals or "patient_ids" in vals:
+ (self.sudo().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]:
+ rec.sudo().patient_ids.recompute_followers()
+ return res
+
+ 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 _compute_allowed_user_ids(self):
+ for rec in self:
+ 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
+ ]
+ )
+
+ 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"
_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",
+ )
+ active = fields.Boolean(related="partner_id.active")
+ 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")
+ res = super().unlink()
+ patients.recompute_followers()
+ return res
+
+ 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/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/security/sports_clinic_rules.xml b/bemade_sports_clinic/security/sports_clinic_rules.xml
index 41bf382..9cfe05b 100644
--- a/bemade_sports_clinic/security/sports_clinic_rules.xml
+++ b/bemade_sports_clinic/security/sports_clinic_rules.xml
@@ -2,13 +2,111 @@
-
+
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 Patient Contact Access to Allowed Internal
+ Users
+
+
+
+
+
+
+
+
+ [('patient_id.team_ids.staff_ids.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/tests/__init__.py b/bemade_sports_clinic/tests/__init__.py
new file mode 100644
index 0000000..e5e9a1e
--- /dev/null
+++ b/bemade_sports_clinic/tests/__init__.py
@@ -0,0 +1,3 @@
+from . import test_patient
+from . import test_rights
+from . import test_users
diff --git a/bemade_sports_clinic/tests/test_patient.py b/bemade_sports_clinic/tests/test_patient.py
new file mode 100644
index 0000000..69c3c52
--- /dev/null
+++ b/bemade_sports_clinic/tests/test_patient.py
@@ -0,0 +1,222 @@
+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
+ 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):
+ """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(
+ {
+ "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
+
+ 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])
+ )
diff --git a/bemade_sports_clinic/tests/test_rights.py b/bemade_sports_clinic/tests/test_rights.py
new file mode 100644
index 0000000..d27783b
--- /dev/null
+++ b/bemade_sports_clinic/tests/test_rights.py
@@ -0,0 +1,114 @@
+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):
+ super().setUpClass()
+ # Create one admin user
+ cls.admin_user = cls.env["res.users"].create(
+ {
+ "name": "Admin User",
+ "login": "sports_admin",
+ "password": "sports_admin",
+ "groups_id": [
+ Command.set(
+ cls.env.ref(
+ "bemade_sports_clinic.group_sports_clinic_admin"
+ ).ids,
+ ),
+ ],
+ }
+ )
+ # 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": [
+ Command.set(
+ cls.env.ref(
+ "bemade_sports_clinic.group_sports_clinic_treatment_professional"
+ ).ids,
+ ),
+ ],
+ }
+ )
+ # _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,
+ 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[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"].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:
+ 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(user)
+ .create(
+ {
+ "name": "Test Team",
+ }
+ )
+ )
+ patients = (
+ self.env["sports.patient"]
+ .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)],
+ },
+ {
+ "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
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)
diff --git a/bemade_sports_clinic/views/res_partner_views.xml b/bemade_sports_clinic/views/res_partner_views.xml
index e2c77af..1716aa9 100644
--- a/bemade_sports_clinic/views/res_partner_views.xml
+++ b/bemade_sports_clinic/views/res_partner_views.xml
@@ -8,7 +8,16 @@
-
+
+
+
+
+
+
+
+
+
+
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_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 @@
-
+
-
diff --git a/bemade_sports_clinic/views/sports_team_views.xml b/bemade_sports_clinic/views/sports_team_views.xml
index b52058b..797985f 100644
--- a/bemade_sports_clinic/views/sports_team_views.xml
+++ b/bemade_sports_clinic/views/sports_team_views.xml
@@ -117,10 +117,25 @@
-
-
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
sports.team.staff.view.form
|