diff --git a/tournaments/migrations/0124_playerregistration_club_code_and_more.py b/tournaments/migrations/0124_playerregistration_club_code_and_more.py new file mode 100644 index 0000000..b8c5271 --- /dev/null +++ b/tournaments/migrations/0124_playerregistration_club_code_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.1 on 2025-05-19 09:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0123_teamregistration_unique_random_index'), + ] + + operations = [ + migrations.AddField( + model_name='playerregistration', + name='club_code', + field=models.CharField(blank=True, max_length=20, null=True), + ), + migrations.AddField( + model_name='playerregistration', + name='club_member', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='tournament', + name='club_member_fee_deduction', + field=models.FloatField(blank=True, null=True), + ), + migrations.AlterField( + model_name='club', + name='code', + field=models.CharField(blank=True, max_length=20, null=True), + ), + ] diff --git a/tournaments/migrations/0125_tournament_unregister_delta_in_hours.py b/tournaments/migrations/0125_tournament_unregister_delta_in_hours.py new file mode 100644 index 0000000..00c801e --- /dev/null +++ b/tournaments/migrations/0125_tournament_unregister_delta_in_hours.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2025-05-19 13:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0124_playerregistration_club_code_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='tournament', + name='unregister_delta_in_hours', + field=models.IntegerField(default=24), + ), + ] diff --git a/tournaments/models/club.py b/tournaments/models/club.py index 75cc2a5..df51b2b 100644 --- a/tournaments/models/club.py +++ b/tournaments/models/club.py @@ -9,7 +9,7 @@ class Club(BaseModel): name = models.CharField(max_length=50) acronym = models.CharField(max_length=50) phone = models.CharField(max_length=15, null=True, blank=True) - code = models.CharField(max_length=10, null=True, blank=True) + code = models.CharField(max_length=20, null=True, blank=True) federal_club_data = models.JSONField(null=True, blank=True) address = models.CharField(max_length=200, null=True, blank=True) diff --git a/tournaments/models/player_registration.py b/tournaments/models/player_registration.py index 0c9e1b6..082ea53 100644 --- a/tournaments/models/player_registration.py +++ b/tournaments/models/player_registration.py @@ -25,6 +25,8 @@ class PlayerRegistration(SideStoreModel): club_name = models.CharField(max_length=200, null=True, blank=True) ligue_name = models.CharField(max_length=200, null=True, blank=True) assimilation = models.CharField(max_length=50, null=True, blank=True) + club_code = models.CharField(max_length=20, null=True, blank=True) + club_member = models.BooleanField(default=False) #beachpadel phone_number = models.CharField(max_length=50, null=True, blank=True) @@ -175,3 +177,15 @@ class PlayerRegistration(SideStoreModel): def has_paid(self): return self.payment_type is not None + + def get_remaining_fee(self): + if self.has_paid(): + return 0 + if self.club_member and self.team_registration.tournament.club_member_fee_deduction is not None and self.team_registration.tournament.entry_fee is not None: + return self.team_registration.tournament.entry_fee - self.team_registration.tournament.club_member_fee_deduction + elif self.team_registration.tournament.entry_fee is not None: + return self.team_registration.tournament.entry_fee + elif self.team_registration.tournament.club_member_fee_deduction is not None: + return self.team_registration.tournament.club_member_fee_deduction + else: + return 0 diff --git a/tournaments/models/team_registration.py b/tournaments/models/team_registration.py index 6fabcde..4be79ee 100644 --- a/tournaments/models/team_registration.py +++ b/tournaments/models/team_registration.py @@ -402,14 +402,11 @@ class TeamRegistration(SideStoreModel): return status == 'PAID' def get_remaining_fee(self): - """Get the remaining fee for this team""" - status = self.get_payment_status() - if status == 'PAID': - return 0 - elif status == 'UNPAID': - return self.tournament.team_fee() - elif status == 'MIXED': - return self.tournament.player_fee() + # 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 is_confirmation_expired(self): """ diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 27496e4..01d5565 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -92,6 +92,8 @@ class Tournament(BaseModel): animation_type = models.IntegerField(default=AnimationType.TOURNAMENT, choices=AnimationType.choices) publish_prog = models.BooleanField(default=False) 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) def delete_dependencies(self): for team_registration in self.team_registrations.all(): @@ -1093,6 +1095,18 @@ class Tournament(BaseModel): return False return True + def options_fee(self): + options = [] + # Entry fee + if self.entry_fee is not None and self.entry_fee > 0: + options.append(f"Frais d'inscription: {self.entry_fee} € par équipe") + + # Club member fee reduction + if self.club_member_fee_deduction and self.club_member_fee_deduction > 0: + options.append(f"Réduction de {self.club_member_fee_deduction} € pour les membres du club") + + return options + def options_online_registration(self): options = [] timezone = self.timezone() @@ -1132,11 +1146,11 @@ class Tournament(BaseModel): if self.enable_online_payment_refund and self.refund_date_limit: date = formats.date_format(self.refund_date_limit.astimezone(timezone), format='j F Y H:i') - options.append(f"Remboursement possible jusqu'au {date}") + options.append(f"Remboursement en ligne possible jusqu'au {date}") elif self.enable_online_payment_refund: - options.append("Remboursement possible") + options.append("Remboursement en ligne possible") else: - options.append("Remboursement impossible") + options.append("Remboursement en ligne impossible") # Joueurs par équipe min_players = self.minimum_player_per_team @@ -1236,7 +1250,7 @@ class Tournament(BaseModel): if self.supposedly_in_progress(): return False - if self.will_start_soon(12): + if self.will_start_soon(self.unregister_delta_in_hours): return False if self.closed_registration_date is not None: @@ -1844,19 +1858,6 @@ class Tournament(BaseModel): else: return False - def player_fee(self): - if self.entry_fee is not None and self.entry_fee > 0 and self.enable_online_payment: - return self.entry_fee - else: - return 0 - - def team_fee(self): - entry_fee = self.entry_fee - if entry_fee is not None and entry_fee > 0 and self.enable_online_payment: - return self.entry_fee * self.minimum_player_per_team - else: - return 0 - def is_free(self): if self.entry_fee is not None and self.entry_fee == 0: return True @@ -2060,6 +2061,17 @@ class Tournament(BaseModel): def has_sponsors(self): return self.event.images.exists() + def is_cart_player_from_club(self, player_data): + player_club_code = player_data.get('club_code', None) + if player_club_code is None and len(player_club_code) > 0: + return False + club_code = self.event.club.code + if club_code is None and len(club_code) > 0: + return False + player_club_code = player_club_code.replace(" ", "") + club_code = club_code.replace(" ", "") + return player_club_code.lower() == club_code.lower() + class MatchGroup: def __init__(self, name, matches, formatted_schedule, round_id=None, round_index=None): self.name = name diff --git a/tournaments/services/email_service.py b/tournaments/services/email_service.py index c7a572b..9d732a8 100644 --- a/tournaments/services/email_service.py +++ b/tournaments/services/email_service.py @@ -699,7 +699,7 @@ class TournamentEmailService: payment_amount = payment['amount'] / 100 if payment_amount is None: - payment_amount = tournament.team_fee() + payment_amount = team_registration.get_remaining_fee() federal_level_category = FederalLevelCategory(tournament.federal_level_category) tournament_word = federal_level_category.localized_word() @@ -774,7 +774,7 @@ class TournamentEmailService: if refund_amount is None: - refund_amount = tournament.team_fee() + refund_amount = team_registration.get_remaining_fee() federal_level_category = FederalLevelCategory(tournament.federal_level_category) tournament_word = federal_level_category.localized_word() diff --git a/tournaments/services/tournament_registration.py b/tournaments/services/tournament_registration.py index d36c0c7..e8c198f 100644 --- a/tournaments/services/tournament_registration.py +++ b/tournaments/services/tournament_registration.py @@ -8,6 +8,7 @@ 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 +from django.utils.dateparse import parse_datetime class RegistrationCartManager: """ @@ -110,7 +111,6 @@ class RegistrationCartManager: 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 @@ -123,6 +123,7 @@ class RegistrationCartManager: 'players': self.session.get('registration_cart_players', []), '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(), 'mobile_number': self.session.get('registration_mobile_number', user_phone) } @@ -132,6 +133,27 @@ class RegistrationCartManager: return cart_data + def team_fee_from_cart_players(self): + # Get tournament + tournament_id = self.session.get('registration_tournament_id') + + try: + tournament = Tournament.objects.get(id=tournament_id) + except Tournament.DoesNotExist: + return 0 + + players = self.session.get('registration_cart_players', []), + entry_fee = tournament.entry_fee + if entry_fee is not None and entry_fee > 0 and tournament.enable_online_payment: + fee = entry_fee * tournament.minimum_player_per_team + players = self.session.get('registration_cart_players', []) + club_members = sum(1 for player in players if player.get('club_member', False)) + if tournament.club_member_fee_deduction is not None: + return fee - club_members * tournament.club_member_fee_deduction + return fee + else: + return 0 + def add_player(self, player_data): """Add a player to the registration cart""" if self.is_cart_expired(): @@ -198,8 +220,10 @@ class RegistrationCartManager: 'tournament_count': fed_data.get('tournament_count'), 'ligue_name': fed_data.get('ligue_name'), 'club_name': fed_data.get('club_name'), + 'club_code': fed_data.get('club_code'), 'birth_year': fed_data.get('birth_year'), 'found_in_french_federation': True, + 'club_member': tournament.is_cart_player_from_club(fed_data) }) elif not first_name or not last_name: # License not required or not found, but name is needed @@ -379,6 +403,15 @@ class RegistrationCartManager: except User.DoesNotExist: pass + is_woman = player_data.get('is_woman') + if is_woman is not None: + if is_woman: + sex = 0 + else: + sex = 1 + else: + sex = None + # Create player registration with all the original fields PlayerRegistration.objects.create( team_registration=team_registration, @@ -393,8 +426,10 @@ class RegistrationCartManager: tournament_played=player_data.get('tournament_count'), ligue_name=player_data.get('ligue_name'), club_name=player_data.get('club_name'), + club_code=player_data.get('club_code'), + club_member=tournament.is_cart_player_from_club(player_data), birthdate=player_data.get('birth_year'), - sex=player_data.get('sex'), + sex= sex, rank=player_data.get('rank'), computed_rank=player_data.get('computed_rank'), licence_id=player_data.get('licence_id'), diff --git a/tournaments/templates/register_tournament.html b/tournaments/templates/register_tournament.html index a2ff531..3f1a1a7 100644 --- a/tournaments/templates/register_tournament.html +++ b/tournaments/templates/register_tournament.html @@ -102,7 +102,10 @@ {{ player.first_name }} {{ player.last_name }}{% if player.licence_id %} ({{ player.licence_id }}){% endif %}
- {{ player.club_name }} + {{ player.club_name }}{% if player.club_member %} | Membre du club{% endif %}{% if player.club_member and tournament.club_member_fee_deduction %} | Tarif réduit{% endif %} +
+
+ {{ player.email }}
Classement à ce jour : {% if player.rank %}{{ player.rank }}{% if player.computed_rank and player.rank != player.computed_rank %} ({{ player.computed_rank }}){% endif %}{% else %}Non classé ({{ player.computed_rank }}){% endif %} @@ -194,7 +197,7 @@ Confirmer votre inscription en payant immédiatement :
{% endif %} {% if tournament.should_request_payment is False or tournament.online_payment_is_mandatory is False or cart_data.waiting_list_position >= 0 %} diff --git a/tournaments/templates/tournaments/tournament_info.html b/tournaments/templates/tournaments/tournament_info.html index afcff7c..48c85d3 100644 --- a/tournaments/templates/tournaments/tournament_info.html +++ b/tournaments/templates/tournaments/tournament_info.html @@ -197,6 +197,16 @@

