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

490 lines
21 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, RegistrationStatus
from ..models.player_enums import PlayerSexType, PlayerDataSource
from django.contrib.auth import get_user_model
from django.conf import settings
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
self.first_tournament = False
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
waiting_list_position = tournament.get_waiting_list_position()
# 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
tournament_federal_category = tournament.federal_category
if tournament_federal_category == FederalCategory.MIXED and len(players) == 1:
other_player_is_woman = players[0].get('is_woman', False)
if other_player_is_woman is False:
tournament_federal_category = FederalCategory.WOMEN
if licence_id:
# Get federation data
fed_data, found = get_player_name_from_csv(tournament_federal_category, licence_id)
if found is False and fed_data:
player_data.update({
'rank': fed_data['rank'],
'is_woman': fed_data['is_woman'],
})
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 not first_name or not last_name:
# License not required or not found, but name is needed
self.first_tournament = True
return False, "Le prénom et le nom sont obligatoires pour les joueurs dont la licence n'a pas été trouvée."
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."
else:
# License is required but not provided
return False, "Le numéro de licence est obligatoire."
# Create player registrations
sex, rank, computed_rank = self._compute_rank_and_sex(
tournament,
player_data
)
player_data['computed_rank'] = computed_rank
# Add player to cart
players.append(player_data)
self.session['registration_cart_players'] = players
self.reset_cart_expiry()
self.session.modified = True
if sex == PlayerSexType.FEMALE:
return True, "Joueuse ajoutée avec succès."
else:
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():
if settings.DEBUG:
return False, f"Le numéro de licence est invalide, la lettre ne correspond pas. {validator.get_computed_license_key(validator.stripped_license)}"
else:
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."
# Identify captain from user's license
# # 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'])
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
weight = sum(int(player_data.get('computed_rank', 0) or 0) for player_data in players)
# Create team registration
team_registration = TeamRegistration.objects.create(
tournament=tournament,
registration_date=timezone.now(),
walk_out=False,
weight=weight,
user=self.request.user
)
for player_data in players: # Compute rank and sex using the original logic
# 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
# 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
User = get_user_model()
matching_user = self.request.user
if player_licence_id and (stripped_license is None or is_captain is False):
try:
# Using icontains for case-insensitive match
matching_user = User.objects.get(licence_id__icontains=player_licence_id)
if matching_user is None:
matching_user = self.request.user
except User.DoesNotExist:
pass
# Create player registration with all the original fields
PlayerRegistration.objects.create(
team_registration=team_registration,
user=matching_user,
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=player_data.get('sex'),
rank=player_data.get('rank'),
computed_rank=player_data.get('computed_rank'),
licence_id=player_data.get('licence_id'),
email=matching_user.email if matching_user else player_data.get('email'),
phone_number=matching_user.phone if matching_user else player_data.get('mobile_number'),
registration_status=RegistrationStatus.CONFIRMED if self.session.get('waiting_list_position', 0) < 0 else RegistrationStatus.WAITING
)
# 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:
rank_int = None
computed_rank = 100000
else:
# Ensure rank is an integer for calculations
try:
rank_int = int(rank)
computed_rank = rank_int
except (ValueError, TypeError):
# If rank can't be converted to int, set a default
rank_int = None
computed_rank = 100000
# 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 and rank_int is not None:
assimilation_addition = FederalCategory.female_in_male_assimilation_addition(rank_int)
computed_rank = computed_rank + assimilation_addition
print(f"_compute_rank_and_sex: {player_data.get('last_name')}, {sex}, {rank}, {computed_rank}")
return sex, rank, str(computed_rank)