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 from ..utils.extensions import format_seconds from ..services.currency_service import CurrencyService 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) unique_random_index = models.IntegerField(default=0) user_canceled_registration = False 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, forced=False): if self.name: return [self.name] #add an empty line if it's a team name else: player_count = self.player_registrations.count() if player_count == 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 player_count == 1: players = self.players_sorted_by_rank return [players[0].shortened_name(forced=forced)] else: players = self.players_sorted_by_rank return [pr.shortened_name(forced=forced) for pr in players] @property def players_sorted_by_rank(self): return self.player_registrations.all().order_by('rank') @property def players_sorted_by_captain(self): return self.player_registrations.all().order_by('-captain') 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.get_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(), 'time_played': self.calculate_time_played(), '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_time_played(self): total_seconds = 0 for match in self.get_matches().filter(end_date__isnull=False): total_seconds += match.smart_time_played() return format_seconds(total_seconds) 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_pre_registration(self): """Confirm the team's pre-registration""" # Update all players in the team for player in self.players_sorted_by_rank: print(f"Updating player {player.first_name} {player.last_name} (ID: {player.id})") player.registration_status = RegistrationStatus.PENDING player.save() def confirm_registration(self, payment_intent_id=None): """Confirm the team's registration after being moved from waiting list""" print(f"Confirming registration for team {self.id} with payment {payment_intent_id}") # Update all players in the team for player in self.players_sorted_by_rank: print(f"Updating player {player.first_name} {player.last_name} (ID: {player.id})") # # Vérifier si ce joueur a déjà ce payment_id # if player.payment_id == payment_intent_id and payment_intent_id: # print(f"Player {player.id} already has payment_id {payment_intent_id}, skipping") # continue 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() print(f"✅ Updated player {player.id} with payment {payment_intent_id}") 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 all player registrations for this team player_registrations = self.players_sorted_by_rank # Check payment status for each player payment_statuses = [player.get_remaining_fee() for player in player_registrations] return sum(payment_statuses) def get_remaining_fee_formatted(self): """Get the remaining fee formatted with the tournament's currency.""" remaining_fee = self.get_remaining_fee() tournament = self.get_tournament() currency_code = tournament.currency_code if tournament and tournament.currency_code else 'EUR' return CurrencyService.format_amount(remaining_fee, currency_code) 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 ) def is_unregistration_possible(self): if self.call_date is not None: return False if self.bracket_position is not None: return False if self.group_stage_position is not None: return False return True def is_positioned(self): return self.bracket_position is not None or self.group_stage_position is not None def get_team_registration_fee(self): # Get all player registrations for this team player_registrations = self.players_sorted_by_rank # Check payment status for each player payment_statuses = [player.get_player_registration_fee() for player in player_registrations] return sum(payment_statuses) def team_contact(self): if self.user: return self.user.email else: player_registrations = self.players_sorted_by_captain if len(player_registrations) > 0: return player_registrations[0].player_contact() return None def cancel_registration(self): self.walk_out = True self.user_canceled_registration = True def user_did_cancel_registration(self): return self.user_canceled_registration and self.walk_out