1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-07-20 08:01:26 +02:00

Compare commits

...

3 Commits

Author SHA1 Message Date
2755a5f7ab Minor fail 2025-07-19 17:10:25 +02:00
9ab4df94e6 Minor fixes 2025-07-19 16:55:07 +02:00
edb6abfff5 Add fee field to WEIRegistration to be able to sort on validation status 2025-07-19 16:24:25 +02:00
9 changed files with 148 additions and 58 deletions

View File

@ -6,7 +6,7 @@ from django.conf import settings
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .signals import save_user_profile from .signals import save_user_profile, update_wei_registration_fee_on_membership_creation, update_wei_registration_fee_on_club_change
class MemberConfig(AppConfig): class MemberConfig(AppConfig):
@ -17,7 +17,16 @@ class MemberConfig(AppConfig):
""" """
Define app internal signals to interact with other apps Define app internal signals to interact with other apps
""" """
from .models import Membership, Club
post_save.connect( post_save.connect(
save_user_profile, save_user_profile,
sender=settings.AUTH_USER_MODEL, sender=settings.AUTH_USER_MODEL,
) )
post_save.connect(
update_wei_registration_fee_on_membership_creation,
sender=Membership
)
post_save.connect(
update_wei_registration_fee_on_club_change,
sender=Club
)

View File

@ -13,3 +13,25 @@ def save_user_profile(instance, created, raw, **_kwargs):
instance.profile.email_confirmed = True instance.profile.email_confirmed = True
instance.profile.registration_valid = True instance.profile.registration_valid = True
instance.profile.save() instance.profile.save()
def update_wei_registration_fee_on_membership_creation(sender, instance, created, **kwargs):
if created:
from wei.models import WEIRegistration
if instance.club.id == 1 or instance.club.id == 2:
registrations = WEIRegistration.objects.filter(
user=instance.user,
wei__year=instance.date_start.year,
)
for r in registrations:
r.save()
def update_wei_registration_fee_on_club_change(sender, instance, **kwargs):
from wei.models import WEIRegistration
if instance.id == 1 or instance.id == 2:
registrations = WEIRegistration.objects.filter(
wei__year=instance.membership_start.year,
)
for r in registrations:
r.save()

View File

@ -1,11 +1,11 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .registration import WEIForm, WEIRegistrationForm, WEIRegistration1AForm, WEIRegistration2AForm, WEIMembership1AForm, \ from .registration import WEIForm, WEIRegistrationForm, WEIMembership1AForm, \
WEIMembershipForm, BusForm, BusTeamForm WEIMembershipForm, BusForm, BusTeamForm
from .surveys import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, CurrentSurvey from .surveys import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, CurrentSurvey
__all__ = [ __all__ = [
'WEIForm', 'WEIRegistrationForm', 'WEIRegistration1AForm', 'WEIRegistration2AForm', 'WEIMembership1AForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm', 'WEIForm', 'WEIRegistrationForm', 'WEIMembership1AForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm',
'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey', 'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey',
] ]

View File

@ -44,7 +44,7 @@ class WEIRegistrationForm(forms.ModelForm):
fields = [ fields = [
'user', 'soge_credit', 'birth_date', 'gender', 'clothing_size', 'user', 'soge_credit', 'birth_date', 'gender', 'clothing_size',
'health_issues', 'emergency_contact_name', 'emergency_contact_phone', 'health_issues', 'emergency_contact_name', 'emergency_contact_phone',
'first_year', 'information_json', 'deposit_check' 'first_year', 'information_json', 'deposit_check', 'deposit_type'
] ]
widgets = { widgets = {
"user": Autocomplete( "user": Autocomplete(
@ -62,21 +62,8 @@ class WEIRegistrationForm(forms.ModelForm):
"deposit_check": forms.BooleanField( "deposit_check": forms.BooleanField(
required=False, required=False,
), ),
}
class WEIRegistration2AForm(WEIRegistrationForm):
class Meta(WEIRegistrationForm.Meta):
fields = WEIRegistrationForm.Meta.fields + ['deposit_type']
widgets = WEIRegistrationForm.Meta.widgets.copy()
widgets.update({
"deposit_type": forms.RadioSelect(), "deposit_type": forms.RadioSelect(),
}) }
class WEIRegistration1AForm(WEIRegistrationForm):
class Meta(WEIRegistrationForm.Meta):
fields = WEIRegistrationForm.Meta.fields
class WEIChooseBusForm(forms.Form): class WEIChooseBusForm(forms.Form):

View File

@ -0,0 +1,23 @@
# Generated by Django 5.2.4 on 2025-07-19 12:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wei', '0015_remove_weiclub_caution_amount_and_more'),
]
operations = [
migrations.AddField(
model_name='weiregistration',
name='fee',
field=models.PositiveIntegerField(blank=True, default=0, verbose_name='fee'),
),
migrations.AlterField(
model_name='weiclub',
name='fee_soge_credit',
field=models.PositiveIntegerField(default=2000, verbose_name='membership fee (soge credit)'),
),
]

View File

@ -285,6 +285,12 @@ class WEIRegistration(models.Model):
"encoded in JSON"), "encoded in JSON"),
) )
fee = models.PositiveIntegerField(
default=0,
verbose_name=_('fee'),
blank=True,
)
class Meta: class Meta:
unique_together = ('user', 'wei',) unique_together = ('user', 'wei',)
verbose_name = _("WEI User") verbose_name = _("WEI User")
@ -309,7 +315,25 @@ class WEIRegistration(models.Model):
self.information_json = json.dumps(information, indent=2) self.information_json = json.dumps(information, indent=2)
@property @property
def fee(self): def is_validated(self):
try:
return self.membership is not None
except AttributeError:
return False
@property
def validation_status(self):
"""
Define an order to have easier access to validatable registrations
"""
if self.fee + (self.wei.deposit_amount if self.deposit_type == 'note' else 0) > self.user.note.balance:
return 2
elif self.first_year:
return 1
else:
return 0
def calculate_fee(self):
bde = Club.objects.get(pk=1) bde = Club.objects.get(pk=1)
kfet = Club.objects.get(pk=2) kfet = Club.objects.get(pk=2)
@ -336,12 +360,9 @@ class WEIRegistration(models.Model):
return fee return fee
@property def save(self, *args, **kwargs):
def is_validated(self): self.fee = self.calculate_fee()
try: super().save(*args, **kwargs)
return self.membership is not None
except AttributeError:
return False
class WEIMembership(Membership): class WEIMembership(Membership):

