From 4f4b293d527756d2ccc5f128464e4f17c5561750 Mon Sep 17 00:00:00 2001
From: Raz
Date: Mon, 19 May 2025 18:02:53 +0200
Subject: [PATCH] fix issue with payment and registration
---
...4_playerregistration_club_code_and_more.py | 33 +++++++++++++
...25_tournament_unregister_delta_in_hours.py | 18 ++++++++
tournaments/models/club.py | 2 +-
tournaments/models/player_registration.py | 14 ++++++
tournaments/models/team_registration.py | 13 ++----
tournaments/models/tournament.py | 46 ++++++++++++-------
tournaments/services/email_service.py | 4 +-
.../services/tournament_registration.py | 39 +++++++++++++++-
.../templates/register_tournament.html | 7 ++-
.../tournaments/tournament_info.html | 10 ++++
tournaments/utils/player_search.py | 2 +
tournaments/views.py | 2 +-
12 files changed, 157 insertions(+), 33 deletions(-)
create mode 100644 tournaments/migrations/0124_playerregistration_club_code_and_more.py
create mode 100644 tournaments/migrations/0125_tournament_unregister_delta_in_hours.py
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
+
+ {% for option in tournament.options_fee %}
+ - {{ option }}
+ {% endfor %}
+
+
+ {% 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
)