timetoconfirm
Raz 7 months ago
parent a19afb2299
commit 3f95477066
  1. 2
      tournaments/models/__init__.py
  2. 92
      tournaments/models/team_registration.py
  3. 52
      tournaments/models/tournament.py
  4. 89
      tournaments/tasks.py
  5. 36
      tournaments/templates/tournaments/tournament_info.html
  6. 11
      tournaments/views.py

@ -4,7 +4,7 @@ from .custom_user import CustomUser
from .club import Club from .club import Club
from .court import Court from .court import Court
from .date_interval import DateInterval from .date_interval import DateInterval
from .enums import UserOrigin, TournamentPayment, FederalCategory, FederalLevelCategory, FederalAgeCategory, FederalMatchCategory, OnlineRegistrationStatus, ModelOperation from .enums import UserOrigin, TournamentPayment, FederalCategory, FederalLevelCategory, FederalAgeCategory, FederalMatchCategory, OnlineRegistrationStatus, RegistrationStatus, ModelOperation
from .player_enums import PlayerSexType, PlayerDataSource, PlayerPaymentType from .player_enums import PlayerSexType, PlayerDataSource, PlayerPaymentType
from .event import Event from .event import Event
from .tournament import Tournament, TeamSummon, TeamSortingType, TeamItem from .tournament import Tournament, TeamSummon, TeamSortingType, TeamItem

