Restructure payment model

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-02-12 22:30:27 +01:00
parent ece128836a
commit 7c9083a6b8
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
11 changed files with 343 additions and 273 deletions

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-02-11 23:19+0100\n"
"POT-Creation-Date: 2024-02-12 22:29+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -368,8 +368,8 @@ msgstr "Êtes-vous sûr·e de vouloir annuler le tirage au sort ?"
msgid "Close"
msgstr "Fermer"
#: draw/views.py:31 participation/views.py:151 participation/views.py:440
#: participation/views.py:471
#: draw/views.py:31 participation/views.py:151 participation/views.py:438
#: participation/views.py:469
msgid "You are not in a team."
msgstr "Vous n'êtes pas dans une équipe."
@ -710,7 +710,7 @@ msgstr ""
"L'équipe {trigram} n'a pas encore été validée par les organisateurices. "
"Merci de patienter."
#: participation/models.py:193 registration/models.py:481
#: participation/models.py:193 registration/models.py:482
msgid "Pending validation"
msgstr "Validation en attente"
@ -814,9 +814,9 @@ msgid ""
"not paid yet are: {participants}.</p>"
msgstr ""
"<p>L'équipe {trigram} a {nb_missing_payments} paiements manquants. Chaque "
"membre de l'équipe doit avoir un paiement valide (ou envoyer une notification "
"de bourse) pour participer au tournoi.</p><p>Les participant⋅es qui n'ont pas "
"encore payé sont : {participants}.</p>"
"membre de l'équipe doit avoir un paiement valide (ou envoyer une "
"notification de bourse) pour participer au tournoi.</p><p>Les participant⋅es "
"qui n'ont pas encore payé sont : {participants}.</p>"
#: participation/models.py:477
msgid "Missing payments"
@ -1088,10 +1088,9 @@ msgstr "Rejoindre"
#: participation/templates/participation/team_detail.html:192
#: participation/templates/participation/tournament_form.html:12
#: participation/templates/participation/update_team.html:12
#: registration/templates/registration/payment_form.html:49
#: registration/templates/registration/update_user.html:16
#: registration/templates/registration/user_detail.html:179
#: registration/templates/registration/user_detail.html:216
#: registration/templates/registration/user_detail.html:186
#: registration/templates/registration/user_detail.html:223
msgid "Update"
msgstr "Modifier"
@ -1157,11 +1156,11 @@ msgstr "Envoyer une solution"
#: registration/templates/registration/upload_parental_authorization.html:17
#: registration/templates/registration/upload_photo_authorization.html:18
#: registration/templates/registration/upload_vaccine_sheet.html:13
#: registration/templates/registration/user_detail.html:189
#: registration/templates/registration/user_detail.html:194
#: registration/templates/registration/user_detail.html:199
#: registration/templates/registration/user_detail.html:204
#: registration/templates/registration/user_detail.html:209
#: registration/templates/registration/user_detail.html:196
#: registration/templates/registration/user_detail.html:201
#: registration/templates/registration/user_detail.html:206
#: registration/templates/registration/user_detail.html:211
#: registration/templates/registration/user_detail.html:216
msgid "Upload"
msgstr "Téléverser"
@ -1499,7 +1498,7 @@ msgid "Invalidate"
msgstr "Invalider"
#: participation/templates/participation/team_detail.html:186
#: participation/views.py:325
#: participation/views.py:323
msgid "Upload motivation letter"
msgstr "Envoyer la lettre de motivation"
@ -1508,7 +1507,7 @@ msgid "Update team"
msgstr "Modifier l'équipe"
#: participation/templates/participation/team_detail.html:196
#: participation/views.py:434
#: participation/views.py:432
msgid "Leave team"
msgstr "Quitter l'équipe"
@ -1642,7 +1641,7 @@ msgstr "Vous êtes déjà dans une équipe."
msgid "Join team"
msgstr "Rejoindre une équipe"
#: participation/views.py:152 participation/views.py:472
#: participation/views.py:152 participation/views.py:470
msgid "You don't participate, so you don't have any team."
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
@ -1678,95 +1677,95 @@ msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi."
msgid "This team has no pending validation."
msgstr "L'équipe n'a pas de validation en attente."
#: participation/views.py:268
#: participation/views.py:266
msgid "You must specify if you validate the registration or not."
msgstr "Vous devez spécifier si vous validez l'inscription ou non."
#: participation/views.py:303
#: participation/views.py:301
#, python-brace-format
msgid "Update team {trigram}"
msgstr "Mise à jour de l'équipe {trigram}"
#: participation/views.py:364 participation/views.py:420
#: participation/views.py:362 participation/views.py:418
#, python-brace-format
msgid "Motivation letter of {team}.{ext}"
msgstr "Lettre de motivation de {team}.{ext}"
#: participation/views.py:395
#: participation/views.py:393
#, python-brace-format
msgid "Photo authorization of {participant}.{ext}"
msgstr "Autorisation de droit à l'image de {participant}.{ext}"
#: participation/views.py:401
#: participation/views.py:399
#, python-brace-format
msgid "Parental authorization of {participant}.{ext}"
msgstr "Autorisation parentale de {participant}.{ext}"
#: participation/views.py:408
#: participation/views.py:406
#, python-brace-format
msgid "Health sheet of {participant}.{ext}"
msgstr "Fiche sanitaire de {participant}.{ext}"
#: participation/views.py:414
#: participation/views.py:412
#, python-brace-format
msgid "Vaccine sheet of {participant}.{ext}"
msgstr "Carnet de vaccination de {participant}.{ext}"
#: participation/views.py:424
#: participation/views.py:422
#, python-brace-format
msgid "Photo authorizations of team {trigram}.zip"
msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip"
#: participation/views.py:442
#: participation/views.py:440
msgid "The team is already validated or the validation is pending."
msgstr "La validation de l'équipe est déjà faite ou en cours."
#: participation/views.py:486
#: participation/views.py:484
msgid "The team is not validated yet."
msgstr "L'équipe n'est pas encore validée."
#: participation/views.py:500
#: participation/views.py:498
#, python-brace-format
msgid "Participation of team {trigram}"
msgstr "Participation de l'équipe {trigram}"
#: participation/views.py:636
#: participation/views.py:634
msgid "You can't upload a solution after the deadline."
msgstr "Vous ne pouvez pas envoyer de solution après la date limite."
#: participation/views.py:744
#: participation/views.py:742
#, python-brace-format
msgid "Solutions for pool {pool} of tournament {tournament}.zip"
msgstr "Solutions pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:745
#: participation/views.py:743
#, python-brace-format
msgid "Syntheses for pool {pool} of tournament {tournament}.zip"
msgstr "Notes de synthèses pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:763
#: participation/views.py:761
#, python-brace-format
msgid "Jurys of {pool}"
msgstr "Juré⋅es de la {pool}"
#: participation/views.py:790
#: participation/views.py:788
msgid "New TFJM² jury account"
msgstr "Nouveau compte de juré⋅e pour le TFJM²"
#: participation/views.py:803
#: participation/views.py:801
#, python-brace-format
msgid "The jury {name} has been successfully added!"
msgstr "{name} a été ajouté⋅e avec succès en tant que juré⋅e !"
#: participation/views.py:840
#: participation/views.py:838
msgid "The following user is not registered as a jury:"
msgstr "L'utilisateur⋅rice suivant n'est pas inscrit⋅e en tant que juré⋅e :"
#: participation/views.py:854
#: participation/views.py:852
msgid "Notes were successfully uploaded."
msgstr "Les notes ont bien été envoyées."
#: participation/views.py:1518
#: participation/views.py:1516
msgid "You can't upload a synthesis after the deadline."
msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite."
@ -1780,9 +1779,9 @@ msgstr "prénom"
msgid "last name"
msgstr "nom de famille"
#: registration/admin.py:105
msgid "registration type"
msgstr "type d'inscription"
#: registration/admin.py:106
msgid "concerned people"
msgstr "personnes concernées"
#: registration/forms.py:22
msgid "role"
@ -1792,7 +1791,7 @@ msgstr "rôle"
msgid "participant"
msgstr "participant⋅e"
#: registration/forms.py:25 registration/models.py:415
#: registration/forms.py:25 registration/models.py:416
msgid "coach"
msgstr "encadrant⋅e"
@ -1801,8 +1800,8 @@ msgid "Pending"
msgstr "En attente"
#: registration/forms.py:245
msgid "You must upload your scholarship attestation."
msgstr "Vous devez envoyer votre attestation de bourse."
msgid "You must upload your receipt."
msgstr "Vous devez envoyer votre justificatif."
#: registration/models.py:36
msgid "Grant Animath to contact me in the future about other actions"
@ -1828,11 +1827,11 @@ msgstr ""
"avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur <a "
"href=\"{send_email_url}\">ce lien</a>."
#: registration/models.py:118 registration/models.py:503
#: registration/models.py:118
msgid "registration"
msgstr "inscription"
#: registration/models.py:119
#: registration/models.py:119 registration/models.py:503
msgid "registrations"
msgstr "inscriptions"
@ -2027,7 +2026,7 @@ msgstr ""
msgid "Vaccine sheet"
msgstr "Carnet de vaccination"
#: registration/models.py:368
#: registration/models.py:369
#, python-brace-format
msgid ""
"You have to pay {amount} € for your registration, or send a scholarship "
@ -2038,27 +2037,27 @@ msgstr ""
"notification de bourse ou un justificatif de paiement. Vous pouvez le faire "
"sur <a href=\"{url}\">la page de paiement</a>."
#: registration/models.py:374 registration/models.py:383
#: registration/models.py:375 registration/models.py:384
msgid "Payment"
msgstr "Paiement"
#: registration/models.py:380
#: registration/models.py:381
msgid "Your payment is under approval."
msgstr "Votre paiement est en cours de validation."
#: registration/models.py:392
#: registration/models.py:393
msgid "student registration"
msgstr "inscription d'élève"
#: registration/models.py:393
#: registration/models.py:394
msgid "student registrations"
msgstr "inscriptions d'élève"
#: registration/models.py:404
#: registration/models.py:405
msgid "most recent degree in mathematics, computer science or physics"
msgstr "Dernier diplôme obtenu en mathématiques, informatique ou physique"
#: registration/models.py:405
#: registration/models.py:406
msgid ""
"Your most recent degree in maths, computer science or physics, or your last "
"entrance exam (CAPES, Agrégation,…)"
@ -2066,23 +2065,23 @@ msgstr ""
"Votre dernier diplôme en mathématiques, informatique ou physique, ou votre "
"dernier concours obtenu (CAPES, Agrégation, …)"
#: registration/models.py:410 registration/models.py:432
#: registration/models.py:411 registration/models.py:433
msgid "professional activity"
msgstr "activité professionnelle"
#: registration/models.py:423
#: registration/models.py:424
msgid "coach registration"
msgstr "inscription d'encadrant⋅e"
#: registration/models.py:424
#: registration/models.py:425
msgid "coach registrations"
msgstr "inscriptions d'encadrant⋅es"
#: registration/models.py:436
#: registration/models.py:437
msgid "administrator"
msgstr "administrateur⋅rice"
#: registration/models.py:437
#: registration/models.py:438
msgid ""
"An administrator has all rights. Please don't give this right to all juries "
"and volunteers."
@ -2090,15 +2089,15 @@ msgstr ""
"Un⋅e administrateur⋅rice a tous les droits. Merci de ne pas donner ce droit "
"à toustes les juré⋅es et bénévoles."
#: registration/models.py:447
#: registration/models.py:448
msgid "admin"
msgstr "admin"
#: registration/models.py:447
#: registration/models.py:448
msgid "volunteer"
msgstr "bénévole"
#: registration/models.py:460
#: registration/models.py:461
msgid ""
"Registrations for tournament {tournament} are closing on {date:%Y-%m-%d %H:"
"%M}. There are for now {validated_teams} validated teams (+ {pending_teams} "
@ -2108,11 +2107,11 @@ msgstr ""
"%M}. Il y a pour l'instant {validated_teams} équipes validées (+ "
"{pending_teams} en attente) sur {max_teams} attendues."
#: registration/models.py:468
#: registration/models.py:469
msgid "Registrations"
msgstr "Inscriptions"
#: registration/models.py:475
#: registration/models.py:476
#, python-brace-format
msgid ""
"The team {trigram} requested to be validated for the tournament of "
@ -2123,68 +2122,98 @@ msgstr ""
"Vous pouvez vérifier le statut de l'équipe sur la <a href=\"{url}\">page de "
"l'équipe</a>."
#: registration/models.py:490
#: registration/models.py:491
msgid "volunteer registration"
msgstr "inscription de bénévole"
#: registration/models.py:491
#: registration/models.py:492
msgid "volunteer registrations"
msgstr "inscriptions de bénévoles"
#: registration/models.py:507
msgid "grouped"
msgstr "groupé"
#: registration/models.py:509
msgid ""
"If set to true, then one payment is made for the full team, for example if "
"the school pays for all."
msgstr ""
"Si vrai, alors un seul paiement est fait pour toute l'équipe, par exemple si "
"le lycée paie pour tout le monde."
#: registration/models.py:514
msgid "total amount"
msgstr "montant total"
#: registration/models.py:515
msgid "Corresponds to the total required amount to pay, in euros."
msgstr "Correspond au montant total à payer, en euros."
#: registration/models.py:520
msgid "for final tournament"
msgstr "pour la finale"
#: registration/models.py:525
msgid "type"
msgstr "type"
#: registration/models.py:510
#: registration/models.py:528
msgid "No payment"
msgstr "Pas de paiement"
#: registration/models.py:512
#: registration/models.py:530
msgid "Scholarship"
msgstr "Notification de bourse"
#: registration/models.py:513
#: registration/models.py:531
msgid "Bank transfer"
msgstr "Virement bancaire"
#: registration/models.py:514
#: registration/models.py:532
msgid "Other (please indicate)"
msgstr "Autre (veuillez spécifier)"
#: registration/models.py:515
#: registration/models.py:533
msgid "The tournament is free"
msgstr "Le tournoi est gratuit"
#: registration/models.py:522
msgid "scholarship file"
msgstr "Notification de bourse"
#: registration/models.py:540
msgid "Hello Asso checkout intent ID"
msgstr "ID de l'intention de paiement Hello Asso"
#: registration/models.py:523
msgid "only if you have a scholarship."
msgstr "Nécessaire seulement si vous déclarez être boursier."
#: registration/models.py:547
msgid "receipt"
msgstr "justificatif"
#: registration/models.py:530
#: registration/models.py:548
msgid "only if you have a scholarship or if you chose a bank transfer."
msgstr ""
"Nécessaire seulement si vous déclarez être boursièr⋅e ou si vous payez par "
"virement bancaire."
#: registration/models.py:555
msgid "additional information"
msgstr "informations additionnelles"
#: registration/models.py:531
#: registration/models.py:556
msgid "To help us to find your payment."
msgstr "Pour nous aider à retrouver votre paiement, si nécessaire."
#: registration/models.py:537
#: registration/models.py:562
msgid "payment valid"
msgstr "paiement valide"
#: registration/models.py:546
#: registration/models.py:571
#, python-brace-format
msgid "Payment of {registration}"
msgstr "Paiement de {registration}"
msgid "Payment of {registrations}"
msgstr "Paiements de {registration}"
#: registration/models.py:549
#: registration/models.py:574
msgid "payment"
msgstr "paiement"
#: registration/models.py:550
#: registration/models.py:575
msgid "payments"
msgstr "paiements"
@ -2349,56 +2378,6 @@ msgstr ""
msgid "Reset my password"
msgstr "Réinitialiser mon mot de passe"
#: registration/templates/registration/payment_form.html:10
#, python-format
msgid ""
"The price of the tournament is %(price)s €. The participation fee is offered "
"for coaches and for students who have a scholarship. If so, please send us "
"your scholarship attestation."
msgstr ""
"Le prix du tournoi est de %(price)s €. Les frais de participation sont "
"offerts pour les encadrant⋅es et les élèves boursièr⋅es. Si c'est le cas, "
"merci de nous transmettre votre notification de bourse."
#: registration/templates/registration/payment_form.html:17
msgid ""
"You can pay with a credit card through <a class=\"alert-link\" "
"href=\"https://www.helloasso.com/associations/animath/evenements/tfjm-2023-"
"tournois-regionaux\">our Hello Asso page</a>. To make the validation of the "
"payment easier, <span class=\"text-danger\">please use the same e-mail "
"address that you use on this platform.</span> The payment verification will "
"be checked automatically under 10 minutes, you don't necessary need to fill "
"this form."
msgstr ""
"Vous pouvez payer par carte bancaire sur <a class=\"alert-link\" "
"href=\"https://www.helloasso.com/associations/animath/evenements/tfjm-2023-"
"tournois-regionaux\">notre page Hello Asso</a>. Pour rendre la validation du "
"paiement plus facile, <span class=\"text-danger\">merci d'utiliser la même "
"adresse e-mail que vous utilisez sur cette plateforme.</span> La "
"vérification du paiement sera faite automatiquement sous 10 minutes, vous "
"n'avez pas nécessairement besoin de remplir ce formulaire."
#: registration/templates/registration/payment_form.html:27
msgid ""
"You can also send a bank transfer to the bank account of Animath. You must "
"put in the reference of the transfer the mention \"TFJMpu\" followed by the "
"last name and the first name of the student."
msgstr ""
"Vous pouvez également faire un virement bancaire sur le compte d'Animath. "
"Vous devez alors mettre dans la référence du transfert la mention \"TFJMpu\" "
"suivie du nom et du prénom de l'élève."
#: registration/templates/registration/payment_form.html:39
msgid ""
"If any payment mean is available to you, please contact us at <a "
"class=\"alert-link\" href=\"mailto:contact@tfjm.org\">contact@tfjm.org</a> "
"to find a solution to your difficulties."
msgstr ""
"Si aucun moyen de paiement ne vous convient, merci de nous contecter à "
"l'adresse <a class=\"alert-link\" href=\"mailto:contact@tfjm."
"org\">contact@tfjm.org</a> pour que nous puissions trouver une solution à "
"vos difficultés."
#: registration/templates/registration/signup.html:5
#: registration/templates/registration/signup.html:12
#: registration/templates/registration/signup.html:19 registration/views.py:44
@ -2533,49 +2512,53 @@ msgstr "Administrateur⋅rice :"
msgid "Grant Animath to contact me in the future about other actions:"
msgstr "Autorise Animath à recontacter à propos d'autres actions :"
#: registration/templates/registration/user_detail.html:149
#: registration/templates/registration/user_detail.html:150
msgid "Payment information:"
msgstr "Informations de paiement :"
#: registration/templates/registration/user_detail.html:151
#: registration/templates/registration/user_detail.html:152
msgid "yes,no,pending"
msgstr "oui,non,en attente"
#: registration/templates/registration/user_detail.html:155
#: registration/templates/registration/user_detail.html:158
#: registration/templates/registration/user_detail.html:156
#: registration/templates/registration/user_detail.html:159
msgid "valid:"
msgstr "valide :"
#: registration/templates/registration/user_detail.html:162
#: registration/templates/registration/user_detail.html:215
#: registration/templates/registration/user_detail.html:163
#: registration/templates/registration/user_detail.html:222
msgid "Update payment"
msgstr "Modifier le paiement"
#: registration/templates/registration/user_detail.html:168
#: registration/templates/registration/user_detail.html:171
msgid "Download scholarship attestation"
msgstr "Télécharger l'attestation de bourse"
#: registration/templates/registration/user_detail.html:181
#: registration/templates/registration/user_detail.html:173
msgid "Download bank transfer receipt"
msgstr "Télécharger le justificatif de virement bancaire"
#: registration/templates/registration/user_detail.html:188
msgid "Impersonate"
msgstr "Impersonifier"
#: registration/templates/registration/user_detail.html:188
#: registration/templates/registration/user_detail.html:195
#: registration/views.py:313
msgid "Upload photo authorization"
msgstr "Téléverser l'autorisation de droit à l'image"
#: registration/templates/registration/user_detail.html:193
#: registration/templates/registration/user_detail.html:200
#: registration/views.py:334
msgid "Upload health sheet"
msgstr "Téléverser la fiche sanitaire"
#: registration/templates/registration/user_detail.html:198
#: registration/templates/registration/user_detail.html:205
#: registration/views.py:355
msgid "Upload vaccine sheet"
msgstr "Téléverser le carnet de vaccination"
#: registration/templates/registration/user_detail.html:203
#: registration/templates/registration/user_detail.html:208
#: registration/templates/registration/user_detail.html:210
#: registration/templates/registration/user_detail.html:215
#: registration/views.py:376
msgid "Upload parental authorization"
msgstr "Téléverser l'autorisation parentale"
@ -2614,27 +2597,27 @@ msgstr "Détails de l'utilisateur⋅rice {user}"
msgid "Update user {user}"
msgstr "Mise à jour de l'utilisateur⋅rice {user}"
#: registration/views.py:494
#: registration/views.py:492
#, python-brace-format
msgid "Photo authorization of {student}.{ext}"
msgstr "Autorisation de droit à l'image de {student}.{ext}"
#: registration/views.py:517
#: registration/views.py:515
#, python-brace-format
msgid "Health sheet of {student}.{ext}"
msgstr "Fiche sanitaire de {student}.{ext}"
#: registration/views.py:540
#: registration/views.py:538
#, python-brace-format
msgid "Vaccine sheet of {student}.{ext}"
msgstr "Carnet de vaccination de {student}.{ext}"
#: registration/views.py:563
#: registration/views.py:561
#, python-brace-format
msgid "Parental authorization of {student}.{ext}"
msgstr "Autorisation parentale de {student}.{ext}"
#: registration/views.py:585
#: registration/views.py:583
#, python-brace-format
msgid "Scholarship attestation of {user}.{ext}"
msgstr "Notification de bourse de {user}.{ext}"

