Merge branch '16.0' into 17.0

Forward port of latest updates.
This commit is contained in:
Marc Durepos 2024-10-15 08:56:28 -04:00
commit 4baef176fb
23 changed files with 1361 additions and 320 deletions

View file

@ -18,7 +18,7 @@
# #
{ {
'name': 'Sports Clinic Management', '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.', 'summary': 'Manage the patients of a sports medicine clinic.',
'description': """ 'description': """
Adds the notion of sports teams, players (patients), coaches and treatment 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 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. personnel access to limited player data such as estimated return-to-play dates.
""", """,
'category': 'Services/Medical', "category": "Services/Medical",
'author': 'Bemade Inc.', "author": "Bemade Inc.",
'website': 'https://www.bemade.org', "website": "https://www.bemade.org",
'license': 'OPL-1', "license": "OPL-1",
'depends': ['portal', 'contacts'], "depends": ["portal", "contacts"],
'data': [ "external_dependencies": {
'security/sports_clinic_groups.xml', "python": [
'security/ir.model.access.csv', "openupgradelib",
'security/sports_clinic_rules.xml', ],
'data/sports_clinic_data.xml', },
'views/sports_team_views.xml', "data": [
'views/sports_clinic_menus.xml', "security/sports_clinic_groups.xml",
'views/sports_patient_injury_views.xml', "security/ir.model.access.csv",
'views/sports_patient_views.xml', "security/sports_clinic_rules.xml",
'views/sports_clinic_portal_views.xml', "data/sports_clinic_data.xml",
'views/res_partner_views.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'], "demo": ["data/demo/sports_clinic_demo_data.xml"],
'installable': True, "installable": True,
'auto_install': False, "auto_install": False,
'application': True, "application": True,
} }

View file

@ -16,9 +16,8 @@ class TeamStaffPortal(CustomerPortal):
@classmethod @classmethod
def _prepare_teams_domain(cls): def _prepare_teams_domain(cls):
user = http.request.env.user user = http.request.env.user
partner = http.request.env.user.partner_id
return [ return [
('staff_ids', 'in', partner.team_staff_rel_ids.ids), ('staff_ids.user_ids', 'in', user.id),
] ]
@classmethod @classmethod

View file

@ -6,8 +6,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Odoo Server 16.0\n" "Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-02-21 00:52+0000\n" "POT-Creation-Date: 2024-09-05 18:11+0000\n"
"PO-Revision-Date: 2024-02-21 00:52+0000\n" "PO-Revision-Date: 2024-09-05 18:11+0000\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -15,15 +15,98 @@ msgstr ""
"Content-Transfer-Encoding: \n" "Content-Transfer-Encoding: \n"
"Plural-Forms: \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 #. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_home #: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_home
msgid "/my/players" msgid "/my/players"
msgstr "" msgstr "/my/players"
#. module: bemade_sports_clinic #. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_home #: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_home
msgid "/my/teams" 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 ""
"<p>A new internal note has been posted to the record for <t t-out=\"object.name\"></t>'s.\n"
" </p>\n"
" <div>\n"
" <p><strong>New Note: </strong></p>\n"
" <div><t t-out=\"object.team_info_notes\"></t></div>\n"
" </div>\n"
" "
msgstr "" msgstr ""
"<p>Une nouvelle note interne a été ajouté au dossier pour <t t-out=\"object.name\">."
" </p>\n"
" <div>\n"
" <p><strong>Nouvelle note : </strong></p>\n"
" <div><t t-out=\"object.team_info_notes\"></t></div>\n"
" </div>\n"
" "
#. module: bemade_sports_clinic
#: model:mail.template,body_html:bemade_sports_clinic.mail_template_patient_injury_new_internal_note
msgid ""
"<p>A new internal note has been posted to the injury record for <t t-out=\"object.patient_name\"></t>'s injury<t t-if=\"object.diagnosis\">with\n"
" the diagnosis of <strong t-out=\"object.diagnosis\"></strong></t>.\n"
" </p>\n"
" <div>\n"
" <p><strong>New Note: </strong></p>\n"
" <div><t t-out=\"object.internal_notes\"></t></div>\n"
" </div>\n"
" "
msgstr ""
"<p>Une nouvelle note interne a été publiée dans le dossier du patient <t t-out=\"object.patient_name\"></t><t t-if=\"object.diagnosis\">avec\n"
" pour la blessure <strong t-out=\"object.diagnosis\"></strong></t>.\n"
" </p>\n"
" <div>\n"
" <p><strong>Nouvelle note : </strong></p>\n"
" <div><t t-out=\"object.internal_notes\"></t></div>\n"
" </div>\n"
" "
#. module: bemade_sports_clinic
#: model:mail.template,body_html:bemade_sports_clinic.mail_template_patient_status_update
msgid ""
"<p>An update has been posted to the record for <t t-out=\"object.name\"></t>'s. Click\n"
" <a t-attf-href=\"{{ user.company_id.website }}/my/players\">here</a> to view the details.\n"
" </p>\n"
" "
msgstr ""
"<p>Une mise à jour a été publiée dans le dossier de <t t-out=\"object.name\"></t>'s. Cliquez\n"
" <a t-attf-href=\"{{ user.company_id.website }}/my/players\">ici</a> pour les détails.\n"
" </p>\n"
" "
#. module: bemade_sports_clinic
#: model:mail.template,body_html:bemade_sports_clinic.mail_template_patient_injury_status_update
msgid ""
"<p>An update has been posted to the injury record for <t t-out=\"object.patient_name\"></t>'s injury<t t-if=\"object.diagnosis\">with\n"
" the diagnosis of <strong t-out=\"object.diagnosis\"></strong></t>. Click\n"
" <a t-attf-href=\"{{ user.company_id.website }}/my/player?player_id={{ object.patient_id.id }}\">here</a> to view the injury details.\n"
" </p>\n"
" "
msgstr ""
"<p>Une mise à jour a été publiée par rapport à la blessure de <t t-out=\"object.patient_name\"></t><t t-if=\"object.diagnosis\">avec\n"
" avec le diagnostic <strong t-out=\"object.diagnosis\"></strong></t>. Cliquez\n"
" <a t-attf-href=\"{{ user.company_id.website }}/my/player?player_id={{ object.patient_id.id }}\">ici</a> pour voir les détails de la blessure.\n"
" </p>\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 #. module: bemade_sports_clinic
#. odoo-python #. 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." msgid "A team can have only one head therapist."
msgstr "Une équipe ne peut avoir qu'un seul thérapeute en chef." 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 #. 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__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_patient_injury__message_needaction
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_needaction
msgid "Action Needed" msgid "Action Needed"
msgstr "Action requise" msgstr "Action requise"
@ -69,24 +158,28 @@ msgstr "Nombre de blessures actives"
#. module: bemade_sports_clinic #. 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__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_patient_injury__activity_ids
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_ids
msgid "Activities" msgid "Activities"
msgstr "Activités" msgstr "Activités"
#. module: bemade_sports_clinic #. 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__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_patient_injury__activity_exception_decoration
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_exception_decoration
msgid "Activity Exception Decoration" msgid "Activity Exception Decoration"
msgstr "Décoration d'activité en exception" msgstr "Décoration d'activité en exception"
#. module: bemade_sports_clinic #. 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__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_patient_injury__activity_state
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_state
msgid "Activity State" msgid "Activity State"
msgstr "État de l'activité" msgstr "État de l'activité"
#. module: bemade_sports_clinic #. 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__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_patient_injury__activity_type_icon
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_type_icon
msgid "Activity Type Icon" msgid "Activity Type Icon"
msgstr "Icône du type d'activité" msgstr "Icône du type d'activité"
@ -105,9 +198,22 @@ msgstr "Âge"
msgid "Allergies" msgid "Allergies"
msgstr "" 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 #. 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__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_patient_injury__message_attachment_count
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_attachment_count
msgid "Attachment Count" msgid "Attachment Count"
msgstr "Nombre de pièces jointes" msgstr "Nombre de pièces jointes"
@ -121,6 +227,11 @@ msgstr "Ville"
msgid "Coach" msgid "Coach"
msgstr "Entraîneur" 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 #. module: bemade_sports_clinic
#: model:ir.model,name:bemade_sports_clinic.model_res_partner #: model:ir.model,name:bemade_sports_clinic.model_res_partner
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__partner_id #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__partner_id
@ -200,6 +311,11 @@ msgstr "Nom pour affichage"
msgid "Doctor" msgid "Doctor"
msgstr "Docteur" 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 #. module: bemade_sports_clinic
#: model:ir.model.constraint,message:bemade_sports_clinic.constraint_sports_team_staff_team_staff_unique #: 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." msgid "Each partner can only be related to a given team once."
@ -242,18 +358,21 @@ msgstr "Prénom"
#. module: bemade_sports_clinic #. 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__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_patient_injury__message_follower_ids
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_follower_ids
msgid "Followers" msgid "Followers"
msgstr "Abonnés" msgstr "Abonnés"
#. module: bemade_sports_clinic #. 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__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_patient_injury__message_partner_ids
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_partner_ids
msgid "Followers (Partners)" msgid "Followers (Partners)"
msgstr "Abonnés (partenaires)" msgstr "Abonnés (partenaires)"
#. module: bemade_sports_clinic #. 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__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_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" msgid "Font awesome icon e.g. fa-tasks"
msgstr "Icône Font Awesome ex. fa-tasks" msgstr "Icône Font Awesome ex. fa-tasks"
@ -265,6 +384,7 @@ msgstr "Donner accès au portail"
#. module: bemade_sports_clinic #. 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__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_patient_injury__has_message
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__has_message
msgid "Has Message" msgid "Has Message"
msgstr "A un message" msgstr "A un message"
@ -317,18 +437,21 @@ msgstr ""
#. module: bemade_sports_clinic #. 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__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_patient_injury__activity_exception_icon
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_exception_icon
msgid "Icon" msgid "Icon"
msgstr "Icône" msgstr "Icône"
#. module: bemade_sports_clinic #. 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__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_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." msgid "Icon to indicate an exception activity."
msgstr "Icône pour indiquer une activité en exception." msgstr "Icône pour indiquer une activité en exception."
#. module: bemade_sports_clinic #. 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__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_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." msgid "If checked, new messages require your attention."
msgstr "Si coché, des nouveaux messages nécessitent votre 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__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_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_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." msgid "If checked, some messages have a delivery error."
msgstr "Si coché, certains messages ont une erreur de livraison." 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 #: code:addons/bemade_sports_clinic/models/patient_injury.py:0
#, python-format #, python-format
msgid "If injury date is not set, the N/A box must be checked." 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 #. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__stage__no_play #: 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" msgid "Injury Record for"
msgstr "Historique des blessures pour" 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 #. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__internal_notes #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__internal_notes
msgid "Internal Notes" msgid "Internal Notes"
@ -404,6 +540,7 @@ msgstr "Combinaison invalide des statuts de match et de pratique."
#. module: bemade_sports_clinic #. 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__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_patient_injury__message_is_follower
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_is_follower
msgid "Is Follower" msgid "Is Follower"
msgstr "Est abonné" msgstr "Est abonné"
@ -461,6 +598,7 @@ msgstr "Dernière mise à jour le"
#. module: bemade_sports_clinic #. 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__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_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" msgid "Main Attachment"
msgstr "Pièce jointe principale" msgstr "Pièce jointe principale"
@ -474,12 +612,14 @@ msgstr "Statut match"
#. module: bemade_sports_clinic #. 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__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_patient_injury__message_has_error
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_has_error
msgid "Message Delivery error" msgid "Message Delivery error"
msgstr "Erreur de livraison du message" msgstr "Erreur de livraison du message"
#. module: bemade_sports_clinic #. 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__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_patient_injury__message_ids
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_ids
msgid "Messages" msgid "Messages"
msgstr "" msgstr ""
@ -498,6 +638,7 @@ msgstr "Mère"
#. module: bemade_sports_clinic #. 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__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_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" msgid "My Activity Deadline"
msgstr "Date limite de mon activité" msgstr "Date limite de mon activité"
@ -521,18 +662,21 @@ msgstr "Nom"
#. module: bemade_sports_clinic #. 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__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_patient_injury__activity_date_deadline
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_date_deadline
msgid "Next Activity Deadline" msgid "Next Activity Deadline"
msgstr "Échéance de la prochaine activité" msgstr "Échéance de la prochaine activité"
#. module: bemade_sports_clinic #. 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__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_patient_injury__activity_summary
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_summary
msgid "Next Activity Summary" msgid "Next Activity Summary"
msgstr "Résumé de la prochaine activité" msgstr "Résumé de la prochaine activité"
#. module: bemade_sports_clinic #. 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__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_patient_injury__activity_type_id
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_type_id
msgid "Next Activity Type" msgid "Next Activity Type"
msgstr "Type de la prochaine activité" msgstr "Type de la prochaine activité"
@ -552,24 +696,28 @@ msgstr ""
#. module: bemade_sports_clinic #. 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__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_patient_injury__message_needaction_counter
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__message_needaction_counter
msgid "Number of Actions" msgid "Number of Actions"
msgstr "Nombre d'actions" msgstr "Nombre d'actions"
#. module: bemade_sports_clinic #. 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__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_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" msgid "Number of errors"
msgstr "Nombre d'erreurs" msgstr "Nombre d'erreurs"
#. module: bemade_sports_clinic #. 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__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_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" msgid "Number of messages requiring action"
msgstr "Nombre de messages nécessitant une action" msgstr "Nombre de messages nécessitant une action"
#. module: bemade_sports_clinic #. 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__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_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" msgid "Number of messages with delivery error"
msgstr "Nombre de messages avec une erreur de livraison" msgstr "Nombre de messages avec une erreur de livraison"
@ -608,11 +756,6 @@ msgstr "Équipe détenue"
msgid "Parent Organization" msgid "Parent Organization"
msgstr "Organisation parente" 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 #. module: bemade_sports_clinic
#: model:ir.model,name:bemade_sports_clinic.model_sports_patient #: model:ir.model,name:bemade_sports_clinic.model_sports_patient
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_res_partner__patient_ids #: 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" msgstr "Contacts du patient"
#. module: bemade_sports_clinic #. module: bemade_sports_clinic
#: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_update #: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_external_update
msgid "Patient File Update" #: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_injury_external_update
msgstr "Mise à jour du dossier du patient" 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 #. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form #: 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" msgstr "Historique des blessures du patient"
#. module: bemade_sports_clinic #. module: bemade_sports_clinic
#: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_injury_update #: model:mail.template,subject:bemade_sports_clinic.mail_template_patient_injury_status_update
msgid "Patient Injury Status Update" msgid "Patient Injury Status Update for {{ object.patient_name }}"
msgstr "Mise à jour du statut de la blessure d'un patient" msgstr "Mise à jour du dossier de la blessure de {{ object.patient_name }}"
#. module: bemade_sports_clinic #. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form #: 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" msgid "Patient Record"
msgstr "Dossier du patient" 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 #. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form #: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form
msgid "Patient's Contacts" msgid "Patient's Contacts"
msgstr "Contacts du patient" 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 #. module: bemade_sports_clinic
#: model:ir.actions.act_window,name:bemade_sports_clinic.action_view_patient #: model:ir.actions.act_window,name:bemade_sports_clinic.action_view_patient
#: model:ir.ui.menu,name:bemade_sports_clinic.sports_clinic_patients #: model:ir.ui.menu,name:bemade_sports_clinic.sports_clinic_patients
@ -749,6 +902,7 @@ msgstr "Résolu"
#. module: bemade_sports_clinic #. 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__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_patient_injury__activity_user_id
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__activity_user_id
msgid "Responsible User" msgid "Responsible User"
msgstr "Utilisateur responsable" msgstr "Utilisateur responsable"
@ -770,6 +924,7 @@ msgstr "Rôle"
#. module: bemade_sports_clinic #. 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__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_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" msgid "SMS Delivery error"
msgstr "Erreur de livraison SMS" msgstr "Erreur de livraison SMS"
@ -836,6 +991,7 @@ msgstr "État"
#. module: bemade_sports_clinic #. 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__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_patient_injury__activity_state
#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__activity_state
msgid "" msgid ""
"Status based on activities\n" "Status based on activities\n"
"Overdue: Due date is already passed\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 #: code:addons/bemade_sports_clinic/models/res_partner.py:0
#, python-format #, python-format
msgid "To change a patient's name, change it from the patient form." 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 #. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_teams #: 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 #. 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__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_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." msgid "Type of the exception activity on record."
msgstr "Type d'exception de l'activité enregistrée." msgstr "Type d'exception de l'activité enregistrée."
@ -960,6 +1118,11 @@ msgstr "Type d'exception de l'activité enregistrée."
msgid "User" msgid "User"
msgstr "Utilisateur" 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 #. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team_staff__user_ids #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team_staff__user_ids
msgid "Users" msgid "Users"
@ -973,12 +1136,14 @@ msgstr "Site web"
#. module: bemade_sports_clinic #. 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__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_patient_injury__website_message_ids
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__website_message_ids
msgid "Website Messages" msgid "Website Messages"
msgstr "Messages du site web" msgstr "Messages du site web"
#. module: bemade_sports_clinic #. 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__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_patient_injury__website_message_ids
#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_team__website_message_ids
msgid "Website communication history" msgid "Website communication history"
msgstr "Historique de communication du site web" msgstr "Historique de communication du site web"
@ -994,8 +1159,8 @@ msgid ""
"Whether the patient has given their consent to share injury details with " "Whether the patient has given their consent to share injury details with "
"their parents." "their parents."
msgstr "" msgstr ""
"Indique si le patient a donné son consentement pour que les détails de la blessure soient partagés avec " "Indique si le patient a donné son consentement pour que les détails de la "
"ses parents." "blessure soient partagés avec ses parents."
#. module: bemade_sports_clinic #. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__match_status__yes #: 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 #: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__zip
msgid "Zip" msgid "Zip"
msgstr "Code postal" 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"

View file

@ -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"')

View file

@ -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()

View file

@ -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

View file

@ -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",
])

