You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
padelclub_backend/tournaments/models/team_registration.py

497 lines
19 KiB

from django.db import models
from django.utils import timezone
from . import TournamentSubModel, Tournament, GroupStage, Match, CustomUser
from .enums import RegistrationStatus
from .player_enums import PlayerPaymentType
from ..services.email_service import TournamentEmailService, TeamEmailType
import uuid
class TeamRegistration(TournamentSubModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
tournament = models.ForeignKey(Tournament, on_delete=models.SET_NULL, related_name='team_registrations', null=True)
group_stage = models.ForeignKey(GroupStage, null=True, blank=True, on_delete=models.SET_NULL, related_name='team_registrations')
user = models.ForeignKey(CustomUser, null=True, blank=True, on_delete=models.SET_NULL, related_name='team_registrations')
registration_date = models.DateTimeField(null=True, blank=True)
call_date = models.DateTimeField(null=True, blank=True)
bracket_position = models.IntegerField(null=True, blank=True)
group_stage_position = models.IntegerField(null=True, blank=True)
comment = models.CharField(max_length=200, null=True, blank=True)
source = models.CharField(max_length=20, null=True, blank=True)
source_value = models.CharField(max_length=200, null=True, blank=True)
logo = models.CharField(max_length=200, null=True, blank=True) #models.FilePathField(path=os.path.join(settings.STATIC_ROOT, "images"), null=True, blank=True)
name = models.CharField(max_length=200, null=True, blank=True)
walk_out = models.BooleanField(default=False)
wild_card_bracket = models.BooleanField(default=False)
wild_card_group_stage = models.BooleanField(default=False)
weight = models.IntegerField(default=0)
locked_weight = models.IntegerField(null=True, blank=True)
confirmation_date = models.DateTimeField(null=True, blank=True)
qualified = models.BooleanField(default=False)
final_ranking = models.IntegerField(null=True, blank=True)
points_earned = models.IntegerField(null=True, blank=True)
def delete_dependencies(self):
for player_registration in self.player_registrations.all():
# player_registration.delete_dependencies()
player_registration.delete()
for team_score in self.team_scores.all():
# match.delete_dependencies()
team_score.delete()
def __str__(self):
if self.name:
return self.name
# return f"{self.name}: {self.player_names()}"
return self.player_names()
def get_tournament(self): # mandatory method for TournamentSubModel
return self.tournament
def player_names_as_list(self):
players = list(self.players_sorted_by_rank)
if len(players) == 0:
return []
elif len(players) == 1:
return [players[0].name()]
else:
return [pr.name() for pr in players]
def team_names(self):
if self.name:
return [self.name] #add an empty line if it's a team name
else:
return self.player_names_as_list()
def shortened_team_names(self):
if self.name:
return [self.name] #add an empty line if it's a team name
else:
players = list(self.players_sorted_by_rank)
if len(players) == 0:
if self.wild_card_bracket:
return ['Place réservée wildcard']
elif self.wild_card_group_stage:
return ['Place réservée wildcard']
else:
return ['Place réservée']
elif len(players) == 1:
return [players[0].shortened_name()]
else:
return [pr.shortened_name() for pr in players]
@property
def players_sorted_by_rank(self):
# Fetch related PlayerRegistration objects
return self.player_registrations.all().order_by('rank')
def player_names(self):
names = self.player_names_as_list()
str = " - ".join(names)
if len(str) > 0:
return str
else:
return "no players"
def formatted_team_names(self):
if self.name:
return self.name
names = [pr.last_name for pr in self.players_sorted_by_rank][:2] # Take max first 2
joined_names = " / ".join(names)
if joined_names:
return f"Paire {joined_names}"
return "Détail de l'équipe"
def next_match(self):
all_matches = [ts.match for ts in self.team_scores.all() if ts.match is not None]
now = timezone.now()
all_matches = sorted(all_matches, key=lambda m: m.start_date if m.start_date is not None else now)
matches = [m for m in all_matches if m.end_date is None]
if matches:
return matches[0]
else:
return None
def next_stage(self):
matches = map(lambda ts: ts.match, self.team_scores.all())
matches = [m for m in matches if m.group_stage is None]
matches = sorted(matches, key=lambda m: m.round.index)
# matches = self.teamscore_set
# matches = Match.objects.filter(group_stage__isnull=True, team_scores__player_registrations__id=self.id).order_by('round__index')
# print(f"matches = {len(matches)}")
if matches:
return matches[0].round.name()
elif self.group_stage:
return self.group_stage.name()
else:
return "--"
def is_valid_for_summon(self):
return self.players_sorted_by_rank.count() > 0 or self.name is not None
def initial_weight(self):
if self.locked_weight is None:
return self.weight
else:
return self.locked_weight
def local_call_date(self):
timezone = self.tournament.timezone()
if self.call_date:
return self.call_date.astimezone(timezone)
else:
# print("no date")
return None
def local_registration_date(self):
timezone = self.tournament.timezone()
if self.registration_date:
return self.registration_date.astimezone(timezone)
else:
# print("no date")
return None
def out_of_tournament(self):
return self.walk_out
def get_other_player(self, player):
for p in self.players_sorted_by_rank:
if p != player:
return p
return None
def is_in_waiting_list(self):
return self.tournament.get_team_waiting_list_position(self)
def get_matches(self):
matches = Match.objects.filter(team_scores__team_registration=self).distinct()
# print(f"All matches for team {self.id}: {matches.count()}")
# for match in matches:
# print(f"Match {match.id}: start_date={match.start_date}, end_date={match.end_date}")
return matches
def get_upcoming_matches(self):
if self.tournament and self.tournament.display_matches() is False:
return []
matches = self.get_matches()
upcoming = matches.filter(end_date__isnull=True).order_by('start_date')
print(f"Upcoming matches count: {upcoming.count()}")
return [match.live_match() for match in upcoming]
def get_completed_matches(self):
matches = self.get_matches()
completed = matches.filter(end_date__isnull=False).order_by('-end_date')
print(f"Completed matches count: {completed.count()}")
return [match.live_match() for match in completed]
def get_statistics(self):
stats = {
'final_ranking': self.get_final_ranking(),
'weight': self.weight,
'points_earned': self.get_points_earned(),
'initial_stage': self.get_initial_stage(),
'matches_played': self.count_matches_played(),
'victory_ratio': self.calculate_victory_ratio(),
'team_rank': self.team_rank_label(),
'total_teams': self.total_teams(),
}
return stats
def team_rank(self):
teams = self.tournament.teams(False) # Get list of TeamItem objects
try:
# Find the TeamItem that corresponds to this TeamRegistration
team_index = next(i for i, team in enumerate(teams)
if team.team_registration.id == self.id)
return team_index + 1
except (StopIteration, ValueError):
return None
def team_rank_label(self):
team_rank = self.team_rank()
if team_rank is None:
return "--"
return f"{team_rank}"
def total_teams(self):
teams = self.tournament.teams(False)
return f"{len(teams)}"
def get_initial_stage(self):
matches = self.get_matches().order_by('start_date')
first_match = matches.first()
if first_match:
if first_match.group_stage:
return "Poule"
elif first_match.round:
return first_match.round.name()
return None
def get_final_ranking(self):
get_final_ranking_component = self.get_final_ranking_component()
if get_final_ranking_component:
return get_final_ranking_component + self.ranking_delta()
return None
def get_final_ranking_component(self):
if self.final_ranking:
if self.final_ranking == 1:
return "1er"
return f"{self.final_ranking}ème"
return None
def ranking_delta(self):
team_rank = self.team_rank()
if team_rank is None or self.final_ranking is None:
return ""
sign = "-"
if team_rank > self.final_ranking:
sign = "+"
if team_rank == self.final_ranking:
sign = "+"
return f" ({sign}"+f"{abs(self.final_ranking - team_rank)})"
def get_points_earned(self):
return self.points_earned
def calculate_total_duration(self):
total_seconds = 0
for match in self.get_matches().filter(end_date__isnull=False):
if match.start_date and match.end_date:
duration = (match.end_date - match.start_date).total_seconds()
total_seconds += duration
if total_seconds > 0:
hours = int(total_seconds // 3600)
minutes = int((total_seconds % 3600) // 60)
if hours > 0:
return f"{hours}h{minutes:02d}"
return f"{minutes}min"
return None
def count_matches_played(self):
return self.get_matches().filter(end_date__isnull=False).count()
def calculate_victory_ratio(self):
matches = self.get_matches().filter(end_date__isnull=False)
total_matches = matches.count()
if total_matches > 0:
wins = matches.filter(winning_team_id=self.id).count()
# ratio = (wins / total_matches) * 100
return f"{wins}/{total_matches}"
return None
def has_registered_online(self):
for p in self.players_sorted_by_rank:
if p.registered_online:
return True
return False
def formatted_special_status(self):
if self.wild_card_bracket:
return "(wildcard tableau)"
if self.wild_card_group_stage:
return "(wildcard poule)"
return ""
def set_time_to_confirm(self, ttc):
for p in self.players_sorted_by_rank:
if p.registered_online:
p.time_to_confirm = ttc
p.registration_status = RegistrationStatus.PENDING
p.save()
def cancel_time_to_confirm(self):
for p in self.players_sorted_by_rank:
if p.registered_online:
save = False
if p.time_to_confirm is not None:
save = True
p.time_to_confirm = None
if p.registration_status == RegistrationStatus.PENDING:
save = True
p.registration_status = RegistrationStatus.WAITING
if save:
p.save()
def needs_confirmation(self):
"""Check if this team needs to confirm their registration"""
# Check if any player has status PENDING and is registered online
return any(p.registration_status == RegistrationStatus.PENDING and p.registered_online
for p in self.players_sorted_by_rank)
def get_confirmation_deadline(self):
"""Get the confirmation deadline for this team"""
deadlines = [p.time_to_confirm for p in self.players_sorted_by_rank if p.time_to_confirm is not None]
return max(deadlines) if deadlines else None
def confirm_registration(self, payment_intent_id=None):
"""Confirm the team's registration after being moved from waiting list"""
# Update all players in the team
for player in self.players_sorted_by_rank:
player.time_to_confirm = None
player.payment_id = payment_intent_id
if payment_intent_id is not None:
player.payment_type = PlayerPaymentType.CREDIT_CARD
player.registration_status = RegistrationStatus.CONFIRMED
player.save()
def confirm_if_placed(self):
if self.needs_confirmation() is False:
return
if self.group_stage or self.bracket_position or self.confirmation_date is not None:
for player in self.players_sorted_by_rank:
if player.registration_status is not RegistrationStatus.CONFIRMED:
player.time_to_confirm = None
player.registration_status = RegistrationStatus.CONFIRMED
player.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.players_sorted_by_rank
# 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}")
# 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.team_fee()
elif status == 'MIXED':
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
)