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.
490 lines
21 KiB
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)
|
|
|