@ -4,6 +4,7 @@ import uuid
from django.utils import timezone from django.utils import timezone
from .enums import RegistrationStatus from .enums import RegistrationStatus
from .player_enums import PlayerPaymentType from .player_enums import PlayerPaymentType
from ..services.email_service import TournamentEmailService, TeamEmailType
class TeamRegistration(SideStoreModel): class TeamRegistration(SideStoreModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
@ -407,3 +408,94 @@ class TeamRegistration(SideStoreModel):
return self.tournament.team_fee() return self.tournament.team_fee()
elif status == 'MIXED': elif status == 'MIXED':
return self.tournament.player_fee() return self.tournament.player_fee()
def is_confirmation_expired(self):
"""
Check if the confirmation deadline has expired.
Returns:
bool: True if expired, False if still valid or no deadline exists
"""
deadline = self.get_confirmation_deadline()
if not deadline:
return False
current_time = timezone.now()
return deadline < current_time
def format_confirmation_deadline(self):
"""
Format the confirmation deadline in a human-readable format.
Returns:
str: Formatted deadline, or None if no deadline exists
"""
deadline = self.get_confirmation_deadline()
if not deadline:
return None
if self.tournament and self.tournament.timezone():
deadline = deadline.astimezone(self.tournament.timezone())
return deadline.strftime("%d/%m/%Y à %H:%M")
def check_confirmation_deadline(self, tournament_context=None):
"""
Check if the confirmation deadline for this team has expired and perform necessary actions.
Args:
tournament_context (dict, optional): Pre-calculated tournament context to avoid redundant calls.
If None, will calculate on-demand.
"""
now = timezone.now()
tournament = self.tournament
if not tournament:
return
# Use provided context or calculate if not provided
if tournament_context is None:
teams = tournament.teams(True)
waiting_list_teams = tournament.waiting_list_teams(teams)
ttc = tournament.calculate_time_to_confirm(len(waiting_list_teams)) if waiting_list_teams is not None else None
first_waiting_list_team = tournament.first_waiting_list_team(teams)
is_online_registration_irrevelant = tournament.is_online_registration_irrevelant()
else:
ttc = tournament_context.get('ttc')
first_waiting_list_team = tournament_context.get('first_waiting_list_team')
is_online_registration_irrevelant = tournament_context.get('is_online_registration_irrevelant', False)
# Get all players in this team
team_players = self.player_registrations.filter(registered_online=True)
should_update_team = False
should_send_mail = False
for team_player in team_players:
if is_online_registration_irrevelant:
team_player.registration_status = RegistrationStatus.CANCELED
team_player.save()
elif team_player.time_to_confirm is None and first_waiting_list_team is not None:
self.set_time_to_confirm(ttc)
should_send_mail = True
print(team_player, "team_player.time_to_confirm is None and", ttc)
elif team_player.time_to_confirm is not None and now > team_player.time_to_confirm:
if first_waiting_list_team is not None:
team_player.registration_status = RegistrationStatus.CANCELED
self.registration_date = now
should_update_team = True
team_player.time_to_confirm = None
team_player.save()
print(team_player, "time_to_confirm = ", team_player.time_to_confirm)
if should_update_team:
self.save()
print(f"Team {self} confirmation expired in tournament {tournament.id}")
if should_send_mail:
TournamentEmailService.notify_team(
self,
tournament,
TeamEmailType.REQUIRES_TIME_CONFIRMATION
)

@ -1,6 +1,7 @@
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from django.db import models from django.db import models
from . import BaseModel, Event, TournamentPayment, FederalMatchCategory, FederalCategory, FederalLevelCategory, FederalAgeCategory, OnlineRegistrationStatus from . import BaseModel, Event, TournamentPayment, FederalMatchCategory, FederalCategory, FederalLevelCategory, FederalAgeCategory, OnlineRegistrationStatus, RegistrationStatus
import uuid import uuid
from django.utils import timezone, formats from django.utils import timezone, formats
from datetime import datetime, timedelta, time from datetime import datetime, timedelta, time
@ -1812,6 +1813,55 @@ class Tournament(BaseModel):
"""Get the commission rate for this tournament, falling back to the umpire default if not set""" """Get the commission rate for this tournament, falling back to the umpire default if not set"""
return 1.00 # Fallback default return 1.00 # Fallback default
def check_all_confirmation_deadlines(self):
"""
Check all confirmation deadlines for teams in this tournament.
Send notification emails as needed.
Returns:
int: Number of teams processed
"""
# Calculate these values once for the tournament
teams = self.teams(True)
waiting_list_teams = self.waiting_list_teams(teams)
ttc = self.calculate_time_to_confirm(len(waiting_list_teams)) if waiting_list_teams is not None else None
first_waiting_list_team = self.first_waiting_list_team(teams)
# Tournament context dict to pass to each team check
tournament_context = {
'ttc': ttc,
'first_waiting_list_team': first_waiting_list_team,
'is_online_registration_irrevelant': self.is_online_registration_irrevelant()
}
# Find players with expired confirmation deadlines in this tournament
PlayerRegistration = apps.get_model('tournaments', 'PlayerRegistration')
expired_confirmations = PlayerRegistration.objects.filter(
registration_status=RegistrationStatus.PENDING,
registered_online=True,
team_registration__tournament=self
).select_related('team_registration')
processed_teams = set() # To avoid processing the same team multiple times
teams_processed = 0
for player in expired_confirmations:
team_registration = player.team_registration
# Skip if we've already processed this team
if team_registration.id in processed_teams:
continue
processed_teams.add(team_registration.id)
teams_processed += 1
# Process in a transaction to ensure atomic operations
team_registration.check_confirmation_deadline(tournament_context)
return teams_processed
class MatchGroup: class MatchGroup:
def __init__(self, name, matches, formatted_schedule, round_id=None): def __init__(self, name, matches, formatted_schedule, round_id=None):

@ -3,9 +3,8 @@ from django.utils import timezone
from django.db import transaction from django.db import transaction
from django.conf import settings from django.conf import settings
from .models import PlayerRegistration from .models import Tournament
from .models.enums import RegistrationStatus from .models.enums import RegistrationStatus
from .services.email_service import TournamentEmailService, TeamEmailType
@background(schedule=settings.BACKGROUND_SCHEDULED_TASK_INTERVAL * 60) # Run every 30 minutes (30*60 seconds) @background(schedule=settings.BACKGROUND_SCHEDULED_TASK_INTERVAL * 60) # Run every 30 minutes (30*60 seconds)
def background_task_check_confirmation_deadlines(): def background_task_check_confirmation_deadlines():
@ -21,81 +20,15 @@ def check_confirmation_deadlines():
now = timezone.now() now = timezone.now()
print(f"[{now}] Running confirmation deadline check...") print(f"[{now}] Running confirmation deadline check...")
# Find players with expired confirmation deadlines # Get all tournaments with online registrations
expired_confirmations = PlayerRegistration.objects.filter( tournaments = Tournament.objects.filter(
registration_status=RegistrationStatus.PENDING, team_registrations__player_registrations__registration_status=RegistrationStatus.PENDING,
registered_online=True, team_registrations__player_registrations__registered_online=True
team_registration__isnull=False ).distinct()
).select_related('team_registration', 'team_registration__tournament')
print(f"Found {expired_confirmations.count()} expired confirmations") total_processed = 0
for tournament in tournaments:
processed = tournament.check_all_confirmation_deadlines()
total_processed += processed
# Process each expired confirmation print(f"Processed confirmation deadlines for {total_processed} teams")
processed_teams = set() # To avoid processing the same team multiple times
for player in expired_confirmations:
team_registration = player.team_registration
# Skip if we've already processed this team
if team_registration.id in processed_teams:
continue
processed_teams.add(team_registration.id)
tournament = team_registration.tournament
if not tournament:
continue
teams = tournament.teams(True)
waiting_list_teams = tournament.waiting_list_teams(teams)
if waiting_list_teams is not None:
ttc = tournament.calculate_time_to_confirm(len(waiting_list_teams))
else:
ttc = None
first_waiting_list_team = tournament.first_waiting_list_team(teams)
# Process in a transaction to ensure atomic operations
with transaction.atomic():
# Get all players in this team and mark them as expired
team_players = PlayerRegistration.objects.filter(
team_registration=team_registration,
registered_online=True
)
should_update_team = False
should_send_mail = False
is_online_registration_irrevelant = team_registration.tournament.is_online_registration_irrevelant()
for team_player in team_players:
if is_online_registration_irrevelant:
team_player.registration_status = RegistrationStatus.CANCELED
team_player.save()
elif team_player.time_to_confirm is None and first_waiting_list_team is not None:
team_registration.set_time_to_confirm(ttc)
team_player.save()
should_send_mail = True
print(team_player, "team_player.time_to_confirm is None and", ttc)
elif team_player.time_to_confirm is not None and now > team_player.time_to_confirm:
if first_waiting_list_team is not None:
team_player.registration_status = RegistrationStatus.CANCELED
team_registration.registration_date = now
should_update_team = True
team_player.time_to_confirm = None
team_player.save()
print(team_player, "time_to_confirm = ", team_player.time_to_confirm)
# elif team_player.time_to_confirm is not None and team_player.time_to_confirm > ttc:
# team_player.registration_status = RegistrationStatus.PENDING
# team_player.time_to_confirm = ttc
# team_player.save()
# should_update_team = True
if should_send_mail:
TournamentEmailService.notify_team(
team_registration,
tournament,
TeamEmailType.REQUIRES_TIME_CONFIRMATION
)
if should_update_team:
team_registration.save()
print(f"Team {team_registration} confirmation expired in tournament {tournament.id}")

@ -51,30 +51,20 @@
</p> </p>
{% if team.needs_confirmation %} {% if team.needs_confirmation %}
<h4 class="semibold">Confirmation requise</h4> <h4 class="semibold">Confirmation requise</h4>
<div>Une place dans le tournoi a été libérée !</div> {% if tournament.should_request_payment and tournament.online_payment_is_mandatory %}
{% if tournament.should_request_payment and tournament.online_payment_is_mandatory %} <div>Vous devez payer votre inscription pour confirmer votre participation.</div>
<div>Vous devez payer votre inscription pour confirmer votre participation.</div> {% endif %}
{% endif %} {% if team.get_confirmation_deadline %}
{% if team.get_confirmation_deadline %} {% if team.is_confirmation_expired %}
{% timezone tournament.timezone %} <p class="alert alert-danger">
{% with time_to_confirm=team.get_confirmation_deadline %} Le délai de confirmation est expiré. Votre place a été proposée à une autre équipe.
{% now "Y-m-d H:i:s" as current_time_str %} </p>
{% if time_to_confirm %} {% else %}
{% if time_to_confirm|date:"Y-m-d H:i:s" > current_time_str %} <div>Vous devez confirmer votre participation avant le : </div>
<div>Vous devez confirmer votre participation avant le : </div> <p class="semibold alert">{{ team.format_confirmation_deadline }}</p>
<p class="semibold alert">{{ time_to_confirm|date:"d/m/Y à H:i" }}</p>
<p>Temps restant: <span class="countdown-timer">{{ time_to_confirm|timeuntil }}</span></p>
{% else %}
<p class="alert alert-danger">
Le délai de confirmation est expiré. Votre place a été proposée à une autre équipe.
</p>
{% endif %}
{% endif %}
{% endwith %}
{% endtimezone %}
{% endif %} {% endif %}
{% endif %}
{% endif %} {% endif %}
{% if team.call_date %} {% if team.call_date %}

@ -1076,6 +1076,12 @@ def confirm_tournament_registration(request, tournament_id):
return redirect('tournament-info', tournament_id=tournament_id) return redirect('tournament-info', tournament_id=tournament_id)
team_registration = team_registrations.first() team_registration = team_registrations.first()
if team_registration.is_confirmation_expired():
team_registration.check_confirmation_deadline()
messages.error(request, "Le délai de confirmation est expiré. Vous ne pouvez plus confirmer votre participation.")
return redirect('tournament-info', tournament_id=tournament_id)
# Confirm registration # Confirm registration
team_registration.confirm_registration() team_registration.confirm_registration()
@ -1094,6 +1100,11 @@ def proceed_to_payment(request, tournament_id):
messages.error(request, "Aucune inscription trouvée pour ce tournoi.") messages.error(request, "Aucune inscription trouvée pour ce tournoi.")
return redirect('tournament-info', tournament_id=tournament_id) return redirect('tournament-info', tournament_id=tournament_id)
if team.is_confirmation_expired():
team.check_confirmation_deadline()
messages.error(request, "Le délai de confirmation est expiré. Vous ne pouvez plus régler votre participation.")
return redirect('tournament-info', tournament_id=tournament_id)
if team.is_paid(): if team.is_paid():
messages.info(request, "Votre inscription est déjà payée.") messages.info(request, "Votre inscription est déjà payée.")
return redirect('tournament-info', tournament_id=tournament_id) return redirect('tournament-info', tournament_id=tournament_id)

Loading…
Cancel
Save