commit
52a0b5ec41
@ -0,0 +1,21 @@ |
|||||||
|
# Generated by Django 5.1 on 2025-05-01 05:59 |
||||||
|
|
||||||
|
import django.db.models.deletion |
||||||
|
from django.conf import settings |
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('shop', '0025_alter_product_cut'), |
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AlterField( |
||||||
|
model_name='order', |
||||||
|
name='user', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), |
||||||
|
), |
||||||
|
] |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
{% extends "admin/change_list.html" %} |
||||||
|
|
||||||
|
{% block object-tools %} |
||||||
|
<ul class="object-tools"> |
||||||
|
<li> |
||||||
|
<a href="?show_preparation=1" class="viewlink"> |
||||||
|
Orders to Prepare ({{ paid_orders_count }}) |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
{% if has_add_permission %} |
||||||
|
<li> |
||||||
|
<a href="{% url 'admin:shop_order_add' %}" class="addlink"> |
||||||
|
Add Order |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
{% endif %} |
||||||
|
</ul> |
||||||
|
{% endblock %} |
||||||
@ -0,0 +1,103 @@ |
|||||||
|
{% extends "admin/base_site.html" %} |
||||||
|
|
||||||
|
{% block content %} |
||||||
|
<div id="content-main"> |
||||||
|
<p>Total orders with status PAID: {{ total_orders }}</p> |
||||||
|
<p>Total items to prepare: {{ total_items }}</p> |
||||||
|
|
||||||
|
<button onclick="window.print()" style="margin-bottom: 20px">Print This Page</button> |
||||||
|
<a href="?" class="button" style="margin-left: 10px">Back to Orders</a> |
||||||
|
|
||||||
|
<h2>Items Summary</h2> |
||||||
|
<table style="width: 100%; border-collapse: collapse;"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Product</th> |
||||||
|
<th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Color</th> |
||||||
|
<th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Size</th> |
||||||
|
<th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Quantity</th> |
||||||
|
<th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Orders</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
{% for item in items %} |
||||||
|
<tr> |
||||||
|
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{{ item.product.title }}</td> |
||||||
|
<td style="padding: 8px; border-bottom: 1px solid #ddd;"> |
||||||
|
{% if item.color %} |
||||||
|
<span style="display: inline-block; width: 15px; height: 15px; border-radius: 50%; background-color: {{ item.color.colorHex }}; margin-right: 5px;"></span> |
||||||
|
{{ item.color.name }} |
||||||
|
{% else %} |
||||||
|
- |
||||||
|
{% endif %} |
||||||
|
</td> |
||||||
|
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{{ item.size.name|default:"-" }}</td> |
||||||
|
<td style="padding: 8px; border-bottom: 1px solid #ddd; font-weight: bold;">{{ item.quantity }}</td> |
||||||
|
<td style="padding: 8px; border-bottom: 1px solid #ddd;"> |
||||||
|
{% for order_id in item.orders %} |
||||||
|
<a href="../{{ order_id }}/change/">Order #{{ order_id }}</a>{% if not forloop.last %}, {% endif %} |
||||||
|
{% endfor %} |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
{% empty %} |
||||||
|
<tr> |
||||||
|
<td colspan="5" style="padding: 8px; text-align: center;">No items to prepare</td> |
||||||
|
</tr> |
||||||
|
{% endfor %} |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
|
||||||
|
<h2 style="margin-top: 30px;">Order Details</h2> |
||||||
|
<table style="width: 100%; border-collapse: collapse;"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Order #</th> |
||||||
|
<th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Date</th> |
||||||
|
<th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Customer</th> |
||||||
|
<th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Items</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
{% for order in orders %} |
||||||
|
<tr> |
||||||
|
<td style="padding: 8px; border-bottom: 1px solid #ddd;"><a href="../{{ order.id }}/change/">Order #{{ order.id }}</a></td> |
||||||
|
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{{ order.date_ordered|date:"Y-m-d H:i" }}</td> |
||||||
|
<td style="padding: 8px; border-bottom: 1px solid #ddd;"> |
||||||
|
{% if order.user %} |
||||||
|
{{ order.user.email }} |
||||||
|
{% elif order.guest_user %} |
||||||
|
{{ order.guest_user.email }} (Guest) |
||||||
|
{% else %} |
||||||
|
Unknown |
||||||
|
{% endif %} |
||||||
|
</td> |
||||||
|
<td style="padding: 8px; border-bottom: 1px solid #ddd;"> |
||||||
|
{% for item in order.items.all %} |
||||||
|
{{ item.quantity }}x {{ item.product.title }} |
||||||
|
{% if item.color %} ({{ item.color.name }}){% endif %} |
||||||
|
{% if item.size %} [{{ item.size.name }}]{% endif %} |
||||||
|
<br> |
||||||
|
{% endfor %} |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
{% empty %} |
||||||
|
<tr> |
||||||
|
<td colspan="4" style="padding: 8px; text-align: center;">No orders found</td> |
||||||
|
</tr> |
||||||
|
{% endfor %} |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
|
||||||
|
<style> |
||||||
|
@media print { |
||||||
|
button, .button, #header, .breadcrumbs { |
||||||
|
display: none !important; |
||||||
|
} |
||||||
|
body { |
||||||
|
padding: 0; |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
</div> |
||||||
|
{% endblock %} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
from django.core.management.base import BaseCommand |
||||||
|
from tournaments.tasks import check_confirmation_deadlines |
||||||
|
from background_task.models import Task |
||||||
|
|
||||||
|
class Command(BaseCommand): |
||||||
|
help = 'Run confirmation deadline check immediately' |
||||||
|
|
||||||
|
def handle(self, *args, **options): |
||||||
|
# Run the function directly (not through the task queue) |
||||||
|
Task.objects.filter(task_name='tournaments.tasks.check_confirmation_deadlines').delete() |
||||||
|
check_confirmation_deadlines() |
||||||
|
self.stdout.write(self.style.SUCCESS('Successfully checked confirmation deadlines')) |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
from django.core.management.base import BaseCommand |
||||||
|
from tournaments.tasks import background_task_check_confirmation_deadlines |
||||||
|
from django.utils import timezone |
||||||
|
import datetime |
||||||
|
from background_task.models import Task |
||||||
|
from django.conf import settings |
||||||
|
|
||||||
|
class Command(BaseCommand): |
||||||
|
help = 'Schedule background tasks to run at :00 and :30 of every hour' |
||||||
|
|
||||||
|
def handle(self, *args, **options): |
||||||
|
# Clear existing tasks first to avoid duplicates |
||||||
|
Task.objects.filter(task_name='tournaments.tasks.check_confirmation_deadlines').delete() |
||||||
|
|
||||||
|
# Get the current timezone-aware time |
||||||
|
now = timezone.now() |
||||||
|
|
||||||
|
# Get local timezone for display purposes |
||||||
|
local_timezone = timezone.get_current_timezone() |
||||||
|
local_now = now.astimezone(local_timezone) |
||||||
|
|
||||||
|
# Calculate time until next interval |
||||||
|
current_minute = local_now.minute |
||||||
|
minutes_until_next = settings.BACKGROUND_SCHEDULED_TASK_INTERVAL - (current_minute % settings.BACKGROUND_SCHEDULED_TASK_INTERVAL) |
||||||
|
next_minute = (current_minute + minutes_until_next) % 60 |
||||||
|
next_hour = local_now.hour + ((current_minute + minutes_until_next) // 60) |
||||||
|
|
||||||
|
# Create a datetime with the next run time in local time |
||||||
|
first_run_local = local_now.replace( |
||||||
|
hour=next_hour, |
||||||
|
minute=next_minute + 1, #let the expiration time be off first |
||||||
|
second=0, |
||||||
|
microsecond=0 |
||||||
|
) |
||||||
|
|
||||||
|
# Handle day rollover if needed |
||||||
|
if first_run_local < local_now: # This would happen if we crossed midnight |
||||||
|
first_run_local += datetime.timedelta(days=1) |
||||||
|
|
||||||
|
# Calculate seconds from now until the first run |
||||||
|
seconds_until_first_run = (first_run_local - local_now).total_seconds() |
||||||
|
if seconds_until_first_run < 0: |
||||||
|
seconds_until_first_run = 0 # If somehow negative, run immediately |
||||||
|
|
||||||
|
# Schedule with seconds delay instead of a specific datetime |
||||||
|
background_task_check_confirmation_deadlines( |
||||||
|
schedule=int(seconds_until_first_run), # Delay in seconds before first run |
||||||
|
repeat=settings.BACKGROUND_SCHEDULED_TASK_INTERVAL * 60 # 30 minutes in seconds |
||||||
|
) |
||||||
|
|
||||||
|
# Show the message with proper timezone info |
||||||
|
local_timezone_name = local_timezone.tzname(local_now) |
||||||
|
self.stdout.write(self.style.SUCCESS( |
||||||
|
f'Task scheduled to first run at {first_run_local.strftime("%H:%M:%S")} {local_timezone_name} ' |
||||||
|
f'(in {int(seconds_until_first_run)} seconds) ' |
||||||
|
f'and then every {settings.BACKGROUND_SCHEDULED_TASK_INTERVAL} minutes' |
||||||
|
)) |
||||||
@ -0,0 +1,133 @@ |
|||||||
|
# Generated by Django 5.1 on 2025-04-14 09:08 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('tournaments', '0115_auto_20250403_1503'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AddField( |
||||||
|
model_name='customuser', |
||||||
|
name='disable_ranking_federal_ruling', |
||||||
|
field=models.BooleanField(default=False), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='customuser', |
||||||
|
name='hide_umpire_mail', |
||||||
|
field=models.BooleanField(default=False), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='customuser', |
||||||
|
name='hide_umpire_phone', |
||||||
|
field=models.BooleanField(default=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='customuser', |
||||||
|
name='registration_payment_mode', |
||||||
|
field=models.IntegerField(choices=[(0, 'Disabled'), (1, 'Corporate'), (2, 'No Service Fee'), (3, 'Stripe')], default=0), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='customuser', |
||||||
|
name='umpire_custom_contact', |
||||||
|
field=models.CharField(blank=True, max_length=200, null=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='customuser', |
||||||
|
name='umpire_custom_mail', |
||||||
|
field=models.EmailField(blank=True, max_length=254, null=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='customuser', |
||||||
|
name='umpire_custom_phone', |
||||||
|
field=models.CharField(blank=True, max_length=15, null=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='customuser', |
||||||
|
name='user_role', |
||||||
|
field=models.IntegerField(blank=True, choices=[(0, 'Juge-Arbitre'), (1, 'Club Owner'), (2, 'Player')], null=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='playerregistration', |
||||||
|
name='payment_id', |
||||||
|
field=models.CharField(blank=True, max_length=255, null=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='playerregistration', |
||||||
|
name='registration_status', |
||||||
|
field=models.IntegerField(choices=[(0, 'Waiting'), (1, 'Pending'), (2, 'Confirmed'), (3, 'Canceled')], default=0), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='playerregistration', |
||||||
|
name='time_to_confirm', |
||||||
|
field=models.DateTimeField(blank=True, null=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='tournament', |
||||||
|
name='enable_online_payment', |
||||||
|
field=models.BooleanField(default=False), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='tournament', |
||||||
|
name='enable_online_payment_refund', |
||||||
|
field=models.BooleanField(default=False), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='tournament', |
||||||
|
name='enable_time_to_confirm', |
||||||
|
field=models.BooleanField(default=False), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='tournament', |
||||||
|
name='is_corporate_tournament', |
||||||
|
field=models.BooleanField(default=False), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='tournament', |
||||||
|
name='is_template', |
||||||
|
field=models.BooleanField(default=False), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='tournament', |
||||||
|
name='online_payment_is_mandatory', |
||||||
|
field=models.BooleanField(default=False), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='tournament', |
||||||
|
name='refund_date_limit', |
||||||
|
field=models.DateTimeField(blank=True, null=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='tournament', |
||||||
|
name='reserved_spots', |
||||||
|
field=models.IntegerField(default=0), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='tournament', |
||||||
|
name='stripe_account_id', |
||||||
|
field=models.CharField(blank=True, max_length=255, null=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='unregisteredplayer', |
||||||
|
name='payment_id', |
||||||
|
field=models.CharField(blank=True, max_length=255, null=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='unregisteredplayer', |
||||||
|
name='payment_type', |
||||||
|
field=models.IntegerField(blank=True, choices=[(0, 'Cash'), (1, 'Lydia'), (2, 'Gift'), (3, 'Check'), (4, 'Paylib'), (5, 'Bank Wire'), (6, 'Club House'), (7, 'Credit Card'), (8, 'Forfeit')], null=True), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='unregisteredplayer', |
||||||
|
name='registered_online', |
||||||
|
field=models.BooleanField(default=False), |
||||||
|
), |
||||||
|
migrations.AlterField( |
||||||
|
model_name='tournament', |
||||||
|
name='federal_level_category', |
||||||
|
field=models.IntegerField(choices=[(0, 'Animation'), (25, 'P25'), (100, 'P100'), (250, 'P250'), (500, 'P500'), (1000, 'P1000'), (1500, 'P1500'), (2000, 'P2000'), (1, 'Championnat')], default=100), |
||||||
|
), |
||||||
|
] |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
# Generated by Django 5.1 on 2025-04-25 07:48 |
||||||
|
|
||||||
|
import django.db.models.deletion |
||||||
|
from django.conf import settings |
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('tournaments', '0116_customuser_disable_ranking_federal_ruling_and_more'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AddField( |
||||||
|
model_name='playerregistration', |
||||||
|
name='user', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='player_registrations', to=settings.AUTH_USER_MODEL), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='teamregistration', |
||||||
|
name='user', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team_registrations', to=settings.AUTH_USER_MODEL), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='unregisteredplayer', |
||||||
|
name='user', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unregistered_players', to=settings.AUTH_USER_MODEL), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='unregisteredteam', |
||||||
|
name='user', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unregistered_teams', to=settings.AUTH_USER_MODEL), |
||||||
|
), |
||||||
|
] |
||||||
@ -1,81 +0,0 @@ |
|||||||
from .models import TeamRegistration, PlayerRegistration |
|
||||||
from .models.player_enums import PlayerSexType, PlayerDataSource |
|
||||||
from .models.enums import FederalCategory |
|
||||||
from tournaments.utils.licence_validator import LicenseValidator |
|
||||||
|
|
||||||
class TournamentRegistrationRepository: |
|
||||||
@staticmethod |
|
||||||
def create_team_registration(tournament, registration_date): |
|
||||||
team_registration = TeamRegistration.objects.create( |
|
||||||
tournament=tournament, |
|
||||||
registration_date=registration_date |
|
||||||
) |
|
||||||
return team_registration |
|
||||||
|
|
||||||
@staticmethod |
|
||||||
def create_player_registrations(request, team_registration, players_data, team_form_data): |
|
||||||
stripped_license = None |
|
||||||
if request.user.is_authenticated and request.user.licence_id: |
|
||||||
stripped_license = LicenseValidator(request.user.licence_id).stripped_license |
|
||||||
|
|
||||||
for player_data in players_data: |
|
||||||
is_captain = False |
|
||||||
player_licence_id = player_data['licence_id'] |
|
||||||
if player_licence_id and stripped_license: |
|
||||||
if stripped_license.lower() in player_licence_id.lower(): |
|
||||||
is_captain = True |
|
||||||
|
|
||||||
sex, rank, computed_rank = TournamentRegistrationRepository._compute_rank_and_sex( |
|
||||||
team_registration.tournament, |
|
||||||
player_data |
|
||||||
) |
|
||||||
|
|
||||||
print("create_player_registrations", player_data.get('last_name'), sex, rank, computed_rank) |
|
||||||
data_source = None |
|
||||||
if player_data.get('found_in_french_federation', False) == True: |
|
||||||
data_source = PlayerDataSource.FRENCH_FEDERATION |
|
||||||
|
|
||||||
player_registration = PlayerRegistration.objects.create( |
|
||||||
team_registration=team_registration, |
|
||||||
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=sex, |
|
||||||
rank=rank, |
|
||||||
computed_rank=computed_rank, |
|
||||||
licence_id=player_data['licence_id'], |
|
||||||
email=player_data.get('email'), |
|
||||||
phone_number=player_data.get('mobile_number'), |
|
||||||
) |
|
||||||
|
|
||||||
player_registration.save() |
|
||||||
|
|
||||||
team_registration.set_weight() |
|
||||||
team_registration.save() |
|
||||||
|
|
||||||
@staticmethod |
|
||||||
def _compute_rank_and_sex(tournament, player_data): |
|
||||||
is_woman = player_data.get('is_woman', False) |
|
||||||
rank = player_data.get('rank', None) |
|
||||||
if rank is None: |
|
||||||
computed_rank = 100000 |
|
||||||
else: |
|
||||||
computed_rank = rank |
|
||||||
|
|
||||||
sex = PlayerSexType.MALE |
|
||||||
if is_woman: |
|
||||||
sex = PlayerSexType.FEMALE |
|
||||||
if tournament.federal_category == FederalCategory.MEN: |
|
||||||
computed_rank = str(int(computed_rank) + |
|
||||||
FederalCategory.female_in_male_assimilation_addition(int(rank))) |
|
||||||
|
|
||||||
print("_compute_rank_and_sex", sex, rank, computed_rank) |
|
||||||
return sex, rank, computed_rank |
|
||||||
@ -0,0 +1,401 @@ |
|||||||
|
from django.shortcuts import get_object_or_404 |
||||||
|
from django.conf import settings |
||||||
|
from django.urls import reverse |
||||||
|
from django.http import HttpResponse |
||||||
|
from django.views.decorators.csrf import csrf_exempt |
||||||
|
from django.views.decorators.http import require_POST |
||||||
|
import stripe |
||||||
|
|
||||||
|
from ..models import TeamRegistration, PlayerRegistration, Tournament |
||||||
|
from ..models.player_registration import PlayerPaymentType |
||||||
|
from .email_service import TournamentEmailService |
||||||
|
from .tournament_registration import RegistrationCartManager |
||||||
|
from ..utils.extensions import is_not_sqlite_backend |
||||||
|
|
||||||
|
class PaymentService: |
||||||
|
""" |
||||||
|
Service for handling payment processing for tournament registrations |
||||||
|
""" |
||||||
|
|
||||||
|
def __init__(self, request): |
||||||
|
self.request = request |
||||||
|
self.stripe_api_key = settings.STRIPE_SECRET_KEY |
||||||
|
|
||||||
|
def create_checkout_session(self, tournament_id, team_fee, cart_data=None, team_registration_id=None): |
||||||
|
""" |
||||||
|
Create a Stripe checkout session for tournament payment |
||||||
|
""" |
||||||
|
stripe.api_key = self.stripe_api_key |
||||||
|
tournament = get_object_or_404(Tournament, id=tournament_id) |
||||||
|
|
||||||
|
# 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 user email if authenticated |
||||||
|
customer_email = self.request.user.email if self.request.user.is_authenticated else None |
||||||
|
|
||||||
|
# Determine the appropriate cancel URL based on the context |
||||||
|
if team_registration_id: |
||||||
|
# If we're paying for an existing registration, go back to tournament info |
||||||
|
cancel_url = self.request.build_absolute_uri( |
||||||
|
reverse('tournament-info', kwargs={'tournament_id': tournament_id}) |
||||||
|
) |
||||||
|
else: |
||||||
|
# If we're in the registration process, go back to registration form |
||||||
|
cancel_url = self.request.build_absolute_uri( |
||||||
|
reverse('register_tournament', kwargs={'tournament_id': tournament_id}) |
||||||
|
) |
||||||
|
|
||||||
|
base_metadata = { |
||||||
|
'tournament_id': str(tournament_id), |
||||||
|
'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', |
||||||
|
} |
||||||
|
|
||||||
|
if tournament.is_corporate_tournament: |
||||||
|
# Corporate tournament metadata |
||||||
|
metadata = { |
||||||
|
**base_metadata, |
||||||
|
'is_corporate_tournament': 'true', |
||||||
|
'stripe_account_type': 'direct' |
||||||
|
} |
||||||
|
else: |
||||||
|
# Regular tournament metadata |
||||||
|
metadata = { |
||||||
|
**base_metadata, |
||||||
|
'is_corporate_tournament': 'false', |
||||||
|
'stripe_account_type': 'connect', |
||||||
|
'stripe_account_id': tournament.stripe_account_id |
||||||
|
} |
||||||
|
|
||||||
|
if cart_data: |
||||||
|
metadata.update({ |
||||||
|
'registration_cart_id': str(cart_data['cart_id']), |
||||||
|
'registration_type': 'cart', |
||||||
|
'player_count': str(cart_data.get('player_count', 0)), |
||||||
|
'waiting_list_position': str(cart_data.get('waiting_list_position', -1)) |
||||||
|
}) |
||||||
|
elif team_registration_id: |
||||||
|
metadata.update({ |
||||||
|
'team_registration_id': str(team_registration_id), |
||||||
|
'registration_type': 'direct' |
||||||
|
}) |
||||||
|
self.request.session['team_registration_id'] = str(team_registration_id) |
||||||
|
|
||||||
|
metadata.update({ |
||||||
|
'tournament_name': tournament.broadcast_display_name(), |
||||||
|
'tournament_date': tournament.formatted_start_date(), |
||||||
|
'tournament_club': tournament.event.club.name, |
||||||
|
'tournament_fee': str(team_fee) |
||||||
|
}) |
||||||
|
|
||||||
|
# Common checkout session parameters |
||||||
|
if tournament.is_corporate_tournament: |
||||||
|
# Direct charge without transfers when umpire is platform owner |
||||||
|
checkout_session_params = { |
||||||
|
'payment_method_types': ['card'], |
||||||
|
'line_items': [{ |
||||||
|
'price_data': { |
||||||
|
'currency': 'eur', |
||||||
|
'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 |
||||||
|
}, |
||||||
|
'quantity': 1, |
||||||
|
}], |
||||||
|
'mode': 'payment', |
||||||
|
'success_url': self.request.build_absolute_uri( |
||||||
|
reverse('tournament-payment-success', kwargs={'tournament_id': tournament_id}) |
||||||
|
), |
||||||
|
'cancel_url': cancel_url, |
||||||
|
'metadata': metadata |
||||||
|
} |
||||||
|
else: |
||||||
|
# Get the umpire's Stripe account ID |
||||||
|
stripe_account_id = tournament.stripe_account_id |
||||||
|
if not stripe_account_id: |
||||||
|
raise Exception("L'arbitre n'a pas configuré son compte Stripe.") |
||||||
|
|
||||||
|
# Calculate commission |
||||||
|
commission_rate = tournament.event.creator.effective_commission_rate() |
||||||
|
platform_amount = int((team_fee * commission_rate) * 100) # Commission in cents |
||||||
|
|
||||||
|
checkout_session_params = { |
||||||
|
'payment_method_types': ['card'], |
||||||
|
'line_items': [{ |
||||||
|
'price_data': { |
||||||
|
'currency': 'eur', |
||||||
|
'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 |
||||||
|
}, |
||||||
|
'quantity': 1, |
||||||
|
}], |
||||||
|
'mode': 'payment', |
||||||
|
'success_url': self.request.build_absolute_uri( |
||||||
|
reverse('tournament-payment-success', kwargs={'tournament_id': tournament_id}) |
||||||
|
), |
||||||
|
'cancel_url': cancel_url, |
||||||
|
'payment_intent_data': { |
||||||
|
'application_fee_amount': platform_amount, |
||||||
|
'transfer_data': { |
||||||
|
'destination': stripe_account_id, |
||||||
|
}, |
||||||
|
}, |
||||||
|
'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 |
||||||
|
|
||||||
|
# Create the checkout session |
||||||
|
try: |
||||||
|
checkout_session = stripe.checkout.Session.create(**checkout_session_params) |
||||||
|
|
||||||
|
# Store checkout session ID and source page in session |
||||||
|
self.request.session['stripe_checkout_session_id'] = checkout_session.id |
||||||
|
self.request.session['payment_source_page'] = 'tournament_info' if team_registration_id else 'register_tournament' |
||||||
|
self.request.session.modified = True |
||||||
|
|
||||||
|
return checkout_session |
||||||
|
except stripe.error.StripeError as e: |
||||||
|
# Handle specific Stripe errors more gracefully |
||||||
|
if 'destination' in str(e): |
||||||
|
raise Exception("Erreur avec le compte Stripe de l'arbitre. Contactez l'administrateur.") |
||||||
|
else: |
||||||
|
raise Exception(f"Erreur Stripe: {str(e)}") |
||||||
|
|
||||||
|
def process_successful_payment(self, tournament_id, checkout_session): |
||||||
|
""" |
||||||
|
Process a successful Stripe payment |
||||||
|
Returns a tuple (success, redirect_response) |
||||||
|
""" |
||||||
|
print(f"Processing payment for tournament {tournament_id}") |
||||||
|
tournament = get_object_or_404(Tournament, id=tournament_id) |
||||||
|
|
||||||
|
# Check if this is a payment for an existing team registration |
||||||
|
team_registration_id = self.request.session.get('team_registration_id') |
||||||
|
print(f"Team registration ID from session: {team_registration_id}") |
||||||
|
|
||||||
|
# Track payment statuses for debugging |
||||||
|
payment_statuses = [] |
||||||
|
|
||||||
|
if team_registration_id: |
||||||
|
success = self._process_direct_payment(checkout_session) |
||||||
|
payment_statuses.append(success) |
||||||
|
print(f"Direct payment processing result: {success}") |
||||||
|
else: |
||||||
|
# This is a payment during registration process |
||||||
|
success = self._process_registration_payment(tournament, checkout_session) |
||||||
|
payment_statuses.append(success) |
||||||
|
print(f"Registration payment processing result: {success}") |
||||||
|
|
||||||
|
# Print combined payment status |
||||||
|
print(f"Payment statuses: {payment_statuses}") |
||||||
|
print(any(payment_statuses)) |
||||||
|
|
||||||
|
# Clear checkout session ID |
||||||
|
if 'stripe_checkout_session_id' in self.request.session: |
||||||
|
del self.request.session['stripe_checkout_session_id'] |
||||||
|
|
||||||
|
return any(payment_statuses) |
||||||
|
|
||||||
|
def _process_direct_payment(self, checkout_session): |
||||||
|
"""Process payment for an existing team registration""" |
||||||
|
team_registration_id = self.request.session.get('team_registration_id') |
||||||
|
if not team_registration_id: |
||||||
|
print("No team registration ID found in session") |
||||||
|
return False |
||||||
|
|
||||||
|
try: |
||||||
|
print(f"Looking for team registration with ID: {team_registration_id}") |
||||||
|
team_registration = TeamRegistration.objects.get(id=team_registration_id) |
||||||
|
success = self._update_registration_payment_info( |
||||||
|
team_registration, |
||||||
|
checkout_session.payment_intent |
||||||
|
) |
||||||
|
|
||||||
|
# Clean up session |
||||||
|
if 'team_registration_id' in self.request.session: |
||||||
|
del self.request.session['team_registration_id'] |
||||||
|
|
||||||
|
if success: |
||||||
|
TournamentEmailService.send_payment_confirmation(team_registration, checkout_session.payment_intent) |
||||||
|
return success |
||||||
|
except TeamRegistration.DoesNotExist: |
||||||
|
print(f"Team registration not found with ID: {team_registration_id}") |
||||||
|
return False |
||||||
|
except Exception as e: |
||||||
|
print(f"Error in _process_direct_payment: {str(e)}") |
||||||
|
return False |
||||||
|
|
||||||
|
def _process_registration_payment(self, tournament, checkout_session): |
||||||
|
"""Process payment made during registration""" |
||||||
|
cart_manager = RegistrationCartManager(self.request) |
||||||
|
cart_data = cart_manager.get_cart_data() |
||||||
|
|
||||||
|
# Checkout and create registration |
||||||
|
success, result = cart_manager.checkout() |
||||||
|
if not success: |
||||||
|
return False |
||||||
|
|
||||||
|
# Process payment for the new registration |
||||||
|
team_registration = result # result is team_registration object |
||||||
|
self._update_registration_payment_info( |
||||||
|
team_registration, |
||||||
|
checkout_session.payment_intent |
||||||
|
) |
||||||
|
|
||||||
|
# Send confirmation email if appropriate |
||||||
|
waiting_list_position = cart_data.get('waiting_list_position', -1) |
||||||
|
if is_not_sqlite_backend(): |
||||||
|
email_service = TournamentEmailService() |
||||||
|
email_service.send_registration_confirmation( |
||||||
|
self.request, |
||||||
|
tournament, |
||||||
|
team_registration, |
||||||
|
waiting_list_position |
||||||
|
) |
||||||
|
|
||||||
|
return True |
||||||
|
|
||||||
|
def _update_registration_payment_info(self, team_registration, payment_intent_id): |
||||||
|
"""Update player registrations with payment information""" |
||||||
|
team_registration.confirm_registration(payment_intent_id) |
||||||
|
return True |
||||||
|
|
||||||
|
def process_refund(self, team_registration_id): |
||||||
|
""" |
||||||
|
Process a refund for a tournament registration as part of unregistration |
||||||
|
Returns a tuple (success, message) |
||||||
|
""" |
||||||
|
stripe.api_key = self.stripe_api_key |
||||||
|
|
||||||
|
try: |
||||||
|
# Get the team registration |
||||||
|
team_registration = get_object_or_404(TeamRegistration, id=team_registration_id) |
||||||
|
tournament = team_registration.tournament |
||||||
|
|
||||||
|
# Check if refund is possible for this tournament |
||||||
|
if not tournament.is_refund_possible(): |
||||||
|
return False, "Les remboursements ne sont plus possibles pour ce tournoi.", None |
||||||
|
|
||||||
|
# Get payment ID from player registrations |
||||||
|
player_registrations = PlayerRegistration.objects.filter(team_registration=team_registration) |
||||||
|
payment_id = None |
||||||
|
|
||||||
|
for player_reg in player_registrations: |
||||||
|
# Find the first valid payment ID |
||||||
|
if player_reg.payment_id and player_reg.payment_type == PlayerPaymentType.CREDIT_CARD: |
||||||
|
payment_id = player_reg.payment_id |
||||||
|
break |
||||||
|
|
||||||
|
if not payment_id: |
||||||
|
return False, "Aucun paiement trouvé pour cette équipe.", None |
||||||
|
|
||||||
|
# Get the Stripe payment intent |
||||||
|
payment_intent = stripe.PaymentIntent.retrieve(payment_id) |
||||||
|
|
||||||
|
if payment_intent.status != 'succeeded': |
||||||
|
return False, "Le paiement n'a pas été complété, il ne peut pas être remboursé.", None |
||||||
|
|
||||||
|
# Process the refund - with different parameters based on tournament type |
||||||
|
refund_params = { |
||||||
|
'payment_intent': payment_id |
||||||
|
} |
||||||
|
|
||||||
|
# Only include transfer reversal for non-corporate tournaments |
||||||
|
if not tournament.is_corporate_tournament: |
||||||
|
refund_params.update({ |
||||||
|
'refund_application_fee': True, |
||||||
|
'reverse_transfer': True |
||||||
|
}) |
||||||
|
|
||||||
|
refund = stripe.Refund.create(**refund_params) |
||||||
|
|
||||||
|
for player_reg in player_registrations: |
||||||
|
player_reg.payment_type = None |
||||||
|
player_reg.payment_id = None |
||||||
|
player_reg.save() |
||||||
|
|
||||||
|
TournamentEmailService.send_refund_confirmation(tournament, team_registration, refund) |
||||||
|
|
||||||
|
# Return success with refund object |
||||||
|
return True, "L'inscription a été remboursée automatiquement.", refund |
||||||
|
|
||||||
|
except stripe.error.StripeError as e: |
||||||
|
return False, f"Erreur de remboursement Stripe: {str(e)}", None |
||||||
|
except Exception as e: |
||||||
|
return False, f"Erreur lors du remboursement: {str(e)}", None |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
@csrf_exempt |
||||||
|
@require_POST |
||||||
|
def stripe_webhook(request): |
||||||
|
payload = request.body |
||||||
|
sig_header = request.META.get('HTTP_STRIPE_SIGNATURE') |
||||||
|
print("Received webhook call") |
||||||
|
print(f"Signature: {sig_header}") |
||||||
|
|
||||||
|
try: |
||||||
|
event = stripe.Webhook.construct_event( |
||||||
|
payload, sig_header, settings.TOURNAMENT_STRIPE_WEBHOOK_SECRET |
||||||
|
) |
||||||
|
print(f"Tournament webhook event type: {event['type']}") |
||||||
|
|
||||||
|
if event['type'] == 'checkout.session.completed': |
||||||
|
session = event['data']['object'] |
||||||
|
metadata = session.get('metadata', {}) |
||||||
|
tournament_id = metadata.get('tournament_id') |
||||||
|
|
||||||
|
if not tournament_id: |
||||||
|
print("No tournament_id in metadata") |
||||||
|
return HttpResponse(status=400) |
||||||
|
|
||||||
|
payment_service = PaymentService(request) |
||||||
|
success = payment_service.process_successful_payment(tournament_id, session) |
||||||
|
|
||||||
|
if success: |
||||||
|
print(f"Successfully processed webhook payment for tournament {tournament_id}") |
||||||
|
return HttpResponse(status=200) |
||||||
|
else: |
||||||
|
print(f"Failed to process webhook payment for tournament {tournament_id}") |
||||||
|
return HttpResponse(status=400) |
||||||
|
|
||||||
|
elif event['type'] == 'payment_intent.payment_failed': |
||||||
|
intent = event['data']['object'] |
||||||
|
metadata = intent.get('metadata', {}) |
||||||
|
|
||||||
|
tournament_id = metadata.get('tournament_id') |
||||||
|
source_page = metadata.get('source_page') |
||||||
|
|
||||||
|
if tournament_id and source_page == 'register_tournament': |
||||||
|
try: |
||||||
|
tournament = Tournament.objects.get(id=tournament_id) |
||||||
|
# Decrease reserved spots, minimum 0 |
||||||
|
tournament.reserved_spots = max(0, tournament.reserved_spots - 1) |
||||||
|
tournament.save() |
||||||
|
print(f"Decreased reserved spots for tournament {tournament_id} after payment failure") |
||||||
|
except Tournament.DoesNotExist: |
||||||
|
print(f"Tournament {tournament_id} not found") |
||||||
|
except Exception as e: |
||||||
|
print(f"Error updating tournament reserved spots: {str(e)}") |
||||||
|
|
||||||
|
return HttpResponse(status=200) |
||||||
|
|
||||||
|
except Exception as e: |
||||||
|
print(f"Tournament webhook error: {str(e)}") |
||||||
@ -1,372 +1,490 @@ |
|||||||
from django.utils import timezone |
from django.utils import timezone |
||||||
from ..forms import TournamentRegistrationForm, AddPlayerForm |
import uuid |
||||||
from ..repositories import TournamentRegistrationRepository |
import datetime |
||||||
from .email_service import TournamentEmailService |
from ..models import PlayerRegistration, TeamRegistration, Tournament |
||||||
from django.contrib import messages |
|
||||||
from ..utils.licence_validator import LicenseValidator |
from ..utils.licence_validator import LicenseValidator |
||||||
from ..utils.player_search import get_player_name_from_csv |
from ..utils.player_search import get_player_name_from_csv |
||||||
from tournaments.models import PlayerRegistration |
from ..models.enums import FederalCategory, RegistrationStatus |
||||||
from ..utils.extensions import is_not_sqlite_backend |
from ..models.player_enums import PlayerSexType, PlayerDataSource |
||||||
from django.contrib.auth import get_user_model |
from django.contrib.auth import get_user_model |
||||||
from django.contrib.messages import get_messages |
from django.conf import settings |
||||||
from django.db import IntegrityError |
|
||||||
|
|
||||||
class TournamentRegistrationService: |
class RegistrationCartManager: |
||||||
def __init__(self, request, tournament): |
""" |
||||||
|
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.request = request |
||||||
self.tournament = tournament |
self.session = request.session |
||||||
self.context = {} |
self.first_tournament = False |
||||||
self.repository = TournamentRegistrationRepository() |
|
||||||
self.email_service = TournamentEmailService() |
def get_or_create_cart_id(self): |
||||||
|
"""Get or create a registration cart ID in the session""" |
||||||
def initialize_context(self): |
if 'registration_cart_id' not in self.session: |
||||||
self.context = { |
self.session['registration_cart_id'] = str(uuid.uuid4()) # Ensure it's a string |
||||||
'tournament': self.tournament, |
self.session.modified = True |
||||||
'registration_successful': False, |
return self.session['registration_cart_id'] |
||||||
'team_form': None, |
|
||||||
'add_player_form': None, |
def get_cart_expiry(self): |
||||||
'current_players': self.request.session.get('team_registration', []), |
"""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) |
||||||
} |
} |
||||||
return self.context |
|
||||||
|
|
||||||
def handle_post_request(self): |
|
||||||
self.context['team_form'] = TournamentRegistrationForm(self.request.POST) |
|
||||||
self.context['add_player_form'] = AddPlayerForm(self.request.POST) |
|
||||||
|
|
||||||
if 'add_player' in self.request.POST: |
|
||||||
self.handle_add_player() |
|
||||||
if 'remove_player' in self.request.POST: |
|
||||||
self.handle_remove_player() |
|
||||||
elif 'register_team' in self.request.POST: |
|
||||||
self.handle_team_registration() |
|
||||||
|
|
||||||
def handle_remove_player(self): |
|
||||||
team_registration = self.request.session.get('team_registration', []) |
|
||||||
if team_registration: # Check if list is not empty |
|
||||||
team_registration.pop() # Remove last element |
|
||||||
self.request.session['team_registration'] = team_registration |
|
||||||
self.context['current_players'] = team_registration |
|
||||||
|
|
||||||
def handle_add_player(self): |
|
||||||
if not self.context['add_player_form'].is_valid(): |
|
||||||
return |
|
||||||
|
|
||||||
# Clear existing messages if the form is valid |
|
||||||
storage = get_messages(self.request) |
|
||||||
# Iterate through the storage to clear it |
|
||||||
for _ in storage: |
|
||||||
pass |
|
||||||
|
|
||||||
player_data = self.context['add_player_form'].cleaned_data |
|
||||||
licence_id = player_data.get('licence_id', '').upper() |
|
||||||
|
|
||||||
# Validate license |
|
||||||
if not self._validate_license(licence_id): |
|
||||||
return |
|
||||||
|
|
||||||
# Check for duplicate players |
|
||||||
if self._is_duplicate_player(licence_id): |
|
||||||
return |
|
||||||
|
|
||||||
# Check if player is already registered in tournament |
|
||||||
if self._is_already_registered(licence_id): |
|
||||||
return |
|
||||||
|
|
||||||
if self.request.user.is_authenticated and self.request.user.licence_id is None and len(self.context['current_players']) == 0: |
|
||||||
if self._update_user_license(player_data.get('licence_id')) == False: |
|
||||||
# if no licence id for authentificated user and trying to add him as first player of the team, we check his federal data |
|
||||||
self._handle_invalid_names(licence_id, player_data) |
|
||||||
else: |
|
||||||
# Handle player data |
|
||||||
if self.context['add_player_form'].names_is_valid(): |
|
||||||
self._handle_valid_names(player_data) |
|
||||||
else: |
|
||||||
self._handle_invalid_names(licence_id, player_data) |
|
||||||
|
|
||||||
def handle_team_registration(self): |
|
||||||
if not self.context['team_form'].is_valid(): |
|
||||||
return |
|
||||||
|
|
||||||
if self.request.user.is_authenticated: |
|
||||||
cleaned_data = self.context['team_form'].cleaned_data |
|
||||||
mobile_number = cleaned_data.get('mobile_number') |
|
||||||
self.request.user.phone = mobile_number |
|
||||||
self.request.user.save() |
|
||||||
|
|
||||||
waiting_list_position = self.tournament.get_waiting_list_position() |
# 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." |
||||||
|
|
||||||
team_registration = self.repository.create_team_registration( |
# Get existing players directly from session |
||||||
self.tournament, |
players = self.session.get('registration_cart_players', []) |
||||||
timezone.now().replace(microsecond=0) |
|
||||||
|
# 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." |
||||||
|
|
||||||
self.repository.create_player_registrations( |
# Create player registrations |
||||||
self.request, |
sex, rank, computed_rank = self._compute_rank_and_sex( |
||||||
team_registration, |
tournament, |
||||||
self.request.session['team_registration'], |
player_data |
||||||
self.context['team_form'].cleaned_data |
|
||||||
) |
) |
||||||
|
|
||||||
if is_not_sqlite_backend(): |
player_data['computed_rank'] = computed_rank |
||||||
self.email_service.send_registration_confirmation( |
|
||||||
self.request, |
|
||||||
self.tournament, |
|
||||||
team_registration, |
|
||||||
waiting_list_position |
|
||||||
) |
|
||||||
|
|
||||||
self.clear_session_data() |
# Add player to cart |
||||||
self.context['registration_successful'] = True |
players.append(player_data) |
||||||
|
self.session['registration_cart_players'] = players |
||||||
def handle_get_request(self): |
self.reset_cart_expiry() |
||||||
print("handle_get_request") |
self.session.modified = True |
||||||
storage = get_messages(self.request) |
|
||||||
# Iterate through the storage to clear it |
|
||||||
for _ in storage: |
|
||||||
pass |
|
||||||
|
|
||||||
self.context['add_player_form'] = AddPlayerForm() |
|
||||||
self.context['team_form'] = self.initialize_team_form() |
|
||||||
self.initialize_session_data() |
|
||||||
|
|
||||||
def add_player_to_session(self, player_data): |
|
||||||
print("add_player_to_session", player_data) |
|
||||||
if not self.request.session.get('team_registration'): |
|
||||||
self.request.session['team_registration'] = [] |
|
||||||
|
|
||||||
self.request.session['team_registration'].append(player_data) |
|
||||||
self.context['current_players'] = self.request.session.get('team_registration', []) |
|
||||||
self.context['add_player_form'].first_tournament = False |
|
||||||
self.context['add_player_form'].user_without_licence = False |
|
||||||
self.request.session.modified = True |
|
||||||
|
|
||||||
def clear_session_data(self): |
|
||||||
self.request.session['team_registration'] = [] |
|
||||||
self.request.session.modified = True |
|
||||||
|
|
||||||
def initialize_team_form(self): |
|
||||||
initial_data = {} |
|
||||||
if self.request.user.is_authenticated: |
|
||||||
initial_data = { |
|
||||||
'email': self.request.user.email, |
|
||||||
'mobile_number': self.request.user.phone, |
|
||||||
} |
|
||||||
return TournamentRegistrationForm(initial=initial_data) |
|
||||||
|
|
||||||
def initialize_session_data(self): |
|
||||||
print("initialize_session_data") |
|
||||||
self.request.session['team_registration'] = [] |
|
||||||
if self.request.user.is_authenticated: |
|
||||||
self._add_authenticated_user_to_session() |
|
||||||
|
|
||||||
def _add_authenticated_user_to_session(self): |
|
||||||
if not self.request.user.licence_id: |
|
||||||
self._handle_user_without_license() |
|
||||||
return |
|
||||||
|
|
||||||
player_data = self._get_authenticated_user_data() |
|
||||||
if player_data: |
|
||||||
self.request.session['team_registration'].insert(0, player_data) |
|
||||||
self.context['current_players'] = self.request.session.get('team_registration', []) |
|
||||||
self.request.session.modified = True |
|
||||||
|
|
||||||
def _handle_user_without_license(self): |
|
||||||
player_data = { |
|
||||||
'first_name': self.request.user.first_name, |
|
||||||
'last_name': self.request.user.last_name.upper(), |
|
||||||
} |
|
||||||
self.context['add_player_form'] = AddPlayerForm(initial=player_data) |
|
||||||
self.context['add_player_form'].user_without_licence = True |
|
||||||
self.request.session.modified = True |
|
||||||
|
|
||||||
def _get_authenticated_user_data(self): |
if sex == PlayerSexType.FEMALE: |
||||||
user = self.request.user |
return True, "Joueuse ajoutée avec succès." |
||||||
validator = LicenseValidator(user.licence_id) |
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 |
||||||
|
) |
||||||
|
|
||||||
player_data = { |
for player_data in players: # Compute rank and sex using the original logic |
||||||
'first_name': user.first_name, |
# Determine if this player is the captain |
||||||
'last_name': user.last_name.upper(), |
is_captain = False |
||||||
'email': user.email, |
player_licence_id = player_data.get('licence_id') |
||||||
'phone': user.phone, |
if player_licence_id and stripped_license: |
||||||
'licence_id': validator.computed_licence_id |
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 |
||||||
|
) |
||||||
|
|
||||||
data, found = get_player_name_from_csv(self.tournament.federal_category, user.licence_id) |
# Clear the cart |
||||||
if found and data: |
self.clear_cart() |
||||||
player_data.update({ |
tournament.reserved_spots = max(0, tournament.reserved_spots - 1) |
||||||
'rank': data['rank'], |
tournament.save() |
||||||
'points': data.get('points'), |
|
||||||
'assimilation': data.get('assimilation'), |
|
||||||
'tournament_count': data.get('tournament_count'), |
|
||||||
'ligue_name': data.get('ligue_name'), |
|
||||||
'club_name': data.get('club_name'), |
|
||||||
'birth_year': data.get('birth_year'), |
|
||||||
'found_in_french_federation': True, |
|
||||||
}) |
|
||||||
|
|
||||||
return player_data |
|
||||||
|
|
||||||
def _validate_license(self, licence_id): |
|
||||||
print("Validating license...") |
|
||||||
validator = LicenseValidator(licence_id) |
|
||||||
|
|
||||||
if validator.validate_license() is False and self.tournament.license_is_required: |
|
||||||
if not licence_id: |
|
||||||
message = ("Le numéro de licence est obligatoire." |
|
||||||
if not self.request.session.get('team_registration', []) |
|
||||||
else "Le numéro de licence de votre partenaire est obligatoire.") |
|
||||||
messages.error(self.request, message) |
|
||||||
else: |
|
||||||
# computed_license_key = validator.computed_license_key |
|
||||||
# messages.error(self.request, f"Le numéro de licence est invalide, la lettre ne correspond pas. {computed_license_key}") |
|
||||||
messages.error(self.request, "Le numéro de licence est invalide, la lettre ne correspond pas.") |
|
||||||
print("License validation failed") |
|
||||||
return False |
|
||||||
return True |
|
||||||
|
|
||||||
def _is_duplicate_player(self, licence_id): |
return True, team_registration |
||||||
existing_players = [player['licence_id'] for player in self.request.session.get('team_registration', [])] |
|
||||||
if licence_id in existing_players: |
|
||||||
messages.error(self.request, "Ce joueur est déjà dans l'équipe.") |
|
||||||
return True |
|
||||||
return False |
|
||||||
|
|
||||||
def _is_already_registered(self, licence_id): |
|
||||||
validator = LicenseValidator(licence_id) |
|
||||||
if (validator.validate_license() and |
|
||||||
self._license_already_registered(validator.stripped_license) and |
|
||||||
self.tournament.license_is_required): |
|
||||||
messages.error(self.request, "Un joueur avec ce numéro de licence est déjà inscrit dans une équipe.") |
|
||||||
return True |
|
||||||
return False |
|
||||||
|
|
||||||
def _handle_valid_names(self, player_data): |
|
||||||
print("_handle_valid_names", player_data) |
|
||||||
if player_data.get('rank') is None: |
|
||||||
self._set_default_rank(player_data) |
|
||||||
|
|
||||||
self.add_player_to_session(player_data) |
|
||||||
self.context['add_player_form'] = AddPlayerForm() |
|
||||||
self.context['add_player_form'].first_tournament = False |
|
||||||
|
|
||||||
def _handle_invalid_names(self, licence_id, player_data): |
|
||||||
data, found = get_player_name_from_csv(self.tournament.federal_category, licence_id) |
|
||||||
print("_handle_invalid_names get_player_name_from_csv", data, found) |
|
||||||
if found and data: |
|
||||||
self._update_player_data_from_csv(player_data, data) |
|
||||||
player_check = self._player_check(player_data) |
|
||||||
if player_check == True: |
|
||||||
self.add_player_to_session(player_data) |
|
||||||
self.context['add_player_form'] = AddPlayerForm() |
|
||||||
else: |
|
||||||
return |
|
||||||
else: |
|
||||||
print("_handle_first_tournament_case") |
|
||||||
self._handle_first_tournament_case(data) |
|
||||||
|
|
||||||
def _set_default_rank(self, player_data): |
|
||||||
if self.request.session.get('last_rank') is None: |
|
||||||
data, found = get_player_name_from_csv(self.tournament.federal_category, None) |
|
||||||
if data: |
|
||||||
self.request.session['last_rank'] = data['rank'] |
|
||||||
self.request.session['is_woman'] = data['is_woman'] |
|
||||||
self.request.session.modified = True |
|
||||||
|
|
||||||
player_data['rank'] = self.request.session.get('last_rank', None) |
|
||||||
player_data['is_woman'] = self.request.session.get('is_woman', False) |
|
||||||
|
|
||||||
def _update_user_license(self, licence_id): |
|
||||||
if not self.request.user.is_authenticated or not licence_id: |
|
||||||
return False |
|
||||||
|
|
||||||
self.context['add_player_form'].user_without_licence = False |
def clear_cart(self): |
||||||
validator = LicenseValidator(licence_id) |
"""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' |
||||||
|
] |
||||||
|
|
||||||
if validator.validate_license(): |
for key in keys_to_clear: |
||||||
computed_licence_id = validator.computed_licence_id |
if key in self.session: |
||||||
try: |
del self.session[key] |
||||||
self.request.user.licence_id = computed_licence_id |
|
||||||
self.request.user.save() |
|
||||||
self.request.user.refresh_from_db() |
|
||||||
self.request.session.modified = True |
|
||||||
return True |
|
||||||
|
|
||||||
except IntegrityError: |
|
||||||
# Handle the duplicate license error |
|
||||||
error_msg = f"Ce numéro de licence ({computed_licence_id}) est déjà utilisé par un autre joueur." |
|
||||||
messages.error(self.request, error_msg) |
|
||||||
return False |
|
||||||
|
|
||||||
def _update_player_data_from_csv(self, player_data, csv_data): |
|
||||||
print("_update_player_data_from_csv", player_data, csv_data) |
|
||||||
player_data.update({ |
|
||||||
'first_name': csv_data['first_name'], |
|
||||||
'last_name': csv_data['last_name'], |
|
||||||
'rank': csv_data['rank'], |
|
||||||
'is_woman': csv_data['is_woman'], |
|
||||||
'points': csv_data.get('points'), |
|
||||||
'assimilation': csv_data.get('assimilation'), |
|
||||||
'tournament_count': csv_data.get('tournament_count'), |
|
||||||
'ligue_name': csv_data.get('ligue_name'), |
|
||||||
'club_name': csv_data.get('club_name'), |
|
||||||
'birth_year': csv_data.get('birth_year'), |
|
||||||
'found_in_french_federation': True, |
|
||||||
'email': None, |
|
||||||
'phone': None, |
|
||||||
}) |
|
||||||
|
|
||||||
User = get_user_model() |
|
||||||
|
|
||||||
# Get the license ID from player_data |
|
||||||
licence_id = player_data.get('licence_id') |
|
||||||
validator = LicenseValidator(licence_id) |
|
||||||
if validator.validate_license(): |
|
||||||
try: |
|
||||||
# Try to find a user with matching license |
|
||||||
user_with_same_license = User.objects.get(licence_id__iexact=validator.computed_licence_id) |
|
||||||
|
|
||||||
# If found, update the email and phone |
|
||||||
if user_with_same_license: |
|
||||||
player_data.update({ |
|
||||||
'email': user_with_same_license.email, |
|
||||||
'phone': user_with_same_license.phone |
|
||||||
}) |
|
||||||
print(f"Found user with license {licence_id}, updated email and phone") |
|
||||||
except User.DoesNotExist: |
|
||||||
# No user found with this license, continue with None email and phone |
|
||||||
pass |
|
||||||
|
|
||||||
def _handle_first_tournament_case(self, data): |
|
||||||
print("_handle_first_tournament_case", data) |
|
||||||
if data: |
|
||||||
self.request.session['last_rank'] = data['rank'] |
|
||||||
self.request.session['is_woman'] = data['is_woman'] |
|
||||||
self.request.session.modified = True |
|
||||||
|
|
||||||
self.context['add_player_form'].first_tournament = True |
|
||||||
|
|
||||||
if not self.context['add_player_form'].names_is_valid(): |
|
||||||
message = ("Pour confirmer votre inscription votre prénom et votre nom sont obligatoires." |
|
||||||
if not self.request.session.get('team_registration', []) |
|
||||||
else "Pour rajouter un partenaire, son prénom et son nom sont obligatoires.") |
|
||||||
messages.error(self.request, message) |
|
||||||
|
|
||||||
def _player_check(self, player_data): |
|
||||||
licence_id = player_data['licence_id'].upper() |
|
||||||
validator = LicenseValidator(licence_id) |
|
||||||
is_license_valid = validator.validate_license() |
|
||||||
|
|
||||||
player_register_check = self.tournament.player_register_check(licence_id) |
|
||||||
if is_license_valid and player_register_check is not None: |
|
||||||
for message in player_register_check: |
|
||||||
messages.error(self.request, message) |
|
||||||
return False |
|
||||||
|
|
||||||
return True |
self.session.modified = True |
||||||
|
|
||||||
def _license_already_registered(self, stripped_license): |
def _is_player_already_registered(self, stripped_license, tournament): |
||||||
|
"""Check if a player is already registered in the tournament""" |
||||||
return PlayerRegistration.objects.filter( |
return PlayerRegistration.objects.filter( |
||||||
team_registration__tournament=self.tournament, |
team_registration__tournament=tournament, |
||||||
licence_id__icontains=stripped_license, |
licence_id__icontains=stripped_license, |
||||||
team_registration__walk_out=False |
team_registration__walk_out=False |
||||||
).exists() |
).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) |
||||||
|
|||||||
@ -0,0 +1,34 @@ |
|||||||
|
from background_task import background |
||||||
|
from django.utils import timezone |
||||||
|
from django.db import transaction |
||||||
|
from django.conf import settings |
||||||
|
|
||||||
|
from .models import Tournament |
||||||
|
from .models.enums import RegistrationStatus |
||||||
|
|
||||||
|
@background(schedule=settings.BACKGROUND_SCHEDULED_TASK_INTERVAL * 60) # Run every 30 minutes (30*60 seconds) |
||||||
|
def background_task_check_confirmation_deadlines(): |
||||||
|
#DEBUG ONLY NOT NEEDED ON PROD |
||||||
|
print("background_task Running confirmation deadline check...") |
||||||
|
check_confirmation_deadlines() |
||||||
|
|
||||||
|
def check_confirmation_deadlines(): |
||||||
|
""" |
||||||
|
Periodic task to check for expired confirmation deadlines |
||||||
|
and notify the next team in the waiting list. |
||||||
|
""" |
||||||
|
now = timezone.now() |
||||||
|
print(f"[{now}] Running confirmation deadline check...") |
||||||
|
|
||||||
|
# Get all tournaments with online registrations |
||||||
|
tournaments = Tournament.objects.filter( |
||||||
|
team_registrations__player_registrations__registration_status=RegistrationStatus.PENDING, |
||||||
|
team_registrations__player_registrations__registered_online=True |
||||||
|
).distinct() |
||||||
|
|
||||||
|
total_processed = 0 |
||||||
|
for tournament in tournaments: |
||||||
|
processed = tournament.check_all_confirmation_deadlines() |
||||||
|
total_processed += processed |
||||||
|
|
||||||
|
print(f"Processed confirmation deadlines for {total_processed} teams") |
||||||
Loading…
Reference in new issue