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/services/tournament_registration.py

462 lines
19 KiB

from django.utils import timezone
import uuid
import datetime
from ..models import PlayerRegistration, TeamRegistration, Tournament
from ..utils.licence_validator import LicenseValidator
from ..utils.player_search import get_player_name_from_csv
from ..models.enums import FederalCategory
from ..models.player_enums import PlayerSexType, PlayerDataSource
class RegistrationCartManager:
"""
Manages the registration cart for tournament registrations.
Handles session-based cart operations, player additions/removals,
and checkout processes.
"""
CART_EXPIRY_SECONDS = 300
def __init__(self, request):
self.request = request
self.session = request.session
def get_or_create_cart_id(self):
"""Get or create a registration cart ID in the session"""
if 'registration_cart_id' not in self.session:
self.session['registration_cart_id'] = str(uuid.uuid4()) # Ensure it's a string
self.session.modified = True
return self.session['registration_cart_id']
def get_cart_expiry(self):
"""Get the cart expiry time from the session"""
if 'registration_cart_expiry' not in self.session:
# Set default expiry to 30 minutes from now
expiry = timezone.now() + datetime.timedelta(seconds=self.CART_EXPIRY_SECONDS)
self.session['registration_cart_expiry'] = expiry.isoformat()
self.session.modified = True
return self.session['registration_cart_expiry']
def is_cart_expired(self):
"""Check if the registration cart is expired"""
if 'registration_cart_expiry' not in self.session:
return False
expiry_str = self.session['registration_cart_expiry']
try:
expiry = datetime.datetime.fromisoformat(expiry_str)
return timezone.now() > expiry
except (ValueError, TypeError):
return True
def reset_cart_expiry(self):
"""Reset the cart expiry time"""
expiry = timezone.now() + datetime.timedelta(seconds=self.CART_EXPIRY_SECONDS)
self.session['registration_cart_expiry'] = expiry.isoformat()
self.session.modified = True
def get_tournament_id(self):
"""Get the tournament ID associated with the current cart"""
return self.session.get('registration_tournament_id')
def initialize_cart(self, tournament_id):
"""Initialize a new registration cart for a tournament"""
# Clear any existing cart
self.clear_cart()
try:
tournament = Tournament.objects.get(id=tournament_id)
except Tournament.DoesNotExist:
return False, "Tournoi introuvable."
# Update tournament reserved spots
tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
waiting_list_position = tournament.get_waiting_list_position()
tournament.reserved_spots += 1
tournament.save()
# Set up the new cart
self.session['registration_cart_id'] = str(uuid.uuid4()) # Ensure it's a string
self.session['waiting_list_position'] = waiting_list_position
self.session['registration_tournament_id'] = str(tournament_id) # Ensure it's a string
self.session['registration_cart_players'] = []
self.reset_cart_expiry()
self.session.modified = True
return True, "Cart initialized successfully"
def get_cart_data(self):
"""Get the data for the current registration cart"""
# Ensure cart players array exists
if 'registration_cart_players' not in self.session:
self.session['registration_cart_players'] = []
self.session.modified = True
# Ensure tournament ID exists
if 'registration_tournament_id' not in self.session:
# If no tournament ID but we have players, this is an inconsistency
if self.session.get('registration_cart_players'):
print("WARNING: Found players but no tournament ID - clearing players")
self.session['registration_cart_players'] = []
self.session.modified = True
# Get user phone if authenticated
user_phone = ''
if hasattr(self.request.user, 'phone'):
user_phone = self.request.user.phone
# Parse the expiry time from ISO format to datetime
expiry_str = self.get_cart_expiry()
expiry_datetime = None
if expiry_str:
try:
# Parse the ISO format string to datetime
from django.utils.dateparse import parse_datetime
expiry_datetime = parse_datetime(expiry_str)
except (ValueError, TypeError):
# If parsing fails, set a new expiry
expiry_datetime = timezone.now() + datetime.timedelta(seconds=self.CART_EXPIRY_SECONDS)
cart_data = {
'cart_id': self.get_or_create_cart_id(),
'tournament_id': self.session.get('registration_tournament_id'),
'waiting_list_position': self.session.get('waiting_list_position'),
'players': self.session.get('registration_cart_players', []),
'expiry': expiry_datetime, # Now a datetime object, not a string
'is_cart_expired': self.is_cart_expired(),
'mobile_number': self.session.get('registration_mobile_number', user_phone)
}
# Debug: print the cart content
print(f"Cart data - Tournament ID: {cart_data['tournament_id']}")
print(f"Cart data - Players count: {len(cart_data['players'])}")
return cart_data
def add_player(self, player_data):
"""Add a player to the registration cart"""
if self.is_cart_expired():
return False, "Votre session d'inscription a expiré, veuillez réessayer."
# Get cart data
tournament_id = self.session.get('registration_tournament_id')
if not tournament_id:
return False, "Pas d'inscription active."
# Get tournament
try:
tournament = Tournament.objects.get(id=tournament_id)
except Tournament.DoesNotExist:
return False, "Tournoi introuvable."
# Get existing players directly from session
players = self.session.get('registration_cart_players', [])
# Check if we've reached the team limit
if len(players) >= 2: # Assuming teams of 2 for padel
return False, "Nombre maximum de joueurs déjà ajouté."
# Process player data
licence_id = player_data.get('licence_id', '').upper() if player_data.get('licence_id') else None
first_name = player_data.get('first_name', '')
last_name = player_data.get('last_name', '').upper() if player_data.get('last_name') else ''
# Handle license validation logic
result = self._process_player_license(
tournament, licence_id, first_name, last_name, players, len(players) == 0
)
if not result[0]:
return result # Return the error
# If we got here, license validation passed
if licence_id:
# Get federation data
fed_data, found = get_player_name_from_csv(tournament.federal_category, licence_id)
if found and fed_data:
# Use federation data (including check for eligibility)
player_register_check = tournament.player_register_check(licence_id)
if player_register_check:
return False, ", ".join(player_register_check)
# Update player data from federation data
player_data.update({
'first_name': fed_data['first_name'],
'last_name': fed_data['last_name'],
'rank': fed_data['rank'],
'is_woman': fed_data['is_woman'],
'points': fed_data.get('points'),
'assimilation': fed_data.get('assimilation'),
'tournament_count': fed_data.get('tournament_count'),
'ligue_name': fed_data.get('ligue_name'),
'club_name': fed_data.get('club_name'),
'birth_year': fed_data.get('birth_year'),
'found_in_french_federation': True,
})
elif tournament.license_is_required:
# License required but not found in federation data
return False, "La licence fournit n'a pas été trouvée dans la base FFT. Contactez le juge arbitre si cette licence est valide."
elif not first_name or not last_name:
# License not required or not found, but name is needed
return False, "Le prénom et le nom sont obligatoires pour les joueurs sans licence."
elif not tournament.license_is_required:
# License not required, check if name is provided
if not first_name or not last_name:
return False, "Le prénom et le nom sont obligatoires pour les joueurs sans licence."
# Set default rank for players without a license
if player_data.get('rank') is None:
default_data, _ = get_player_name_from_csv(tournament.federal_category, None)
if default_data:
player_data['rank'] = default_data.get('rank')
player_data['is_woman'] = default_data.get('is_woman', False)
else:
# License is required but not provided
return False, "Le numéro de licence est obligatoire."
# Add player to cart
players.append(player_data)
self.session['registration_cart_players'] = players
self.reset_cart_expiry()
self.session.modified = True
return True, "Joueur ajouté avec succès."
def _process_player_license(self, tournament, licence_id, first_name, last_name, players, is_first_player):
"""
Process and validate player license
Returns (True, None) if valid, (False, error_message) if invalid
"""
# Handle case where license is required
if tournament.license_is_required:
# If license is required but not provided
if not licence_id:
# First player (authentication check) or partner
user_message = "Le numéro de licence est obligatoire." if is_first_player else "Le numéro de licence de votre partenaire est obligatoire."
return False, user_message
# Validate the license format
validator = LicenseValidator(licence_id)
if not validator.validate_license():
return False, "Le numéro de licence est invalide, la lettre ne correspond pas."
# Check if player is already registered in tournament
stripped_license = validator.stripped_license
if self._is_player_already_registered(stripped_license, tournament):
return False, "Un joueur avec ce numéro de licence est déjà inscrit dans une équipe."
# Check if this is the authenticated user trying to register as first player
if self.request.user.is_authenticated and is_first_player and self.request.user.licence_id is None:
# Try to update the user's license ID in the database
try:
self.request.user.licence_id = validator.computed_licence_id
self.request.user.save()
self.request.user.refresh_from_db()
except:
return False, "Erreur lors de la mise à jour de votre licence: cette licence est déjà utilisée par un autre joueur."
# Check for duplicate licenses in cart
existing_licenses = [p.get('licence_id') for p in players if p.get('licence_id')]
if licence_id and licence_id in existing_licenses:
return False, "Ce joueur est déjà dans l'équipe."
return True, None
def remove_player(self):
"""Remove the last player from the cart"""
if self.is_cart_expired():
return False, "Votre session d'inscription a expiré, veuillez réessayer."
players = self.session.get('registration_cart_players', [])
if not players:
return False, "Pas de joueur à supprimer."
# Remove last player
players.pop()
self.session['registration_cart_players'] = players
self.reset_cart_expiry()
self.session.modified = True
return True, "Joueur retiré."
def update_contact_info(self, mobile_number=None):
"""Update contact info for the cart"""
if self.is_cart_expired():
return False, "Votre session d'inscription a expiré, veuillez réessayer."
if mobile_number is not None:
self.session['registration_mobile_number'] = mobile_number
self.reset_cart_expiry()
self.session.modified = True
return True, "Informations de contact mises à jour."
def checkout(self):
"""Convert cart to an actual tournament registration"""
if self.is_cart_expired():
return False, "Votre session d'inscription a expiré, veuillez réessayer."
# Get cart data
cart_data = self.get_cart_data()
tournament_id = cart_data.get('tournament_id')
players = cart_data.get('players')
mobile_number = cart_data.get('mobile_number')
# Validate cart data
if not tournament_id:
return False, "Aucun tournoi sélectionné."
if not players:
return False, "Aucun joueur dans l'inscription."
# Get tournament
try:
tournament = Tournament.objects.get(id=tournament_id)
except Tournament.DoesNotExist:
return False, "Tournoi introuvable."
# Check minimum players
if len(players) < tournament.minimum_player_per_team:
return False, f"Vous avez besoin d'au moins {tournament.minimum_player_per_team} joueurs pour vous inscrire."
# Create team registration
team_registration = TeamRegistration.objects.create(
tournament=tournament,
registration_date=timezone.now(),
walk_out=False
)
# Identify captain from user's license
stripped_license = None
if self.request.user.is_authenticated and self.request.user.licence_id:
validator = LicenseValidator(self.request.user.licence_id)
stripped_license = validator.stripped_license
# Create player registrations
for player_data in players:
# Determine if this player is the captain
is_captain = False
player_licence_id = player_data.get('licence_id')
if player_licence_id and stripped_license:
if stripped_license.lower() in player_licence_id.lower():
is_captain = True
# Compute rank and sex using the original logic
sex, rank, computed_rank = self._compute_rank_and_sex(
tournament,
player_data
)
# Determine data source
data_source = None
if player_data.get('found_in_french_federation', False) == True:
data_source = PlayerDataSource.FRENCH_FEDERATION # Now using the enum value
# Create player registration with all the original fields
PlayerRegistration.objects.create(
team_registration=team_registration,
captain=is_captain,
source=data_source,
registered_online=True,
first_name=player_data.get('first_name'),
last_name=player_data.get('last_name'),
points=player_data.get('points'),
assimilation=player_data.get('assimilation'),
tournament_played=player_data.get('tournament_count'),
ligue_name=player_data.get('ligue_name'),
club_name=player_data.get('club_name'),
birthdate=player_data.get('birth_year'),
sex=sex,
rank=rank,
computed_rank=computed_rank,
licence_id=player_data.get('licence_id'),
email=player_data.get('email'),
phone_number=player_data.get('mobile_number', mobile_number),
registration_status='CONFIRMED' if self.session.get('waiting_list_position', 0) < 0 else 'WAITING'
)
# Calculate and set team weight
team_registration.set_weight()
team_registration.save()
# Update user phone if provided
if self.request.user.is_authenticated and mobile_number:
self.request.user.phone = mobile_number
self.request.user.save(update_fields=['phone'])
# Clear the cart
self.clear_cart()
tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
tournament.save()
return True, team_registration
def clear_cart(self):
"""Clear the registration cart"""
keys_to_clear = [
'registration_cart_id',
'team_registration_id',
'registration_tournament_id',
'registration_cart_players',
'registration_cart_expiry',
'registration_mobile_number'
]
for key in keys_to_clear:
if key in self.session:
del self.session[key]
self.session.modified = True
def _is_player_already_registered(self, stripped_license, tournament):
"""Check if a player is already registered in the tournament"""
return PlayerRegistration.objects.filter(
team_registration__tournament=tournament,
licence_id__icontains=stripped_license,
team_registration__walk_out=False
).exists()
def add_authenticated_user(self):
"""
Adds the authenticated user to the cart if they have a valid license.
Returns True if added, False otherwise.
"""
if not self.request.user.is_authenticated or not self.request.user.licence_id:
return False
# Create player data for the authenticated user
player_data = {
'first_name': self.request.user.first_name,
'last_name': self.request.user.last_name,
'licence_id': self.request.user.licence_id,
'email': self.request.user.email,
'phone': self.request.user.phone
}
# Add the user to the cart
success, _ = self.add_player(player_data)
return success
def _compute_rank_and_sex(self, tournament, player_data):
"""
Compute the player's sex, rank, and computed rank based on tournament category.
This reimplements the original logic from TournamentRegistrationRepository.
"""
is_woman = player_data.get('is_woman', False)
rank = player_data.get('rank', None)
if rank is None:
computed_rank = 100000
else:
computed_rank = rank
# Use the integer enum values
sex = PlayerSexType.FEMALE if is_woman else PlayerSexType.MALE
# Apply assimilation for women playing in men's tournaments
if is_woman and tournament.federal_category == FederalCategory.MEN:
assimilation_addition = FederalCategory.female_in_male_assimilation_addition(rank)
computed_rank = computed_rank + assimilation_addition
print(f"_compute_rank_and_sex: {player_data.get('last_name')}, {sex}, {rank}, {computed_rank}")
return sex, rank, computed_rank