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.
570 lines
22 KiB
570 lines
22 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
|
|
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.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
|
|
|