View File

@ -14,7 +14,7 @@ from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _
from registration.models import VolunteerRegistration, Payment
from registration.models import Payment, VolunteerRegistration
from tfjm.lists import get_sympa_client
@ -465,14 +465,15 @@ class Participation(models.Model):
def important_informations(self):
informations = []
missing_payments = Payment.objects.filter(registration__in=self.team.participants.all(), valid=False)
missing_payments = Payment.objects.filter(registrations__in=self.team.participants.all(), valid=False)
if missing_payments.exists():
text = _("<p>The team {trigram} has {nb_missing_payments} missing payments. Each member of the team "
"must have a valid payment (or send a scholarship notification) "
"to participate to the tournament.</p>"
"<p>Participants that have not paid yet are: {participants}.</p>")
content = format_lazy(text, trigram=self.team.trigram, nb_missing_payments=missing_payments.count(),
participants=", ".join(str(p.registration) for p in missing_payments.all()))
participants=", ".join(", ".join(str(r) for r in p.registrations.all())
for p in missing_payments.all()))
informations.append({
'title': _("Missing payments"),
'type': "danger",

View File

@ -30,7 +30,7 @@ from odf.opendocument import OpenDocumentSpreadsheet
from odf.style import Style, TableCellProperties, TableColumnProperties, TextProperties
from odf.table import CoveredTableCell, Table, TableCell, TableColumn, TableRow
from odf.text import P
from registration.models import StudentRegistration, VolunteerRegistration
from registration.models import Payment, StudentRegistration, VolunteerRegistration
from tfjm.lists import get_sympa_client
from tfjm.views import AdminMixin, VolunteerMixin
@ -246,16 +246,20 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
mail_html = render_to_string("participation/mails/team_validated.html", mail_context)
send_mail("[TFJM²] Équipe validée", mail_plain, None, [self.object.email], html_message=mail_html)
if self.object.participation.tournament.price == 0:
for registration in self.object.participants.all():
registration.payment.type = "free"
registration.payment.valid = True
registration.payment.save()
else:
for coach in self.object.coaches.all():
coach.payment.type = "free"
coach.payment.valid = True
coach.payment.save()
for student in self.object.students.all():
payment_qs = Payment.objects.filter(registrations=student)
if payment_qs.exists():
payment = payment_qs.get()
else:
payment = Payment.objects.create()
payment.registrations.add(student)
payment.save()
payment.amount = self.object.participation.tournament.price
if payment.amount == 0:
payment.type = "free"
payment.valid = True
payment.save()
elif "invalidate" in self.request.POST:
self.object.participation.valid = None
self.object.participation.save()

View File

@ -97,11 +97,12 @@ class VolunteerRegistrationAdmin(PolymorphicChildModelAdmin):
@admin.register(Payment)
class PaymentAdmin(ModelAdmin):
list_display = ('registration', 'registration_type', 'type', 'valid', )
search_fields = ('registration__user__last_name', 'registration__user__first_name', 'registration__user__email',)
list_filter = ('registration__team__participation__valid', 'type', 'type', 'valid',)
autocomplete_fields = ('registration',)
list_display = ('id', 'concerned_people', 'grouped', 'type', 'valid', )
search_fields = ('registrations__user__last_name', 'registrations__user__first_name', 'registrations__user__email',)
list_filter = ('registrations__team__participation__valid', 'type',
'grouped', 'valid', 'registrations__polymorphic_ctype',)
autocomplete_fields = ('registrations',)
@admin.display(description=_('registration type'), ordering='registration__polymorphic_ctype')
def registration_type(self, record: Payment):
return record.registration.get_real_instance().type
@admin.display(description=_('concerned people'))
def concerned_people(self, record: Payment):
return ", ".join(f"{reg.user.first_name} {reg.user.last_name}" for reg in record.registrations.all())

View File

@ -12,11 +12,8 @@ class RegistrationConfig(AppConfig):
name = 'registration'
def ready(self):
from registration.signals import create_admin_registration, create_payment, \
from registration.signals import create_admin_registration, \
set_username, send_email_link
pre_save.connect(set_username, "auth.User")
pre_save.connect(send_email_link, "auth.User")
post_save.connect(create_admin_registration, "auth.User")
post_save.connect(create_payment, "registration.Registration")
post_save.connect(create_payment, "registration.StudentRegistration")
post_save.connect(create_payment, "registration.CoachRegistration")

View File

@ -227,25 +227,25 @@ class PaymentForm(forms.ModelForm):
super().__init__(*args, **kwargs)
self.fields["valid"].widget.choices[0] = ('unknown', _("Pending"))
def clean_scholarship_file(self):
def clean_receipt(self):
print(self.files)
if "scholarship_file" in self.files:
file = self.files["scholarship_file"]
if "receipt" in self.files:
file = self.files["receipt"]
if file.size > 2e6:
raise ValidationError(_("The uploaded file size must be under 2 Mo."))
if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]:
raise ValidationError(_("The uploaded file must be a PDF, PNG of JPEG file."))
return self.cleaned_data["scholarship_file"]
return self.cleaned_data["receipt"]
def clean(self):
cleaned_data = super().clean()
if "type" in cleaned_data and cleaned_data["type"] == "scholarship" \
and "scholarship_file" not in self.files and not self.instance.scholarship_file:
self.add_error("scholarship_file", _("You must upload your scholarship attestation."))
if "type" in cleaned_data and cleaned_data['type'] in ["scholarship", "bank_transfer"] \
and "receipt" not in self.files and not self.instance.scholarship_file:
self.add_error("receipt", _("You must upload your receipt."))
return cleaned_data
class Meta:
model = Payment
fields = ('type', 'scholarship_file', 'additional_information', 'valid',)
fields = ('type', 'receipt', 'additional_information', 'valid',)

View File

@ -0,0 +1,76 @@
# Generated by Django 5.0.1 on 2024-02-12 20:40
import registration.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registration", "0010_coachregistration_last_degree"),
]
operations = [
migrations.RemoveField(
model_name="payment",
name="registration",
),
migrations.RemoveField(
model_name="payment",
name="scholarship_file",
),
migrations.AddField(
model_name="payment",
name="amount",
field=models.PositiveSmallIntegerField(
default=0,
help_text="Corresponds to the total required amount to pay, in euros.",
verbose_name="total amount",
),
),
migrations.AddField(
model_name="payment",
name="checkout_intent_id",
field=models.IntegerField(
blank=True,
default=None,
null=True,
verbose_name="Hello Asso checkout intent ID",
),
),
migrations.AddField(
model_name="payment",
name="final",
field=models.BooleanField(
default=False, verbose_name="for final tournament"
),
),
migrations.AddField(
model_name="payment",
name="grouped",
field=models.BooleanField(
default=False,
help_text="If set to true, then one payment is made for the full team, for example if the school pays for all.",
verbose_name="grouped",
),
),
migrations.AddField(
model_name="payment",
name="receipt",
field=models.FileField(
blank=True,
default="",
help_text="only if you have a scholarship or if you chose a bank transfer.",
upload_to=registration.models.get_scholarship_filename,
verbose_name="receipt",
),
),
migrations.AddField(
model_name="payment",
name="registrations",
field=models.ManyToManyField(
related_name="payments",
to="registration.participantregistration",
verbose_name="registrations",
),
),
]

