diff --git a/sync/admin.py b/sync/admin.py index cb97d80..48b3ee0 100644 --- a/sync/admin.py +++ b/sync/admin.py @@ -6,13 +6,14 @@ from .models import BaseModel, ModelLog, DataAccess class SyncedObjectAdmin(admin.ModelAdmin): exclude = ('data_access_ids',) + raw_id_fields = ['related_user'] def save_model(self, request, obj, form, change): if isinstance(obj, BaseModel): obj.last_updated_by = request.user obj.last_update = timezone.now() - if obj.related_user is None: - obj.related_user = request.user + # if obj.related_user is None: + # obj.related_user = request.user super().save_model(request, obj, form, change) def delete_model(self, request, obj): diff --git a/tournaments/admin.py b/tournaments/admin.py index 3b67f56..708fd9d 100644 --- a/tournaments/admin.py +++ b/tournaments/admin.py @@ -90,7 +90,7 @@ class TournamentAdmin(SyncedObjectAdmin): list_display = ['display_name', 'event', 'is_private', 'start_date', 'payment', 'creator', 'is_canceled'] list_filter = [StartDateRangeFilter, 'is_deleted', 'event__creator'] ordering = ['-start_date'] - search_fields = ['id'] + search_fields = ['id', 'display_name', 'federal_level_category'] def dashboard_view(self, request): """Tournament dashboard view with comprehensive statistics""" diff --git a/tournaments/forms.py b/tournaments/forms.py index 2ca881d..3c7c7f8 100644 --- a/tournaments/forms.py +++ b/tournaments/forms.py @@ -77,10 +77,10 @@ class SimpleCustomUserCreationForm(UserCreationForm): def clean_phone(self): phone = self.cleaned_data.get('phone') if phone: - # Remove all spaces - phone = phone.replace(' ', '') - # Basic regex for phone numbers, matching common formats - if not re.match(r"^\+?\d{10,15}$", phone): + # Remove all spaces, dots, dashes, and parentheses + phone = re.sub(r'[\s\.\-\(\)]', '', phone) + # Basic regex for phone numbers, allowing 6-15 digits for international numbers + if not re.match(r"^\+?\d{6,15}$", phone): raise forms.ValidationError("Entrer un numéro de téléphone valide.") return phone @@ -181,10 +181,9 @@ class TournamentRegistrationForm(forms.Form): def clean_mobile_number(self): mobile_number = self.cleaned_data.get('mobile_number') if mobile_number: - # Basic regex for mobile numbers, matching common formats - # Remove spaces from the number first - mobile_number = mobile_number.replace(' ', '') - if not re.match(r"^\+?\d{10,15}$", mobile_number): + # Remove spaces, dots, dashes, and parentheses from the number first + mobile_number = re.sub(r'[\s\.\-\(\)]', '', mobile_number) + if not re.match(r"^\+?\d{6,15}$", mobile_number): raise forms.ValidationError("Entrer un numéro de téléphone valide.") return mobile_number @@ -292,10 +291,10 @@ class ProfileUpdateForm(forms.ModelForm): def clean_phone(self): phone = self.cleaned_data.get('phone') if phone: - # Remove all spaces - phone = phone.replace(' ', '') - # Basic regex for phone numbers, matching common formats - if not re.match(r"^\+?\d{10,15}$", phone): + # Remove all spaces, dots, dashes, and parentheses + phone = re.sub(r'[\s\.\-\(\)]', '', phone) + # Basic regex for phone numbers, allowing 6-15 digits for international numbers + if not re.match(r"^\+?\d{6,15}$", phone): raise forms.ValidationError("Entrer un numéro de téléphone valide.") return phone diff --git a/tournaments/migrations/0129_tournament_currency_code.py b/tournaments/migrations/0129_tournament_currency_code.py new file mode 100644 index 0000000..c1cdd58 --- /dev/null +++ b/tournaments/migrations/0129_tournament_currency_code.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2025-06-23 16:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0128_club_data_access_ids_court_data_access_ids_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='tournament', + name='currency_code', + field=models.CharField(blank=True, default='EUR', max_length=3, null=True), + ), + ] diff --git a/tournaments/models/match.py b/tournaments/models/match.py index af8660c..33cf642 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -339,7 +339,7 @@ class Match(TournamentSubModel): local_start = self.start_date.astimezone(timezone) time_format ='l H:i' if self.get_tournament().day_duration >= 7: - time_format = 'l d M à H:i' + time_format = 'D. d F à H:i' if self.confirmed: return formats.date_format(local_start, format=time_format) else: @@ -464,7 +464,10 @@ class Match(TournamentSubModel): bracket_name = "Match #" + f"{self.index_in_round() + 1}" ended = self.end_date is not None - live_format = "Format " + FederalMatchCategory(self.format).format_label_short + live_format = "Format " + if self.get_tournament().day_duration >= 7: + live_format = "" + live_format = live_format + FederalMatchCategory(self.format).format_label_short tournament_title = None if event_mode is True: diff --git a/tournaments/models/team_registration.py b/tournaments/models/team_registration.py index 5e19969..deb7ed3 100644 --- a/tournaments/models/team_registration.py +++ b/tournaments/models/team_registration.py @@ -6,6 +6,7 @@ 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 @@ -412,6 +413,13 @@ class TeamRegistration(TournamentSubModel): 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. diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 86c380d..bfdaa0c 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -12,6 +12,7 @@ from django.utils.formats import date_format from ..utils.licence_validator import LicenseValidator from django.apps import apps from django.conf import settings +from tournaments.services.currency_service import CurrencyService class TeamSortingType(models.IntegerChoices): RANK = 1, 'Rank' @@ -94,6 +95,7 @@ class Tournament(BaseModel): show_teams_in_prog = models.BooleanField(default=False) club_member_fee_deduction = models.FloatField(null=True, blank=True) unregister_delta_in_hours = models.IntegerField(default=24) + currency_code = models.CharField(null=True, blank=True, max_length=3, default='EUR') def delete_dependencies(self): for team_registration in self.team_registrations.all(): @@ -596,7 +598,7 @@ class Tournament(BaseModel): # Format the date timezone = first_match.get_tournament().timezone() local_start = first_match.start_date.astimezone(timezone) - time_format = 'l d M' + time_format = 'l d F' formatted_schedule = f" - {formats.date_format(local_start, format=time_format)}" return MatchGroup(name, live_matches, formatted_schedule, round_id, round_index) @@ -911,8 +913,6 @@ class Tournament(BaseModel): return False def display_prog(self): - if self.end_date is not None: - return True if self.publish_prog: return True if self.has_started(): @@ -1108,18 +1108,18 @@ class Tournament(BaseModel): return True def options_fee(self): - def format_currency(amount): - """Format currency amount, removing unnecessary decimals""" - return f"{amount:g}" if amount % 1 == 0 else f"{amount:.2f}" - options = [] + currency_service = CurrencyService() + # Entry fee if self.entry_fee is not None and self.entry_fee > 0: - options.append(f"Frais d'inscription: {format_currency(self.entry_fee)} € par joueur") + formatted_fee = currency_service.format_amount(self.entry_fee, self.currency_code) + options.append(f"Frais d'inscription: {formatted_fee} par joueur") # Club member fee reduction if self.club_member_fee_deduction and self.club_member_fee_deduction > 0: - options.append(f"Réduction de {format_currency(self.club_member_fee_deduction)} € pour les membres du club") + formatted_deduction = currency_service.format_amount(self.club_member_fee_deduction, self.currency_code) + options.append(f"Réduction de {formatted_deduction} pour les membres du club") return options @@ -1952,6 +1952,11 @@ class Tournament(BaseModel): if event_mode is True and self.event.tournaments.count() == 1: event_mode = False + if self.event.tournaments.count() == 1: + show_teams_in_prog = self.show_teams_in_prog + else: + show_teams_in_prog = self.event.tournaments.filter(show_teams_in_prog=True).first() is not None + # Get all matches from rounds and group stages - use a set to avoid duplicates all_matches = set() @@ -1959,6 +1964,15 @@ class Tournament(BaseModel): if event_mode is True: tournaments = self.event.tournaments.all() + # Check if all tournaments have started - if so, always show teams + all_started = True + for t in tournaments: + if not t.has_started(): + all_started = False + break + if all_started: + show_teams_in_prog = True + for tournament in tournaments: # Get matches only from top-level rounds to avoid duplicates for round in tournament.rounds.filter(parent=None).all(): @@ -2020,7 +2034,7 @@ class Tournament(BaseModel): matches_by_hour[hour_key].append(match) - hide_teams = self.show_teams_in_prog == False + hide_teams = show_teams_in_prog == False # Create match groups for each hour for hour, matches in sorted(matches_by_hour.items()): # Sort matches by court if available @@ -2065,7 +2079,7 @@ class Tournament(BaseModel): matches_by_hour[hour_key].append(match) - hide_teams = self.show_teams_in_prog == False + hide_teams = show_teams_in_prog == False # Create match groups for each hour for hour, matches in sorted(matches_by_hour.items()): # Sort matches by court if available diff --git a/tournaments/services/currency_service.py b/tournaments/services/currency_service.py new file mode 100644 index 0000000..69952c5 --- /dev/null +++ b/tournaments/services/currency_service.py @@ -0,0 +1,189 @@ +""" +Currency service for handling multi-currency support in tournaments. +""" + +class CurrencyService: + """Service for handling currency formatting, symbols, and conversions.""" + + # Currency symbols mapping + CURRENCY_SYMBOLS = { + 'EUR': '€', + 'USD': '$', + 'GBP': '£', + 'CHF': 'CHF', + 'CAD': 'C$', + 'AUD': 'A$', + 'JPY': '¥', + 'CNY': '¥', + 'SEK': 'kr', + 'NOK': 'kr', + 'DKK': 'kr', + 'PLN': 'zł', + 'CZK': 'Kč', + 'HUF': 'Ft', + 'RON': 'lei', + 'BGN': 'лв', + 'HRK': 'kn', + 'RSD': 'RSD', + 'BAM': 'KM', + 'MKD': 'ден', + 'ALL': 'L', + 'MDL': 'L', + 'UAH': '₴', + 'RUB': '₽', + 'BYN': 'Br', + 'LTL': 'Lt', + 'LVL': 'Ls', + 'EEK': 'kr', + 'ISK': 'kr', + 'TRY': '₺', + 'ILS': '₪', + 'AED': 'د.إ', + 'SAR': 'ر.س', + 'QAR': 'ر.ق', + 'KWD': 'د.ك', + 'BHD': '.د.ب', + 'OMR': 'ر.ع.', + 'JOD': 'د.أ', + 'LBP': 'ل.ل', + 'EGP': 'ج.م', + 'MAD': 'د.م.', + 'TND': 'د.ت', + 'DZD': 'د.ج', + 'LYD': 'ل.د', + 'ZAR': 'R', + 'NGN': '₦', + 'GHS': '₵', + 'KES': 'KSh', + 'UGX': 'USh', + 'TZS': 'TSh', + 'ETB': 'Br', + 'XOF': 'CFA', + 'XAF': 'FCFA', + 'MZN': 'MT', + 'BWP': 'P', + 'ZMW': 'ZK', + 'INR': '₹', + 'PKR': '₨', + 'BDT': '৳', + 'LKR': '₨', + 'MVR': 'Rf', + 'NPR': '₨', + 'BTN': 'Nu.', + 'THB': '฿', + 'MYR': 'RM', + 'SGD': 'S$', + 'IDR': 'Rp', + 'PHP': '₱', + 'VND': '₫', + 'KHR': '៛', + 'LAK': '₭', + 'MMK': 'K', + 'KRW': '₩', + 'TWD': 'NT$', + 'HKD': 'HK$', + 'MOP': 'MOP$', + 'BND': 'B$', + 'FJD': 'FJ$', + 'PGK': 'K', + 'WST': 'WS$', + 'TOP': 'T$', + 'VUV': 'VT', + 'SBD': 'SI$', + 'MXN': '$', + 'GTQ': 'Q', + 'BZD': 'BZ$', + 'SVC': '$', + 'HNL': 'L', + 'NIO': 'C$', + 'CRC': '₡', + 'PAB': 'B/.', + 'COP': '$', + 'VES': 'Bs.S', + 'GYD': 'GY$', + 'SRD': 'Sr$', + 'UYU': '$U', + 'PYG': '₲', + 'BOB': 'Bs.', + 'BRL': 'R$', + 'PEN': 'S/', + 'ECU': '$', + 'CLP': '$', + 'ARS': '$', + 'FKP': '£', + 'XPF': '₣' + } + + # Zero-decimal currencies (amounts are in their base unit, not subdivided) + ZERO_DECIMAL_CURRENCIES = { + 'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', + 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF' + } + + @classmethod + def get_currency_symbol(cls, currency_code): + """Get the currency symbol for a given currency code.""" + if not currency_code: + return '€' # Default to Euro symbol + return cls.CURRENCY_SYMBOLS.get(currency_code.upper(), currency_code.upper()) + + @classmethod + def format_amount(cls, amount, currency_code, show_symbol=True): + """Format amount with proper currency symbol and decimal places.""" + if amount is None: + return "0" + + currency_code = currency_code.upper() if currency_code else 'EUR' + + # Handle zero-decimal currencies + if currency_code in cls.ZERO_DECIMAL_CURRENCIES: + formatted_amount = f"{int(amount)}" + else: + # Format with 2 decimal places, removing unnecessary zeros + if amount == int(amount): + formatted_amount = f"{int(amount)}" + else: + formatted_amount = f"{amount:.2f}" + + if show_symbol: + symbol = cls.get_currency_symbol(currency_code) + return f"{formatted_amount} {symbol}" + + return formatted_amount + + @classmethod + def convert_to_stripe_amount(cls, amount, currency_code): + """Convert amount to Stripe's expected format (minor units).""" + if not amount: + return 0 + + currency_code = currency_code.upper() if currency_code else 'EUR' + + # Zero-decimal currencies don't need conversion + if currency_code in cls.ZERO_DECIMAL_CURRENCIES: + return int(amount) + + # Two-decimal currencies need to be multiplied by 100 + return int(amount * 100) + + @classmethod + def convert_from_stripe_amount(cls, stripe_amount, currency_code): + """Convert Stripe amount (minor units) back to standard decimal format.""" + if not stripe_amount: + return 0.0 + + currency_code = currency_code.upper() if currency_code else 'EUR' + + # Zero-decimal currencies are already in the correct format + if currency_code in cls.ZERO_DECIMAL_CURRENCIES: + return float(stripe_amount) + + # Two-decimal currencies need to be divided by 100 + return float(stripe_amount) / 100 + + @classmethod + def validate_currency_code(cls, currency_code): + """Validate if a currency code is supported.""" + if not currency_code: + return False + return currency_code.upper() in cls.CURRENCY_SYMBOLS diff --git a/tournaments/services/email_service.py b/tournaments/services/email_service.py index 9d732a8..70fbb75 100644 --- a/tournaments/services/email_service.py +++ b/tournaments/services/email_service.py @@ -3,6 +3,7 @@ from enum import Enum from ..models.enums import RegistrationStatus, FederalLevelCategory from ..models.tournament import TeamSortingType from django.utils import timezone +from tournaments.services.currency_service import CurrencyService class TeamEmailType(Enum): REGISTERED = "registered" @@ -666,14 +667,16 @@ class TournamentEmailService: if payment_status == 'PAID': return "\n\n✅ Le paiement de votre inscription a bien été reçu." - # If the team is on the waiting list, don't mention payment if team_registration.is_in_waiting_list() >= 0: return "" + currency_service = CurrencyService() + # For unpaid teams, add payment instructions + formatted_fee = currency_service.format_amount(tournament.entry_fee, tournament.currency_code) payment_info = [ "\n\n⚠️ Paiement des frais d'inscription requis", - f"Les frais d'inscription de {tournament.entry_fee:.2f}€ par joueur doivent être payés pour confirmer votre participation.", + f"Les frais d'inscription de {formatted_fee} par joueur doivent être payés pour confirmer votre participation.", "Vous pouvez effectuer le paiement en vous connectant à votre compte Padel Club.", f"Lien pour payer: https://padelclub.app/tournament/{tournament.id}/info" ] @@ -694,12 +697,17 @@ class TournamentEmailService: # Calculate payment amount payment_amount = None - if payment and 'amount' in payment: - # Convert cents to euros - payment_amount = payment['amount'] / 100 + currency_service = CurrencyService() - if payment_amount is None: - payment_amount = team_registration.get_remaining_fee() + if payment and 'amount' in payment: + # Get currency from payment metadata or tournament + payment_currency = payment.get('currency', tournament.currency_code or 'EUR') + # Convert cents/minor units to standard format + payment_amount = currency_service.convert_from_stripe_amount(payment['amount'], payment_currency) + formatted_amount = currency_service.format_amount(payment_amount, payment_currency) + else: + # Fallback to tournament fee + formatted_amount = currency_service.format_amount(team_registration.get_team_registration_fee(), tournament.currency_code) federal_level_category = FederalLevelCategory(tournament.federal_level_category) tournament_word = federal_level_category.localized_word() @@ -725,7 +733,7 @@ class TournamentEmailService: # Add payment details body_parts.append( - f"\n\nMontant payé : {payment_amount:.2f}€" + f"\n\nMontant payé : {formatted_amount}" ) payment_date = timezone.now().strftime("%d/%m/%Y") @@ -768,6 +776,19 @@ class TournamentEmailService: """ player_registrations = team_registration.players_sorted_by_rank refund_amount = None + + currency_service = CurrencyService() + + if refund_details and 'amount' in refund_details: + # Get currency from refund details or tournament + refund_currency = refund_details.get('currency', tournament.currency_code or 'EUR') + # Convert cents/minor units to standard format + refund_amount = currency_service.convert_from_stripe_amount(refund_details['amount'], refund_currency) + formatted_amount = currency_service.format_amount(refund_amount, refund_currency) + else: + # Fallback to tournament fee + formatted_amount = currency_service.format_amount(tournament.entry_fee, tournament.currency_code) + if refund_details and 'amount' in refund_details: # Convert cents to euros refund_amount = refund_details['amount'] / 100 @@ -803,7 +824,7 @@ class TournamentEmailService: # Add refund details body_parts.append( - f"\n\nMontant remboursé : {refund_amount:.2f}€" + f"\n\nMontant remboursé : {formatted_amount}" ) refund_date = timezone.now().strftime("%d/%m/%Y") diff --git a/tournaments/services/payment_service.py b/tournaments/services/payment_service.py index 2b5d51d..064d732 100644 --- a/tournaments/services/payment_service.py +++ b/tournaments/services/payment_service.py @@ -11,6 +11,7 @@ from ..models.player_registration import PlayerPaymentType from .email_service import TournamentEmailService from .tournament_registration import RegistrationCartManager from ..utils.extensions import is_not_sqlite_backend +from tournaments.services.currency_service import CurrencyService class PaymentService: """ @@ -28,10 +29,20 @@ class PaymentService: stripe.api_key = self.stripe_api_key tournament = get_object_or_404(Tournament, id=tournament_id) + currency_service = CurrencyService() + # Check if payments are enabled for this tournament if not tournament.should_request_payment(): raise Exception("Les paiements ne sont pas activés pour ce tournoi.") + # Get currency code and validate it + currency_code = tournament.currency_code or 'EUR' + if not currency_service.validate_currency_code(currency_code): + raise Exception(f"Devise non supportée: {currency_code}") + + # Convert amount to Stripe format + stripe_amount = currency_service.convert_to_stripe_amount(team_fee, currency_code) + # Get user email if authenticated customer_email = self.request.user.email if self.request.user.is_authenticated else None @@ -52,6 +63,7 @@ class PaymentService: 'user_id': str(self.request.user.id) if self.request.user.is_authenticated else None, 'payment_source': 'tournament', # Identify payment source 'source_page': 'tournament_info' if team_registration_id else 'register_tournament', + 'currency_code': currency_code, # Store currency for later reference } if tournament.is_corporate_tournament: @@ -98,12 +110,12 @@ class PaymentService: 'payment_method_types': ['card'], 'line_items': [{ 'price_data': { - 'currency': 'eur', + 'currency': currency_code.lower(), # Use tournament currency 'product_data': { 'name': f'{tournament.broadcast_display_name()} du {tournament.formatted_start_date()}', 'description': f'Lieu {tournament.event.club.name}', }, - 'unit_amount': int(team_fee * 100), # Amount in cents + 'unit_amount': stripe_amount, # Amount in proper currency format }, 'quantity': 1, }], @@ -122,18 +134,20 @@ class PaymentService: # Calculate commission commission_rate = tournament.event.creator.effective_commission_rate() - platform_amount = int((team_fee * commission_rate) * 100) # Commission in cents + platform_amount = currency_service.convert_to_stripe_amount( + team_fee * commission_rate, currency_code + ) checkout_session_params = { 'payment_method_types': ['card'], 'line_items': [{ 'price_data': { - 'currency': 'eur', + 'currency': currency_code.lower(), # Use tournament currency 'product_data': { 'name': f'{tournament.broadcast_display_name()} du {tournament.formatted_start_date()}', 'description': f'Lieu {tournament.event.club.name}', }, - 'unit_amount': int(team_fee * 100), # Amount in cents + 'unit_amount': stripe_amount, # Amount in proper currency format }, 'quantity': 1, }], @@ -151,13 +165,6 @@ class PaymentService: 'metadata': metadata } - # # Add cart or team data to metadata based on payment context - # if cart_data: - # checkout_session_params['metadata']['registration_cart_id'] = str(cart_data['cart_id']) # Convert to string - # elif team_registration_id: - # checkout_session_params['metadata']['team_registration_id'] = str(team_registration_id) # Convert to string - # self.request.session['team_registration_id'] = str(team_registration_id) # Convert to string - # Add customer_email if available if customer_email: checkout_session_params['customer_email'] = customer_email diff --git a/tournaments/services/tournament_registration.py b/tournaments/services/tournament_registration.py index 990b335..21fe47b 100644 --- a/tournaments/services/tournament_registration.py +++ b/tournaments/services/tournament_registration.py @@ -9,6 +9,7 @@ from ..models.player_enums import PlayerSexType, PlayerDataSource from django.contrib.auth import get_user_model from django.conf import settings from django.utils.dateparse import parse_datetime +from .currency_service import CurrencyService class RegistrationCartManager: """ @@ -47,7 +48,9 @@ class RegistrationCartManager: expiry_str = self.session['registration_cart_expiry'] try: - expiry = datetime.datetime.fromisoformat(expiry_str) + expiry = parse_datetime(expiry_str) + if expiry is None: + return True return timezone.now() > expiry except (ValueError, TypeError): return True @@ -124,6 +127,7 @@ class RegistrationCartManager: 'expiry': expiry_datetime, # Now a datetime object, not a string 'is_cart_expired': self.is_cart_expired(), 'team_fee_from_cart_players': self.team_fee_from_cart_players(), + 'team_fee_from_cart_players_formatted': self.team_fee_from_cart_players_formatted(), 'mobile_number': self.session.get('registration_mobile_number', user_phone) } @@ -154,6 +158,19 @@ class RegistrationCartManager: else: return 0 + def team_fee_from_cart_players_formatted(self): + """Get the team fee formatted with the tournament's currency.""" + team_fee = self.team_fee_from_cart_players() + tournament_id = self.session.get('registration_tournament_id') + + try: + tournament = Tournament.objects.get(id=tournament_id) + currency_code = tournament.currency_code if tournament and tournament.currency_code else 'EUR' + except Tournament.DoesNotExist: + currency_code = 'EUR' + + return CurrencyService.format_amount(team_fee, currency_code) + def add_player(self, player_data): """Add a player to the registration cart""" print("add_player", player_data) diff --git a/tournaments/static/rankings/CLASSEMENT-PADEL-MESSIEURS-06-2025.csv b/tournaments/static/rankings/CLASSEMENT-PADEL-MESSIEURS-06-2025.csv index c617797..b931b65 100644 --- a/tournaments/static/rankings/CLASSEMENT-PADEL-MESSIEURS-06-2025.csv +++ b/tournaments/static/rankings/CLASSEMENT-PADEL-MESSIEURS-06-2025.csv @@ -1,4 +1,5 @@ max-players:97914 +unrank-male-value:90415 ;1;ZAPATA PIZZARO;Teodoro Victor;ESP;2098059;4145;Oui;6;ILE DE FRANCE;57 93 0505;AS PADEL AFICIONADOS;0;1;1995; ;1;HERNANDEZ QUESADA;luis;ESP;2744589;1200;Oui;1;NOUVELLE AQUITAINE;59 33 0723;BIG PADEL;0;1;1999; ;1;LIJO;Pablo ;ESP;3306546;900;Oui;1;PROVENCE ALPES COTE D'AZUR;62 13 0603;ALL IN PADEL ASSOCIATION;0;1;1991; diff --git a/tournaments/templates/admin/tournaments/dashboard.html b/tournaments/templates/admin/tournaments/dashboard.html index b6f6fb1..339224d 100644 --- a/tournaments/templates/admin/tournaments/dashboard.html +++ b/tournaments/templates/admin/tournaments/dashboard.html @@ -13,7 +13,35 @@ {% block content %}
-

🏆 Tournament Dashboard

+ +
+
+ + Tournaments + + + Teams + + + Players + + + Matches + + + Events + + + Clubs + +
+
@@ -377,36 +405,6 @@
- -
-

🚀 Quick Actions

-
- - View All Tournaments - - - Manage Teams - - - Manage Players - - - View Matches - - - Manage Events - - - Manage Clubs - -
-