View File

@ -58,8 +58,8 @@ class WEIRegistrationTable(tables.Table):
validate = tables.Column( validate = tables.Column(
verbose_name=_("Validate"), verbose_name=_("Validate"),
orderable=False, orderable=True,
accessor=A('pk'), accessor='validate_status',
attrs={ attrs={
'th': { 'th': {
'id': 'validate-membership-header' 'id': 'validate-membership-header'
@ -98,12 +98,13 @@ class WEIRegistrationTable(tables.Table):
if not hasperm: if not hasperm:
return format_html("<span class='no-perm'></span>") return format_html("<span class='no-perm'></span>")
url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) + '?validate=true' url = reverse_lazy('wei:validate_registration', args=(record.pk,))
text = _('Validate') text = _('Validate')
if record.fee > record.user.note.balance and not record.soge_credit: status = record.validation_status
if status == 2:
btn_class = 'btn-secondary' btn_class = 'btn-secondary'
tooltip = _("The user does not have enough money.") tooltip = _("The user does not have enough money.")
elif record.first_year: elif status == 1:
btn_class = 'btn-info' btn_class = 'btn-info'
tooltip = _("The user is in first year. You may validate the credit, the algorithm will run later.") tooltip = _("The user is in first year. You may validate the credit, the algorithm will run later.")
else: else:
@ -121,6 +122,7 @@ class WEIRegistrationTable(tables.Table):
attrs = { attrs = {
'class': 'table table-condensed table-striped table-hover' 'class': 'table table-condensed table-striped table-hover'
} }
order_by = ('validate', 'user',)
model = WEIRegistration model = WEIRegistration
template_name = 'django_tables2/bootstrap4.html' template_name = 'django_tables2/bootstrap4.html'
fields = ('user', 'user__first_name', 'user__last_name', 'first_year', 'deposit_check', fields = ('user', 'user__first_name', 'user__last_name', 'first_year', 'deposit_check',

View File

@ -67,20 +67,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
{% endif %} {% endif %}
{% if history_list.data %}
<div class="card bg-white mb-3">
<div class="card-header position-relative" id="historyListHeading">
<a class="stretched-link font-weight-bold text-decoration-none" {% if "note.view_note"|has_perm:club.note %}
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
</a>
</div>
<div id="history_list">
{% render_table history_list %}
</div>
</div>
{% endif %}
{% if pre_registrations.data %} {% if pre_registrations.data %}
<div class="card bg-white mb-3"> <div class="card bg-white mb-3">
<div class="card-header position-relative" id="historyListHeading"> <div class="card-header position-relative" id="historyListHeading">
@ -99,6 +85,19 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a href="{% url 'wei:wei_1A_list' pk=object.pk %}" class="btn btn-block btn-info">{% trans "Attribute buses" %}</a> <a href="{% url 'wei:wei_1A_list' pk=object.pk %}" class="btn btn-block btn-info">{% trans "Attribute buses" %}</a>
{% endif %} {% endif %}
{% if history_list.data %}
<div class="card bg-white mt-3">
<div class="card-header position-relative" id="historyListHeading">
<a class="stretched-link font-weight-bold text-decoration-none" {% if "note.view_note"|has_perm:club.note %}
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
</a>
</div>
<div id="history_list">
{% render_table history_list %}
</div>
</div>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -13,7 +13,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import transaction from django.db import transaction
from django.db.models import Q, Count from django.db.models import Q, Count, Case, When, Value, IntegerField, F
from django.db.models.functions.text import Lower from django.db.models.functions.text import Lower
from django import forms from django import forms
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
@ -35,7 +35,7 @@ from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms.registration import WEIChooseBusForm from .forms.registration import WEIChooseBusForm
from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam, WEIRole from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam, WEIRole
from .forms import WEIForm, WEIRegistrationForm, WEIRegistration1AForm, WEIRegistration2AForm, BusForm, BusTeamForm, WEIMembership1AForm, \ from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm, WEIMembership1AForm, \
WEIMembershipForm, CurrentSurvey WEIMembershipForm, CurrentSurvey
from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \ from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \
WEIRegistration1ATable, WEIMembershipTable WEIRegistration1ATable, WEIMembershipTable
@ -133,6 +133,23 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, D
membership=None, membership=None,
wei=club wei=club
) )
# Annotate the query to be able to sort registrations on validate status
pre_registrations = pre_registrations.annotate(
deposit=Case(
When(deposit_type='note', then=F('wei__deposit_amount')),
default=Value(0),
output_field=IntegerField()
)
).annotate(
total_fee=F('fee') + F('deposit')
).annotate(
validate_status=Case(
When(total_fee__gt=F('user__note__balance'), then=Value(2)),
When(first_year=True, then=Value(1)),
default=Value(0),
output_field=IntegerField(),
)
)
buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \ buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \
.filter(wei=self.object).annotate(count=Count("memberships")).order_by("name") .filter(wei=self.object).annotate(count=Count("memberships")).order_by("name")
return [club_transactions, club_member, pre_registrations, buses, ] return [club_transactions, club_member, pre_registrations, buses, ]
@ -260,6 +277,23 @@ class WEIRegistrationsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTable
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs).filter(wei=self.club, membership=None).distinct() qs = super().get_queryset(**kwargs).filter(wei=self.club, membership=None).distinct()
# Annotate the query to be able to sort registrations on validate status
qs = qs.annotate(
deposit=Case(
When(deposit_type='note', then=F('wei__deposit_amount')),
default=Value(0),
output_field=IntegerField()
)
).annotate(
total_fee=F('fee') + F('deposit')
).annotate(
validate_status=Case(
When(total_fee__gt=F('user__note__balance'), then=Value(2)),
When(first_year=True, then=Value(1)),
default=Value(0),
output_field=IntegerField(),
)
)
pattern = self.request.GET.get("search", "") pattern = self.request.GET.get("search", "")
@ -510,7 +544,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
Register a new user to the WEI Register a new user to the WEI
""" """
model = WEIRegistration model = WEIRegistration
form_class = WEIRegistration1AForm form_class = WEIRegistrationForm
extra_context = {"title": _("Register first year student to the WEI")} extra_context = {"title": _("Register first year student to the WEI")}
def get_sample_object(self): def get_sample_object(self):
@ -606,7 +640,7 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
Register an old user to the WEI Register an old user to the WEI
""" """
model = WEIRegistration model = WEIRegistration
form_class = WEIRegistration2AForm form_class = WEIRegistrationForm
extra_context = {"title": _("Register old student to the WEI")} extra_context = {"title": _("Register old student to the WEI")}
def get_sample_object(self): def get_sample_object(self):
@ -739,14 +773,11 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
if today >= wei.date_start or today < wei.membership_start: if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,))) return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
# Store the validate parameter in the view's state # Store the validate parameter in the view's state
self.should_validate = request.GET.get('validate', False)
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["club"] = self.object.wei context["club"] = self.object.wei
# Pass the validate parameter to the template
context["should_validate"] = self.should_validate
if self.object.is_validated: if self.object.is_validated:
membership_form = self.get_membership_form(instance=self.object.membership, membership_form = self.get_membership_form(instance=self.object.membership,
@ -788,7 +819,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
if not self.object.first_year and "deposit_type" in form.fields: if not self.object.first_year and "deposit_type" in form.fields:
form.fields["deposit_type"].required = True form.fields["deposit_type"].required = True
form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit")
form.fields["deposit_type"].widget = forms.RadioSelect(choices=form.fields["deposit_type"].choices)
return form return form
@ -862,9 +892,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
survey = CurrentSurvey(self.object) survey = CurrentSurvey(self.object)
if not survey.is_complete(): if not survey.is_complete():
return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk}) return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk})
# On redirige vers la validation uniquement si c'est explicitement demandé (et stocké dans la vue)
if self.should_validate and self.request.user.has_perm("wei.add_weimembership"):
return reverse_lazy("wei:validate_registration", kwargs={"pk": self.object.pk})
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk}) return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk})
@ -963,9 +990,9 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
form = context["form"] form = context["form"]
if registration.soge_credit: if registration.soge_credit:
form.fields["credit_amount"].initial = registration.fee form.fields["credit_amount"].initial = fee
else: else:
form.fields["credit_amount"].initial = max(0, registration.fee - registration.user.note.balance) form.fields["credit_amount"].initial = max(0, fee - registration.user.note.balance)
return context return context
@ -1052,7 +1079,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid
if registration.soge_credit: if registration.soge_credit:
fee = 2000 fee = registration.wei.fee_soge_credit
kfet = club.parent_club kfet = club.parent_club
bde = kfet.parent_club bde = kfet.parent_club