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 }}
Votre place dans le tournoi a été libérée suite à une désinscription.
@@ -79,7 +102,7 @@ {% endif %} {% if tournament.is_unregistration_possible %} -+