View File

@ -364,27 +364,28 @@ class StudentRegistration(ParticipantRegistration):
})
if self.team and self.team.participation.valid:
if self.payment.valid is False:
text = _("You have to pay {amount} € for your registration, or send a scholarship "
"notification or a payment proof. "
"You can do it on <a href=\"{url}\">the payment page</a>.")
url = reverse_lazy("registration:update_payment", args=(self.payment.id,))
content = format_lazy(text, amount=self.team.participation.tournament.price, url=url)
informations.append({
'title': _("Payment"),
'type': "danger",
'priority': 3,
'content': content,
})
elif self.payment.valid is None:
text = _("Your payment is under approval.")
content = text
informations.append({
'title': _("Payment"),
'type': "warning",
'priority': 3,
'content': content,
})
for payment in self.payments.all():
if payment.valid is False:
text = _("You have to pay {amount} € for your registration, or send a scholarship "
"notification or a payment proof. "
"You can do it on <a href=\"{url}\">the payment page</a>.")
url = reverse_lazy("registration:update_payment", args=(payment.id,))
content = format_lazy(text, amount=payment.amount, url=url)
informations.append({
'title': _("Payment"),
'type': "danger",
'priority': 3,
'content': content,
})
elif self.payment.valid is None:
text = _("Your payment is under approval.")
content = text
informations.append({
'title': _("Payment"),
'type': "warning",
'priority': 3,
'content': content,
})
return informations
@ -496,11 +497,28 @@ def get_scholarship_filename(instance, filename):
class Payment(models.Model):
registration = models.OneToOneField(
registrations = models.ManyToManyField(
ParticipantRegistration,
on_delete=models.CASCADE,
related_name="payment",
verbose_name=_("registration"),
related_name="payments",
verbose_name=_("registrations"),
)
grouped = models.BooleanField(
verbose_name=_("grouped"),
default=False,
help_text=_("If set to true, then one payment is made for the full team, "
"for example if the school pays for all."),
)
amount = models.PositiveSmallIntegerField(
verbose_name=_("total amount"),
help_text=_("Corresponds to the total required amount to pay, in euros."),
default=0,
)
final = models.BooleanField(
verbose_name=_("for final tournament"),
default=False,
)
type = models.CharField(
@ -518,9 +536,16 @@ class Payment(models.Model):
default="",
)
scholarship_file = models.FileField(
verbose_name=_("scholarship file"),
help_text=_("only if you have a scholarship."),
checkout_intent_id = models.IntegerField(
verbose_name=_("Hello Asso checkout intent ID"),
blank=True,
null=True,
default=None,
)
receipt = models.FileField(
verbose_name=_("receipt"),
help_text=_("only if you have a scholarship or if you chose a bank transfer."),
upload_to=get_scholarship_filename,
blank=True,
default="",
@ -540,10 +565,10 @@ class Payment(models.Model):
)
def get_absolute_url(self):
return reverse_lazy("registration:user_detail", args=(self.registration.user.id,))
return reverse_lazy("registration:update_payment", args=(self.pk,))
def __str__(self):
return _("Payment of {registration}").format(registration=self.registration)
return _("Payment of {registrations}").format(registrations=", ".join(map(str, self.registrations.all())))
class Meta:
verbose_name = _("payment")

View File

@ -4,7 +4,7 @@
from django.contrib.auth.models import User
from tfjm.lists import get_sympa_client
from .models import Payment, Registration, VolunteerRegistration
from .models import Registration, VolunteerRegistration
def set_username(instance, **_):
@ -41,16 +41,3 @@ def create_admin_registration(instance, **_):
"""
if instance.is_superuser:
VolunteerRegistration.objects.get_or_create(user=instance, admin=True)
def create_payment(instance: Registration, raw, **_):
"""
When a user is saved, create the associated payment.
For a free tournament, the payment is valid.
"""
if instance.participates and not raw:
payment = Payment.objects.get_or_create(registration=instance)[0]
if instance.team and instance.team.participation.valid and instance.team.participation.tournament.price == 0:
payment.valid = True
payment.type = "free"
payment.save()

View File

@ -143,35 +143,42 @@
</dl>
{% if user_object.registration.participates and user_object.registration.team.participation.valid %}
<hr>
{% for payment in user_object.registration.payments.all %}
<hr>
<dl class="row">
<dt class="col-sm-6 text-end">{% trans "Payment information:" %}</dt>
<dd class="col-sm-6">
{% trans "yes,no,pending" as yesnodefault %}
{% with info=user_object.registration.payment.additional_information %}
{% if info %}
<abbr title="{{ info }}">
{{ user_object.registration.payment.get_type_display }}, {% trans "valid:" %} {{ user_object.registration.payment.valid|yesno:yesnodefault }}
</abbr>
{% else %}
{{ user_object.registration.payment.get_type_display }}, {% trans "valid:" %} {{ user_object.registration.payment.valid|yesno:yesnodefault }}
{% endif %}
{% if user.registration.is_admin or user_object.registration.payment.valid is False %}
<button class="btn-sm btn-secondary" data-bs-toggle="modal" data-bs-target="#updatePaymentModal">
<i class="fas fa-money-bill-wave"></i> {% trans "Update payment" %}
</button>
{% endif %}
{% if user_object.registration.payment.type == "scholarship" %}
{% if user.registration.is_admin or user == user_object %}
<a href="{{ user_object.registration.payment.scholarship_file.url }}" class="btn btn-info">
<i class="fas fa-file-pdf"></i> {% trans "Download scholarship attestation" %}
<dl class="row">
<dt class="col-sm-6 text-end">{% trans "Payment information:" %}</dt>
<dd class="col-sm-6">
{% trans "yes,no,pending" as yesnodefault %}
{% with info=payment.additional_information %}
{% if info %}
<abbr title="{{ info }}">
{{ payment.get_type_display }}, {% trans "valid:" %} {{ payment.valid|yesno:yesnodefault }}
</abbr>
{% else %}
{{ payment.get_type_display }}, {% trans "valid:" %} {{ payment.valid|yesno:yesnodefault }}
{% endif %}
{% if user.registration.is_admin or payment.valid is False %}
<a href="{% url "registration:update_payment" pk=payment.pk %}" class="btn btn-secondary">
<i class="fas fa-money-bill-wave"></i> {% trans "Update payment" %}
</a>
{% endif %}
{% endif %}
{% endwith %}
</dd>
</dl>
{% if payment.type == "scholarship" or payment.type == "bank_transfer" %}
{% if user.registration.is_admin or user == user_object %}
<a href="{{ payment.receipt.url }}" class="btn btn-info">
<i class="fas fa-file-pdf"></i>
{% if payment.type == "scholarship" %}
{% trans "Download scholarship attestation" %}
{% elif payment.type == "bank_transfer" %}
{% trans "Download bank transfer receipt" %}
{% endif %}
</a>
{% endif %}
{% endif %}
{% endwith %}
</dd>
</dl>
{% endfor %}
{% endif %}
</div>
{% if user.pk == user_object.pk or user.registration.is_admin %}
@ -210,13 +217,6 @@
{% url "registration:upload_user_parental_authorization" pk=user_object.registration.pk as modal_action %}
{% include "base_modal.html" with modal_id="uploadParentalAuthorization" modal_enctype="multipart/form-data" %}
{% endif %}
{% if user_object.registration.team.participation.valid %}
{% trans "Update payment" as modal_title %}
{% trans "Update" as modal_button %}
{% url "registration:update_payment" pk=user_object.registration.payment.pk as modal_action %}
{% include "base_modal.html" with modal_id="updatePayment" modal_additional_class="modal-xl" modal_enctype="multipart/form-data" %}
{% endif %}
{% endblock %}
{% block extrajavascript %}
@ -228,10 +228,6 @@
initModal("uploadVaccineSheet", "{% url "registration:upload_user_vaccine_sheet" pk=user_object.registration.pk %}")
initModal("uploadParentalAuthorization", "{% url "registration:upload_user_parental_authorization" pk=user_object.registration.pk %}")
{% endif %}
{% if user_object.registration.team.participation.valid %}
initModal("updatePayment", "{% url "registration:update_payment" pk=user_object.registration.payment.pk %}")
{% endif %}
});
</script>
{% endblock %}

View File

@ -448,7 +448,7 @@ class PaymentUpdateView(LoginRequiredMixin, UpdateView):
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated or \
not self.request.user.registration.is_admin \
and (self.request.user != self.get_object().registration.user
and (self.request.user.registration not in self.get_object().registrations.all()
or self.get_object().valid is not False):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
@ -464,8 +464,8 @@ class PaymentUpdateView(LoginRequiredMixin, UpdateView):
if not self.request.user.registration.is_admin:
form.instance.valid = None
old_instance = Payment.objects.get(pk=self.object.pk)
if old_instance.scholarship_file:
old_instance.scholarship_file.delete()
if old_instance.receipt:
old_instance.receipt.delete()
old_instance.save()
return super().form_valid(form)
@ -568,12 +568,12 @@ class ScholarshipView(LoginRequiredMixin, View):
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
path = f"media/authorization/scholarship/{filename}"
path = f"media/authorization/receipt/{filename}"
if not os.path.exists(path):
raise Http404
payment = Payment.objects.get(scholarship_file__endswith=filename)
payment = Payment.objects.get(receipt__endswith=filename)
user = request.user
if not (payment.registration.user == user or user.registration.is_admin):
if not (user.registration in payment.registrations or user.registration.is_admin):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)