From 42090b5ab7f7d672d11d582c3dc41273e407981c Mon Sep 17 00:00:00 2001
From: Raz
Date: Wed, 9 Apr 2025 18:40:33 +0200
Subject: [PATCH] add payment for registration
---
..._playerregistration_payment_id_and_more.py | 23 ++
...yerregistration_payment_status_and_more.py | 22 ++
tournaments/models/player_registration.py | 10 +-
tournaments/models/team_registration.py | 51 ++++
tournaments/models/tournament.py | 6 +
tournaments/services/email_service.py | 38 ++-
tournaments/services/registration_cart.py | 21 +-
.../templates/register_tournament.html | 33 ++-
.../tournaments/tournament_info.html | 31 +-
tournaments/urls.py | 2 +
tournaments/views.py | 277 ++++++++++++++++--
11 files changed, 471 insertions(+), 43 deletions(-)
create mode 100644 tournaments/migrations/0117_playerregistration_payment_id_and_more.py
create mode 100644 tournaments/migrations/0118_remove_playerregistration_payment_status_and_more.py
diff --git a/tournaments/migrations/0117_playerregistration_payment_id_and_more.py b/tournaments/migrations/0117_playerregistration_payment_id_and_more.py
new file mode 100644
index 0000000..faf0fb1
--- /dev/null
+++ b/tournaments/migrations/0117_playerregistration_payment_id_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.1 on 2025-04-09 13:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tournaments', '0116_playerregistration_payment_status_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='playerregistration',
+ name='payment_id',
+ field=models.CharField(blank=True, max_length=255, null=True),
+ ),
+ migrations.AddField(
+ model_name='tournament',
+ name='reserved_spots',
+ field=models.IntegerField(default=0),
+ ),
+ ]
diff --git a/tournaments/migrations/0118_remove_playerregistration_payment_status_and_more.py b/tournaments/migrations/0118_remove_playerregistration_payment_status_and_more.py
new file mode 100644
index 0000000..6c2702d
--- /dev/null
+++ b/tournaments/migrations/0118_remove_playerregistration_payment_status_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.1 on 2025-04-09 13:52
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tournaments', '0117_playerregistration_payment_id_and_more'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='playerregistration',
+ name='payment_status',
+ ),
+ migrations.AlterField(
+ model_name='playerregistration',
+ name='registration_status',
+ field=models.CharField(choices=[('WAITING', 'Waiting'), ('PENDING', 'Pending'), ('CONFIRMED', 'Confirmed'), ('CANCELED', 'Canceled')], default='WAITING', max_length=20),
+ ),
+ ]
diff --git a/tournaments/models/player_registration.py b/tournaments/models/player_registration.py
index 2c40b10..e907e94 100644
--- a/tournaments/models/player_registration.py
+++ b/tournaments/models/player_registration.py
@@ -7,7 +7,6 @@ class RegistrationStatus(models.TextChoices):
WAITING = 'WAITING', 'Waiting'
PENDING = 'PENDING', 'Pending'
CONFIRMED = 'CONFIRMED', 'Confirmed'
- PAID = 'PAID', 'Paid'
CANCELED = 'CANCELED', 'Canceled'
@@ -47,11 +46,7 @@ class PlayerRegistration(SideStoreModel):
registered_online = models.BooleanField(default=False)
time_to_confirm = models.DateTimeField(null=True, blank=True)
registration_status = models.CharField(max_length=20, choices=RegistrationStatus.choices, default=RegistrationStatus.WAITING)
- payment_status = models.CharField(max_length=20, default='UNPAID', choices=[
- ('UNPAID', 'Unpaid'),
- ('PAID', 'Paid'),
- ('FAILED', 'Failed'),
- ])
+ payment_id = models.CharField(max_length=255, blank=True, null=True)
def delete_dependencies(self):
pass
@@ -172,3 +167,6 @@ class PlayerRegistration(SideStoreModel):
status['short_label'] = 'inscrit'
return status
+
+ def has_paid(self):
+ return self.payment_type is not None
diff --git a/tournaments/models/team_registration.py b/tournaments/models/team_registration.py
index 26195a6..4eed0d6 100644
--- a/tournaments/models/team_registration.py
+++ b/tournaments/models/team_registration.py
@@ -340,3 +340,54 @@ class TeamRegistration(SideStoreModel):
# Save confirmation date
self.confirmation_date = now
self.save()
+
+ # Add to TeamRegistration class in team_registration.py
+ def get_payment_status(self):
+ """
+ Gets the payment status for this team.
+ Returns:
+ - 'PAID': If all players in the team have paid
+ - 'UNPAID': If no player has paid
+ - 'MIXED': If some players have paid and others haven't (unusual case)
+ """
+ # Get all player registrations for this team
+ player_registrations = self.player_registrations.all()
+
+ # If we have no players, return None
+ if not player_registrations.exists():
+ return None
+
+ # Check payment status for each player
+ payment_statuses = [player.has_paid() for player in player_registrations]
+
+ print(f"Payment statuses: {payment_statuses}")
+ print(all(payment_statuses))
+ # If all players have paid
+ if all(payment_statuses):
+ return 'PAID'
+
+ # If no players have paid
+ if not any(payment_statuses):
+ return 'UNPAID'
+
+ # If some players have paid and others haven't (unusual case)
+ return 'MIXED'
+
+ def is_payment_required(self):
+ """Check if payment is required for this team"""
+ return self.tournament.should_request_payment() and self.is_in_waiting_list() < 0
+
+ def is_paid(self):
+ """Check if this team has paid"""
+ status = self.get_payment_status()
+ return status == 'PAID'
+
+ def get_remaining_fee(self):
+ """Get the remaining fee for this team"""
+ status = self.get_payment_status()
+ if status == 'PAID':
+ return 0
+ elif status == 'UNPAID':
+ return self.tournament.entry_fee * 2
+ elif status == 'MIXED':
+ return self.tournament.entry_fee
diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py
index e58ed9c..44c0bde 100644
--- a/tournaments/models/tournament.py
+++ b/tournaments/models/tournament.py
@@ -78,6 +78,7 @@ class Tournament(BaseModel):
hide_umpire_mail = models.BooleanField(default=False)
hide_umpire_phone = models.BooleanField(default=True)
disable_ranking_federal_ruling = models.BooleanField(default=False)
+ reserved_spots = models.IntegerField(default=0)
def delete_dependencies(self):
for team_registration in self.team_registrations.all():
@@ -1699,6 +1700,11 @@ class Tournament(BaseModel):
team_registration__walk_out=False
).exists()
+ def should_request_payment(self):
+ return True
+
+ def team_fee(self):
+ return self.entry_fee * 2
class MatchGroup:
def __init__(self, name, matches, formatted_schedule, round_id=None):
diff --git a/tournaments/services/email_service.py b/tournaments/services/email_service.py
index ca0ca3e..6e2b40a 100644
--- a/tournaments/services/email_service.py
+++ b/tournaments/services/email_service.py
@@ -90,7 +90,15 @@ class TournamentEmailService:
f"\nDate d'inscription: {inscription_date}",
f"\nÉquipe inscrite: {captain.name()} et {other_player.name()}",
f"\nLe tournoi commencera le {tournament.formatted_start_date()} au club {tournament.event.club.name}",
- f"\nVoir les {absolute_url}",
+ f"\nVoir les {absolute_url}"
+ ])
+
+ # Add payment information if applicable
+ if tournament.should_request_payment:
+ payment_info = TournamentEmailService._build_payment_info(tournament, captain.team_registration)
+ body_parts.append(payment_info)
+
+ body_parts.extend([
"\nPour toute question, veuillez contacter votre juge-arbitre. Si vous n'êtes pas à l'origine de cette inscription, merci de le contacter rapidement.",
f"\n{TournamentEmailService._format_umpire_contact(tournament)}",
"\nCeci est un e-mail automatique, veuillez ne pas y répondre.",
@@ -485,3 +493,31 @@ class TournamentEmailService:
print("TournamentEmailService.notify_team 1p", team)
# If there's only one player, just send them the notification
TournamentEmailService.notify(players[0], None, tournament, message_type)
+
+ @staticmethod
+ def _build_payment_info(tournament, team_registration):
+ """
+ Build payment information section for emails
+ """
+ if not tournament.should_request_payment:
+ return ""
+
+ # Check payment status
+ payment_status = team_registration.get_payment_status()
+
+ if payment_status == 'PAID':
+ return "\n\n✅ Le paiement de votre inscription a bien été reçu."
+
+ # If the team is on the waiting list, don't mention payment
+ if team_registration.is_in_waiting_list() >= 0:
+ return ""
+
+ # For unpaid teams, add payment instructions
+ payment_info = [
+ "\n\n⚠️ Paiement des frais d'inscription requis",
+ f"Les frais d'inscription de {tournament.entry_fee}€ doivent être payés pour confirmer votre participation.",
+ "Vous pouvez effectuer le paiement en vous connectant à votre compte Padel Club.",
+ f"Lien pour payer: https://padelclub.app/tournament/{tournament.id}/info"
+ ]
+
+ return "\n".join(payment_info)
diff --git a/tournaments/services/registration_cart.py b/tournaments/services/registration_cart.py
index ab67176..82e976e 100644
--- a/tournaments/services/registration_cart.py
+++ b/tournaments/services/registration_cart.py
@@ -46,8 +46,24 @@ def initialize_registration_cart(request, tournament_id):
# Clear any existing cart
clear_registration_cart(request)
+ try:
+ tournament = Tournament.objects.get(id=tournament_id)
+ except Tournament.DoesNotExist:
+ return False, "Tournoi introuvable."
+
+ tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
+ waiting_list_position = tournament.get_waiting_list_position()
+
+ if waiting_list_position >= 0:
+ tournament.reserved_spots = 0
+ else:
+ tournament.reserved_spots += 1
+
+ tournament.save()
+
# Set up the new cart
request.session['registration_cart_id'] = str(uuid.uuid4())
+ request.session['waiting_list_position'] = waiting_list_position
request.session['registration_tournament_id'] = tournament_id
request.session['registration_cart_players'] = []
reset_cart_expiry(request)
@@ -71,6 +87,7 @@ def get_registration_cart_data(request):
cart_data = {
'cart_id': get_or_create_registration_cart_id(request),
'tournament_id': request.session.get('registration_tournament_id'),
+ 'waiting_list_position': request.session.get('waiting_list_position'),
'players': request.session.get('registration_cart_players', []),
'expiry': get_cart_expiry(request),
'mobile_number': request.session.get('registration_mobile_number',
@@ -287,7 +304,7 @@ def checkout_registration_cart(request):
birthdate=str(player_data.get('birth_year', '')),
captain=(idx == 0), # First player is captain
registered_online=True,
- registration_status='CONFIRMED' if tournament.get_waiting_list_position() < 0 else 'WAITING'
+ registration_status='CONFIRMED' if request.session['waiting_list_position'] < 0 else 'WAITING'
)
# Update user phone if provided
@@ -297,6 +314,8 @@ def checkout_registration_cart(request):
# Clear the cart
clear_registration_cart(request)
+ tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
+ tournament.save()
return True, team_registration
diff --git a/tournaments/templates/register_tournament.html b/tournaments/templates/register_tournament.html
index 456ca64..45b23d8 100644
--- a/tournaments/templates/register_tournament.html
+++ b/tournaments/templates/register_tournament.html
@@ -29,6 +29,7 @@
{% if not registration_successful %}
Votre session d'inscription est active. Complétez le formulaire pour confirmer votre participation.
+
DEBUG reserved_spots: {{ tournament.reserved_spots }}
{% endif %}
@@ -164,23 +165,29 @@
- {% if tournament.get_waiting_list_position == 1 %}
- Tournoi complet, {{ tournament.get_waiting_list_position }} équipe en liste d'attente actuellement.
- {% elif tournament.get_waiting_list_position > 1 %}
- Tournoi complet, {{ tournament.get_waiting_list_position }} équipes en liste d'attente actuellement.
- {% elif tournament.get_waiting_list_position == 0 %}
- Tournoi complet, vous seriez la première équipe en liste d'attente.
+ {% if cart_data.waiting_list_position == 1 %}
+ Tournoi complet, {{ cart_data.waiting_list_position }} équipe en liste d'attente actuellement.
+ {% elif cart_data.waiting_list_position > 1 %}
+ Tournoi complet, {{ cart_data.waiting_list_position }} équipes en liste d'attente actuellement.
+ {% elif cart_data.waiting_list_position == 0 %}
+ Tournoi complet, vous seriez la première équipe en liste d'attente.
{% endif %}
-
+ {% if tournament.should_request_payment and cart_data.waiting_list_position < 0 %}
+
+ {% else %}
+
+ {% endif %}
{% endif %}
diff --git a/tournaments/templates/tournaments/tournament_info.html b/tournaments/templates/tournaments/tournament_info.html
index c1fab19..a1b72f2 100644
--- a/tournaments/templates/tournaments/tournament_info.html
+++ b/tournaments/templates/tournaments/tournament_info.html
@@ -43,8 +43,31 @@
Inscrits le {{ team.local_registration_date }}
-
- {% if team and team.needs_confirmation %}
+ {% if messages %}
+
+ {% for message in messages %}
+
+ {{ message }}
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% if tournament.should_request_payment and team.is_in_waiting_list < 0 %}
+
+ {% if team.is_paid %}
+
+ ✅ Paiement confirmé
+
+ {% else %}
+
+ {% endif %}
+
+ {% elif team.needs_confirmation %}
Confirmation requise
Votre place dans le tournoi a été libérée suite à une désinscription.
@@ -79,7 +102,7 @@
{% endif %}
{% if tournament.is_unregistration_possible %}
-
+