{% endif %} + {% if tournament.options_fee %} +

+

Frais d'inscription
+ +

+ {% endif %} {% with status=tournament.get_online_registration_status %} {% if tournament.enable_online_registration %}

diff --git a/tournaments/utils/player_search.py b/tournaments/utils/player_search.py index f8aeedd..521bf67 100644 --- a/tournaments/utils/player_search.py +++ b/tournaments/utils/player_search.py @@ -70,6 +70,7 @@ def get_player_name_from_csv(category, licence_id, base_folder=None): if cleaned_licence_id: for row in rows: if len(row) >= 15: # Ensure row has enough columns + player_club_code = row[10].replace(" ", "") current_licence_id = row[5] if current_licence_id == str(cleaned_licence_id): data = { @@ -81,6 +82,7 @@ def get_player_name_from_csv(category, licence_id, base_folder=None): "tournament_count": row[8], "ligue_name": row[9], "club_name": row[11], + "club_code": player_club_code, "birth_year": row[14], "is_woman": is_woman, } diff --git a/tournaments/views.py b/tournaments/views.py index feccbe2..a408f80 100644 --- a/tournaments/views.py +++ b/tournaments/views.py @@ -1459,7 +1459,7 @@ def handle_payment_request(request, tournament, cart_manager, context, tournamen payment_service = PaymentService(request) checkout_session = payment_service.create_checkout_session( tournament_id=tournament_id, - team_fee=tournament.team_fee(), # Use the appropriate fee field + team_fee=cart_data.team_fee_from_cart_players(), # Use the appropriate fee field cart_data=cart_data )