View file

@ -3,105 +3,113 @@ from odoo.exceptions import ValidationError
from datetime import date from datetime import date
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo.addons.phone_validation.tools import phone_validation from odoo.addons.phone_validation.tools import phone_validation
from typing import Set, Tuple import logging
_logger = logging.getLogger(__name__)
external_tracking_fields = { external_tracking_fields = {
'last_consultation_date', "last_consultation_date",
'match_status', "match_status",
'practice_status', "practice_status",
'predicted_return_date', "predicted_return_date",
'return_date', "return_date",
} }
internal_tracking_fields = { internal_tracking_fields = {
'team_info_notes', "team_info_notes",
'age', "age",
'date_of_birth', "date_of_birth",
} }
class Patient(models.Model): class Patient(models.Model):
_name = 'sports.patient' _name = "sports.patient"
_description = "Patient" _description = "Patient"
_inherit = ['mail.thread', 'mail.activity.mixin'] _inherit = ["mail.thread", "mail.activity.mixin"]
_order = 'last_name, first_name' _order = "last_name, first_name"
# res.partner fields # 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) first_name = fields.Char(required=True, tracking=True)
last_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) name = fields.Char(
phone = fields.Char(related='partner_id.phone', readonly=False) related="partner_id.name",
mobile = fields.Char(related='partner_id.mobile', readonly=False) )
street = fields.Char(related='partner_id.street', readonly=False) phone = fields.Char(related="partner_id.phone", readonly=False)
street2 = fields.Char(related='partner_id.street2', readonly=False) mobile = fields.Char(related="partner_id.mobile", readonly=False)
city = fields.Char(related='partner_id.city', readonly=False) street = fields.Char(related="partner_id.street", readonly=False)
state_id = fields.Many2one(related='partner_id.state_id', readonly=False) street2 = fields.Char(related="partner_id.street2", readonly=False)
zip = fields.Char(related='partner_id.zip', readonly=False) city = fields.Char(related="partner_id.city", readonly=False)
country_id = fields.Many2one(related='partner_id.country_id', readonly=False) state_id = fields.Many2one(related="partner_id.state_id", readonly=False)
email = fields.Char(related='partner_id.email', 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 # Patient fields
date_of_birth = fields.Date( date_of_birth = fields.Date(
groups="bemade_sports_clinic.group_sports_clinic_treatment_professional", groups="bemade_sports_clinic.group_sports_clinic_treatment_professional",
tracking=True) tracking=True,
)
age = fields.Integer( age = fields.Integer(
compute='_compute_age', compute="_compute_age",
groups="bemade_sports_clinic.group_sports_clinic_treatment_professional" groups="bemade_sports_clinic.group_sports_clinic_treatment_professional",
) )
contact_ids = fields.One2many( contact_ids = fields.One2many(
comodel_name='sports.patient.contact', comodel_name="sports.patient.contact",
inverse_name='patient_id', inverse_name="patient_id",
string='Patient Contacts', string="Patient Contacts",
groups="bemade_sports_clinic.group_sports_clinic_user" groups="bemade_sports_clinic.group_sports_clinic_user",
) )
team_ids = fields.Many2many( team_ids = fields.Many2many(
comodel_name='sports.team', comodel_name="sports.team",
relation='sports_team_patient_rel', relation="sports_team_patient_rel",
column1='patient_id', column1="patient_id",
column2='team_id', column2="team_id",
string='Teams', 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=[ selection=[
('yes', 'Yes'), ("yes", "Yes"),
('no', 'No'), ("no", "No"),
], ],
required=True, required=True,
default='yes', default="yes",
tracking=True) tracking=True,
)
practice_status = fields.Selection( practice_status = fields.Selection(
selection=[ selection=[("yes", "Yes"), ("no_contact", "Yes, no contact"), ("no", "No")],
('yes', 'Yes'),
('no_contact', 'Yes, no contact'),
('no', 'No')
],
tracking=True, tracking=True,
required=True, required=True,
default='yes', default="yes",
) )
injury_ids = fields.One2many( injury_ids = fields.One2many(
comodel_name='sports.patient.injury', comodel_name="sports.patient.injury",
inverse_name='patient_id', inverse_name="patient_id",
string='Injuries', 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) predicted_return_date = fields.Date(tracking=True)
return_date = fields.Date( return_date = fields.Date(
tracking=True, tracking=True,
help="When the player was cleared by medical staff to " help="When the player was cleared by medical staff to " "return to match play.",
"return to match play."
) )
is_injured = fields.Boolean(compute="_compute_is_injured") is_injured = fields.Boolean(compute="_compute_is_injured")
stage = fields.Selection( stage = fields.Selection(
selection=[ selection=[
('no_play', 'Injured'), ("no_play", "Injured"),
('practice_ok', 'Practice OK'), ("practice_ok", "Practice OK"),
('healthy', 'Play OK') ("healthy", "Play OK"),
], ],
compute='_compute_stage') compute="_compute_stage",
)
last_consultation_date = fields.Date(tracking=True) 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() allergies = fields.Text()
team_info_notes = fields.Html( team_info_notes = fields.Html(
string="Notes", string="Notes",
@ -110,55 +118,92 @@ class Patient(models.Model):
def default_get(self, fields_list): def default_get(self, fields_list):
res = super().default_get(fields_list) res = super().default_get(fields_list)
if 'team_ids' in fields_list and 'params' in self.env.context \ if (
and self.env.context.get('params')['model'] == 'sports.team': "team_ids" in fields_list
team = self.env['sports.team'].browse(self.env.context.get('params')['id']) 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])] team_ids = [Command.set([team.id])]
if team_ids: if team_ids:
res.update({'team_ids': team_ids}) res.update({"team_ids": team_ids})
return res 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 @api.model_create_multi
def create(self, vals_list): def create(self, vals_list):
for row in vals_list: for row in vals_list:
if 'partner_id' not in row: if "partner_id" not in row:
row['partner_id'] = self.env['res.partner'].create({ row["partner_id"] = (
'name': self._get_name_from_first_and_last(row['first_name'], row['last_name']) self.env["res.partner"]
}).id .create(
return super().create(vals_list) {
"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): def constrain_match_and_practice_status(self):
""" Avoid invalid combinations of match and practice status: """Avoid invalid combinations of match and practice status:
- Yes (match), No (practice) - Yes (match), No (practice)
- Yes (match), No Contact (practice) - Yes (match), No Contact (practice)
""" """
# combinations of (match_status, practice_status) that are valid # 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: for rec in self:
if (rec.match_status, rec.practice_status) not in valid_combinations: 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): def _compute_active_injury_count(self):
for rec in 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): def _compute_stage(self):
stage_map = { stage_map = {
('yes', 'yes'): 'healthy', ("yes", "yes"): "healthy",
('no', 'yes'): 'practice_ok', ("no", "yes"): "practice_ok",
('no', 'no_contact'): 'practice_ok', ("no", "no_contact"): "practice_ok",
('no', 'no'): 'no_play', ("no", "no"): "no_play",
} }
for rec in self: 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: 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 continue
rec.stage = stage_map[(rec.match_status, rec.practice_status)] rec.stage = stage_map[(rec.match_status, rec.practice_status)]
@api.depends('date_of_birth') @api.depends("date_of_birth")
def _compute_age(self): def _compute_age(self):
for rec in self: for rec in self:
if not rec.date_of_birth: if not rec.date_of_birth:
@ -166,56 +211,60 @@ class Patient(models.Model):
else: else:
rec.age = relativedelta(date.today(), rec.date_of_birth).years 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 @api.model
def _get_name_from_first_and_last(self, first_name, last_name): 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): def _compute_is_injured(self):
for rec in 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: if rec.is_injured:
unresolved_injuries = rec.injury_ids.filtered(lambda r: not r.stage == 'resolved') unresolved_injuries = rec.injury_ids.filtered(
rec.injured_since = unresolved_injuries and unresolved_injuries[0].injury_date lambda r: not r.stage == "resolved"
)
rec.injured_since = (
unresolved_injuries and unresolved_injuries[0].injury_date
)
else: else:
rec.injured_since = False rec.injured_since = False
def action_view_patient_form(self): def action_view_patient_form(self):
self.ensure_one() self.ensure_one()
return { return {
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'view_mode': 'form', "view_mode": "form",
'res_model': 'sports.patient', "res_model": "sports.patient",
'res_id': self.id, "res_id": self.id,
'context': self._context, "context": self._context,
} }
def action_consulted_today(self): def action_consulted_today(self):
self.ensure_one() # should just be called from form view self.ensure_one() # should just be called from form view
self.last_consultation_date = date.today() self.last_consultation_date = date.today()
return { return {
'view_mode': 'form', "view_mode": "form",
'res_model': 'sports.patient', "res_model": "sports.patient",
'context': self._context, "context": self._context,
'res_id': self.id, "res_id": self.id,
} }
@api.onchange('mobile', 'country_id') @api.onchange("mobile", "country_id")
def _onchange_mobile_validation(self): def _onchange_mobile_validation(self):
if self.mobile: if self.mobile:
self.mobile = self._phone_format(self.mobile, force_format="INTERNATIONAL") 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): def _onchange_phone_validation(self):
if self.phone: if self.phone:
self.phone = self._phone_format(self.phone, force_format="INTERNATIONAL") 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 country = self.country_id or self.env.company.country_id
if not country or not number: if not country or not number:
return number return number
@ -224,11 +273,11 @@ class Patient(models.Model):
country.code if country else None, country.code if country else None,
country.phone_code if country else None, country.phone_code if country else None,
force_format=force_format, force_format=force_format,
raise_exception=False raise_exception=False,
) )
def _track_subtype(self, init_values): 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): def _track_template(self, changes):
res = super()._track_template(changes) res = super()._track_template(changes)
@ -237,18 +286,47 @@ class Patient(models.Model):
if external: if external:
first_external_field = (external_tracking_fields & params).pop() first_external_field = (external_tracking_fields & params).pop()
res[first_external_field] = ( res[first_external_field] = (
self.env.ref('bemade_sports_clinic.mail_template_patient_status_update'), { self.env.ref(
'auto_delete_message': False, "bemade_sports_clinic.mail_template_patient_status_update"
'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_external_update').id, ),
'email_layout_xmlid': 'mail.mail_notification_light', {
} "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: if "team_info_notes" in changes:
res['team_info_notes'] = ( res["team_info_notes"] = (
self.env.ref('bemade_sports_clinic.mail_template_patient_new_internal_note'), { self.env.ref(
'auto_delete_message': False, "bemade_sports_clinic.mail_template_patient_new_internal_note"
'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_internal_update').id, ),
'email_layout_xmlid': 'mail.mail_notification_light', {
} "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 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)

View file

@ -7,13 +7,13 @@ class PatientContact(models.Model):
_description = "Emergency or other contacts for a patient." _description = "Emergency or other contacts for a patient."
sequence = fields.Integer(required=True, default=0) sequence = fields.Integer(required=True, default=0)
name = fields.Char(unaccent=False) name = fields.Char(unaccent=True)
contact_type = fields.Selection(selection=[ contact_type = fields.Selection(selection=[
('mother', 'Mother'), ('mother', 'Mother'),
('father', 'Father'), ('father', 'Father'),
('other', 'Other'), ('other', 'Other'),
], required=True) ], required=True)
mobile = fields.Char(unaccent=False, required=True) mobile = fields.Char(unaccent=False)
# TODO: add email here and on views # TODO: add email here and on views
patient_id = fields.Many2one(comodel_name='sports.patient', string='Patient') patient_id = fields.Many2one(comodel_name='sports.patient', string='Patient')

View file

@ -2,132 +2,138 @@ from odoo import models, fields, api, _
from datetime import datetime, date from datetime import datetime, date
import pytz import pytz
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from typing import Set, Tuple import logging
_logger = logging.getLogger(__name__)
external_tracking_fields = { external_tracking_fields = {
'diagnosis', "diagnosis",
'predicted_resolution_date', "predicted_resolution_date",
'resolution_date', "resolution_date",
'external_notes', "external_notes",
} }
# Include only fields not already included in external_tracking_fields here # Include only fields not already included in external_tracking_fields here
internal_tracking_fields = { internal_tracking_fields = {
'internal_notes', "internal_notes",
'parental_consent', "parental_consent",
} }
class PatientInjury(models.Model): class PatientInjury(models.Model):
_name = 'sports.patient.injury' _name = "sports.patient.injury"
_description = "Patient Injury" _description = "Patient Injury"
_inherit = ['mail.thread', 'mail.activity.mixin'] _inherit = ["mail.thread", "mail.activity.mixin"]
_rec_name = 'diagnosis' _rec_name = "diagnosis"
@api.model @api.model
def _today(self): def _today(self):
"""Get the current date in the user's time zone.""" """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: Find a way to improve notifications send about tracking injury details
# TODO: Add field consentement_parental = fields.Selection(oui, non, non-applicable) # TODO: Add field consentement_parental = fields.Selection(oui, non, non-applicable)
patient_id = fields.Many2one( patient_id = fields.Many2one(
comodel_name='sports.patient', comodel_name="sports.patient",
string="Patient", string="Patient",
readonly=True, readonly=True,
required=True required=True,
ondelete="cascade",
) )
patient_name = fields.Char(related="patient_id.name") patient_name = fields.Char(related="patient_id.name")
diagnosis = fields.Char(tracking=True) diagnosis = fields.Char(tracking=True)
injury_date = fields.Date( injury_date = fields.Date(
string='Date of Injury', string="Date of Injury",
default=_today, default=_today,
) )
injury_date_na = fields.Boolean(string="N/A", default=False) injury_date_na = fields.Boolean(string="N/A", default=False)
internal_notes = fields.Html(tracking=True) internal_notes = fields.Html(tracking=True)
external_notes = fields.Html(tracking=True) external_notes = fields.Html(tracking=True)
treatment_professional_ids = fields.Many2many( treatment_professional_ids = fields.Many2many(
comodel_name='res.users', comodel_name="res.users",
relation='patient_injury_treatment_pro_rel', relation="patient_injury_treatment_pro_rel",
column1='patient_injury_id', column1="patient_injury_id",
column2='treatment_pro_id', string='Treatment Professionals', column2="treatment_pro_id",
domain=[ string="Treatment Professionals",
('is_treatment_professional', '=', domain=[("is_treatment_professional", "=", True)],
True)], tracking=True tracking=True,
) )
predicted_resolution_date = fields.Date(tracking=True) predicted_resolution_date = fields.Date(tracking=True)
resolution_date = fields.Date( resolution_date = fields.Date(
tracking=True, tracking=True, help="The date when the injury was actually resolved."
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( parental_consent = fields.Selection(
string="Consent for Disclosure to Parent", string="Consent for Disclosure to Parent",
selection=[ selection=[("yes", "Yes"), ("no", "No"), ("na", "N/A")],
('yes', 'Yes'),
('no', 'No'),
('na', 'N/A')
],
help="Whether the patient has given their consent to share injury details with their parents.", help="Whether the patient has given their consent to share injury details with their parents.",
tracking=True, tracking=True,
) )
@api.constrains('injury_date_na', 'injury_date') @api.constrains("injury_date_na", "injury_date")
def constrain_date_blank_only_if_na(self): def constrain_date_blank_only_if_na(self):
for rec in self: for rec in self:
if not rec.injury_date_na and not rec.injury_date: 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): def _onchange_injury_date_na(self):
for rec in self: for rec in self:
if rec.injury_date_na: if rec.injury_date_na:
rec.injury_date = None rec.injury_date = None
@api.onchange('injury_date') @api.onchange("injury_date")
def _onchange_injury_date(self): def _onchange_injury_date(self):
for rec in self: for rec in self:
if rec.injury_date: if rec.injury_date:
rec.injury_date_na = False rec.injury_date_na = False
@api.depends('resolution_date') @api.depends("resolution_date")
def _compute_stage(self): def _compute_stage(self):
for rec in self: for rec in self:
if rec.resolution_date and rec.resolution_date <= date.today(): if rec.resolution_date and rec.resolution_date <= date.today():
rec.stage = 'resolved' rec.stage = "resolved"
else: 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'))
self.message_subscribe(to_subscribe.ids)
@api.model_create_multi @api.model_create_multi
def create(self, vals_list): def create(self, vals_list):
res = super().create(vals_list) res = super().create(vals_list)
for rec in res: for rec in res.sudo():
to_subscribe = (rec.treatment_professional_ids.mapped('partner_id') rec.message_subscribe(rec.patient_id.message_partner_ids)
- rec.message_follower_ids.mapped('partner_id')) msg_body = _("A new injury was created for this patient.")
rec.message_subscribe(to_subscribe.ids) if rec.diagnosis:
msg_body += _(" Diagnosis: %s." % rec.diagnosis)
rec.patient_id.message_post(body=msg_body, message_type="comment")
return res 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): def action_view_injury_form(self):
self.ensure_one() self.ensure_one()
return { return {
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'view_mode': 'form', "view_mode": "form",
'res_model': 'sports.patient.injury', "res_model": "sports.patient.injury",
'res_id': self.id, "res_id": self.id,
'context': self._context, "context": self._context,
} }
def _track_subtype(self, init_values): 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): def _track_template(self, changes):
res = super()._track_template(changes) res = super()._track_template(changes)
@ -136,18 +142,34 @@ class PatientInjury(models.Model):
if external: if external:
first_external_field = (external_tracking_fields & params).pop() first_external_field = (external_tracking_fields & params).pop()
res[first_external_field] = ( res[first_external_field] = (
self.env.ref('bemade_sports_clinic.mail_template_patient_injury_status_update'), { self.env.ref(
'auto_delete_message': False, "bemade_sports_clinic.mail_template_patient_injury_status_update"
'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_injury_external_update').id, ),
'email_layout_xmlid': 'mail.mail_notification_light', {
} "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: if "internal_notes" in changes:
res['internal_notes'] = ( res["internal_notes"] = (
self.env.ref('bemade_sports_clinic.mail_template_patient_injury_new_internal_note'), { self.env.ref(
'auto_delete_message': False, "bemade_sports_clinic.mail_template_patient_injury_new_internal_note"
'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_injury_internal_update').id, ),
'email_layout_xmlid': 'mail.mail_notification_light', {
} "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 return res
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
res.patient_id.recompute_followers()
return res

View file

@ -4,25 +4,54 @@ from odoo.exceptions import ValidationError
class Partner(models.Model): class Partner(models.Model):
_inherit = 'res.partner' _inherit = "res.partner"
owned_team_ids = fields.One2many(comodel_name='sports.team', owned_team_ids = fields.One2many(
inverse_name='parent_id') comodel_name="sports.team", inverse_name="parent_id"
staff_ids = fields.One2many(comodel_name='sports.team.staff', )
inverse_name='team_id') staff_ids = fields.One2many(
team_staff_rel_ids = fields.One2many(comodel_name='sports.team.staff', comodel_name="sports.team.staff", inverse_name="team_id"
inverse_name='partner_id', )
string='Teams Served', team_staff_rel_ids = fields.One2many(
help='The teams this person works for.') comodel_name="sports.team.staff",
teams_served_ids = fields.One2many(comodel_name='sports.team', compute='_compute_teams_served') inverse_name="partner_id",
patient_ids = fields.One2many(comodel_name='sports.patient', 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): def write(self, vals):
if self.patient_ids and 'name' in vals: if (
raise ValidationError(_("To change a patient's name, change it from the patient form.")) 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) 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): def _compute_teams_served(self):
for rec in 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.")
)

View file

@ -1,14 +1,45 @@
from odoo import models, fields, api, _ from odoo import models, fields, api, _, Command
class User(models.Model): class User(models.Model):
_inherit = 'res.users' _inherit = "res.users"
is_treatment_professional = fields.Boolean( 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): def _compute_is_treatment_professional(self):
for rec in self: for rec in self:
rec.is_treatment_professional = rec.has_group( 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
]
)

View file

@ -5,105 +5,240 @@ from odoo.exceptions import ValidationError
class SportsTeam(models.Model): class SportsTeam(models.Model):
_name = "sports.team" _name = "sports.team"
_description = "Sports Team" _description = "Sports Team"
_inherit = ["mail.thread", "mail.activity.mixin"]
name = fields.Char() name = fields.Char()
patient_ids = fields.Many2many(comodel_name='sports.patient', patient_ids = fields.Many2many(
relation='sports_team_patient_rel', comodel_name="sports.patient",
column1='team_id', relation="sports_team_patient_rel",
column2='patient_id', column1="team_id",
string='Players') column2="patient_id",
string="Players",
tracking=True,
)
player_count = fields.Integer(compute="_compute_player_counts") player_count = fields.Integer(compute="_compute_player_counts")
injured_count = fields.Integer(compute="_compute_player_counts") injured_count = fields.Integer(compute="_compute_player_counts")
healthy_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', parent_id = fields.Many2one(
ondelete='restrict') comodel_name="res.partner",
staff_ids = fields.One2many(comodel_name='sports.team.staff', string="Parent Organization",
inverse_name='team_id') ondelete="restrict",
head_coach_id = fields.Many2one(comodel_name='res.partner', tracking=True,
compute='_compute_head_coach', )
store=True) staff_ids = fields.One2many(
head_coach_name = fields.Char(related='head_coach_id.name') comodel_name="sports.team.staff",
head_therapist_id = fields.Many2one(comodel_name='res.partner', inverse_name="team_id",
compute='_compute_head_therapist', tracking=True,
store=True) )
head_therapist_name = fields.Char(related='head_therapist_id.name') 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() 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): def _compute_player_counts(self):
for rec in self: for rec in self:
rec.player_count = len(rec.patient_ids) rec.player_count = len(rec.patient_ids)
rec.injured_count = len(rec.patient_ids.filtered(lambda p: p.is_injured)) rec.injured_count = len(rec.patient_ids.filtered(lambda p: p.is_injured))
rec.healthy_count = rec.player_count - rec.injured_count rec.healthy_count = rec.player_count - rec.injured_count
@api.depends('staff_ids.role') @api.depends("staff_ids.role")
def _compute_head_coach(self): def _compute_head_coach(self):
for rec in 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 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): def _compute_head_therapist(self):
for rec in 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 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): class TeamStaff(models.Model):
_name = "sports.team.staff" _name = "sports.team.staff"
_description = "Sports Team Staff" _description = "Sports Team Staff"
sequence = fields.Integer() sequence = fields.Integer()
team_id = fields.Many2one(comodel_name='sports.team', string='Team', required=True) team_id = fields.Many2one(
partner_id = fields.Many2one(comodel_name='res.partner', string='Staff Member', comodel_name="sports.team",
required=True, domain=[('is_company', '=', False)]) string="Team",
role = fields.Selection(selection=[ required=True,
('head_coach', 'Head Coach'), ondelete="cascade",
('head_therapist', 'Head Therapist'), )
('coach', 'Coach'), partner_id = fields.Many2one(
('therapist', 'Therapist'), comodel_name="res.partner",
('doctor', 'Doctor'), string="Staff Member",
('other', 'Other') required=True,
], required=True) domain=[("is_company", "=", False)],
mobile = fields.Char(related='partner_id.mobile', readonly=False) ondelete="cascade",
name = fields.Char(related='partner_id.name', readonly=False) )
parent_id = fields.Many2one(related='partner_id.parent_id', readonly=False, string="Organization", active = fields.Boolean(related="partner_id.active")
domain=[('is_company', '=', True)]) role = fields.Selection(
email = fields.Char(related='partner_id.email', readonly=False) selection=[
user_ids = fields.One2many(related='partner_id.user_ids', readonly=True) ("head_coach", "Head Coach"),
has_portal_access = fields.Boolean(compute='_compute_has_portal_access', compute_sudo=True) ("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)', _sql_constraints = [
'Each partner can only be related to a given team once.')] (
"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): def _constrain_role(self):
teams = self.mapped('team_id') teams = self.mapped("team_id")
for team in teams: 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.")) 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.")) raise ValidationError(_("A team can have only one head therapist."))
@api.onchange('mobile') @api.onchange("mobile")
def _onchange_mobile_validation(self): def _onchange_mobile_validation(self):
if self.mobile: 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): def _compute_has_portal_access(self):
for rec in 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.has_portal_access = (
rec.user_ids.filtered(lambda r: r.has_group('base.group_user'))) or bool(rec.partner_id.signup_token) 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): def action_revoke_portal_access(self):
group_portal = self.env.ref('base.group_portal') group_portal = self.env.ref("base.group_portal")
group_public = self.env.ref('base.group_public') group_public = self.env.ref("base.group_public")
self.user_ids.write( 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 # Remove the signup token, so it cannot be used
self.partner_id.sudo().signup_token = False self.partner_id.sudo().signup_token = False
def action_grant_portal_access(self): 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() 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)

View file

@ -1,12 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<odoo> <odoo>
<data noupdate="1"> <data>
<record id="module_category_sports_clinic_management" model="ir.module.category"> <record id="module_category_sports_clinic_management" model="ir.module.category">
<field name="name">Sports Medicine Clinic Management</field> <field name="name">Sports Medicine Clinic Management</field>
</record> </record>
<record id="group_sports_clinic_user" model="res.groups"> <record id="group_sports_clinic_user" model="res.groups">
<field name="name">Internal User</field> <field name="name">Internal User</field>
<field name="category_id" ref="module_category_sports_clinic_management"/> <field name="category_id" ref="module_category_sports_clinic_management"/>
<field name="implied_ids" eval="[
Command.set(
[
ref('base.group_user'),
ref('base.group_partner_manager'),
]
)
]"/>
</record> </record>
<record id="group_sports_clinic_treatment_professional" model="res.groups"> <record id="group_sports_clinic_treatment_professional" model="res.groups">
<field name="name">Treatment Professional</field> <field name="name">Treatment Professional</field>

View file

@ -2,13 +2,111 @@
<odoo> <odoo>
<data noupdate="1"> <data noupdate="1">
<!-- Refuse access to patient records that are not part of staff user's teams --> <!-- Refuse access to patient records that are not part of staff user's teams -->
<record id="restrict_team_access_to_team_players" model="ir.rule"> <record id="restrict_staff_access_to_team_players" model="ir.rule">
<field name="name">Restrict Team Staff Access to Their Players Only</field> <field name="name">Restrict Team Staff Access to Their Players Only</field>
<field name="model_id" ref="model_sports_patient"/> <field name="model_id" ref="model_sports_patient"/>
<field name="groups" eval="[(6, 0, [ref('base.group_portal')])]"/> <field name="groups"
eval="[(6, 0, [ref('base.group_portal'), ref('base.group_user')])]"/>
<field name="perm_create" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="domain_force"> <field name="domain_force">
[('id', 'in', [('team_ids.staff_ids.user_ids', 'in', user.id)]
user.partner_id.team_staff_rel_ids.team_id.patient_ids.ids)] </field>
</record>
<record id="restrict_staff_access_to_teams" model="ir.rule">
<field name="name">Restrict Team Staff Access to Their Teams Only</field>
<field name="model_id" ref="model_sports_team"/>
<field name="groups"
eval="[(6, 0, [ref('base.group_portal'), ref('base.group_user')])]"/>
<field name="perm_create" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="domain_force">
[('staff_ids.user_ids', 'in', user.id)]
</field>
</record>
<record id="restrict_staff_access_to_team_injuries" model="ir.rule">
<field name="name">Restrict Team Staff Access to Their Teams Only</field>
<field name="model_id" ref="model_sports_patient_injury"/>
<field name="groups"
eval="[(6, 0, [ref('base.group_portal'), ref('base.group_user')])]"/>
<field name="perm_create" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="domain_force">
[('patient_id.team_ids.staff_ids.user_ids', 'in', user.id)]
</field>
</record>
<record id="restrict_patient_contact_access_to_allowed_internal_users"
model="ir.rule">
<field name="name">Restrict Patient Contact Access to Allowed Internal
Users
</field>
<field name="model_id" ref="model_sports_patient_contact"/>
<field name="groups" eval="[(6, 0, [ref('base.group_user')])]"/>
<field name="perm_create" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="domain_force">
[('patient_id.team_ids.staff_ids.user_ids', 'in', user.id)]
</field>
</record>
<record id="allow_team_access_to_admins" model="ir.rule">
<field name="name">Allow Team Access to Administrators</field>
<field name="model_id" ref="model_sports_team"/>
<field name="groups"
eval="[(6, 0, [ref('base.group_system'), ref('bemade_sports_clinic.group_sports_clinic_admin')])]"/>
<field name="perm_create" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="domain_force">
[(1, '=', 1)]
</field>
</record>
<record id="allow_patient_access_to_admins" model="ir.rule">
<field name="name">Allow Patient Access to Administrators</field>
<field name="model_id" ref="model_sports_patient"/>
<field name="groups"
eval="[(6, 0, [ref('base.group_system'), ref('bemade_sports_clinic.group_sports_clinic_admin')])]"/>
<field name="perm_create" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="domain_force">
[(1, '=', 1)]
</field>
</record>
<record id="allow_injury_access_to_admins" model="ir.rule">
<field name="name">Allow Injury Access to Administrators</field>
<field name="model_id" ref="model_sports_patient_injury"/>
<field name="groups"
eval="[(6, 0, [ref('base.group_system'), ref('bemade_sports_clinic.group_sports_clinic_admin')])]"/>
<field name="perm_create" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="domain_force">
[(1, '=', 1)]
</field>
</record>
<record id="allow_patient_contact_access_to_admins" model="ir.rule">
<field name="name">Allow Sports Patient Contact Access to Administrators
</field>
<field name="model_id" ref="model_sports_patient_contact"/>
<field name="groups"
eval="[(6, 0, [ref('base.group_system'), ref('bemade_sports_clinic.group_sports_clinic_admin')])]"/>
<field name="perm_create" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="domain_force">
[(1, '=', 1)]
</field> </field>
</record> </record>
</data> </data>

View file

@ -0,0 +1,3 @@
from . import test_patient
from . import test_rights
from . import test_users

View file

@ -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])
)

View file

@ -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

View file

@ -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)

View file

@ -8,7 +8,16 @@
<page name="contact_addresses" position="after"> <page name="contact_addresses" position="after">
<page name="Teams"> <page name="Teams">
<field invisible="is_company == False" name="owned_team_ids" string="Teams"/> <field invisible="is_company == False" name="owned_team_ids" string="Teams"/>
<field invisible="is_company == True" name="teams_served_ids" string="Teams Served"/> <field invisible="is_company == True" name="teams_served_ids" string="Teams Served">
<tree editable="bottom" create="False" delete="True">
<field name="name"/>
<field name="parent_id"/>
<field name="head_coach_id"/>
<field name="head_therapist_id"/>
<field name="player_count"/>
<field name="injured_count"/>
</tree>
</field>
</page> </page>
</page></field> </page></field>
</record> </record>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_users_form_inherit" model="ir.ui.view">
<field name="name">view.users.form.inherit</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="model">res.users</field>
<field name="arch" type="xml">
<page name="preferences" position="before">
<page name="Sports Team Access"
groups="base.group_system,bemade_sports_clinic.group_sports_clinic_admin">
<field name="accessible_team_ids" widget="many2many_checkboxes"/>
</page>
</page>
</field>
</record>
</odoo>

View file

@ -128,7 +128,7 @@
</td> </td>
<td> <td>
<ul class="list-unstyled"> <ul class="list-unstyled">
<t t-foreach="player.team_ids" t-as="team"> <t t-foreach="player.sudo().team_ids" t-as="team">
<li> <li>
<a t-attf-href="/my/team?team_id={{ team.id }}" <a t-attf-href="/my/team?team_id={{ team.id }}"
t-field="team.name"/> t-field="team.name"/>

View file

@ -117,10 +117,25 @@
</tree></field> </tree></field>
</group> </group>
</group> </group>
<separator string="Players"/> <notebook>
<field context="{'tree_view_ref': 'bemade_sports_clinic.sports_patient_view_list_embedded'}" name="patient_ids" nolabel="1"/> <page name="players" string="Players">
<field name="patient_ids" nolabel="1"
context="{'tree_view_ref': 'bemade_sports_clinic.sports_patient_view_list_embedded'}"/>
</page>
<page name="user_access"
string="User Access"
groups="bemade_sports_clinic.group_sports_clinic_admin,base.group_system">
<field name="allowed_user_ids"/>
</page>
</notebook>
</sheet> </sheet>
</form></field> <div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record> </record>
<record id="sports_team_staff_view_form" model="ir.ui.view"> <record id="sports_team_staff_view_form" model="ir.ui.view">
<field name="name">sports.team.staff.view.form</field> <field name="name">sports.team.staff.view.form</field>