1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2025-06-21 05:18:26 +02:00

Restructure payment model

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello
2024-02-12 22:30:27 +01:00
parent ece128836a
commit 7c9083a6b8
11 changed files with 343 additions and 273 deletions

View File

@ -97,11 +97,12 @@ class VolunteerRegistrationAdmin(PolymorphicChildModelAdmin):
@admin.register(Payment)
class PaymentAdmin(ModelAdmin):
list_display = ('registration', 'registration_type', 'type', 'valid', )
search_fields = ('registration__user__last_name', 'registration__user__first_name', 'registration__user__email',)
list_filter = ('registration__team__participation__valid', 'type', 'type', 'valid',)
autocomplete_fields = ('registration',)
list_display = ('id', 'concerned_people', 'grouped', 'type', 'valid', )
search_fields = ('registrations__user__last_name', 'registrations__user__first_name', 'registrations__user__email',)
list_filter = ('registrations__team__participation__valid', 'type',
'grouped', 'valid', 'registrations__polymorphic_ctype',)
autocomplete_fields = ('registrations',)
@admin.display(description=_('registration type'), ordering='registration__polymorphic_ctype')
def registration_type(self, record: Payment):
return record.registration.get_real_instance().type
@admin.display(description=_('concerned people'))
def concerned_people(self, record: Payment):
return ", ".join(f"{reg.user.first_name} {reg.user.last_name}" for reg in record.registrations.all())

View File

@ -12,11 +12,8 @@ class RegistrationConfig(AppConfig):
name = 'registration'
def ready(self):
from registration.signals import create_admin_registration, create_payment, \
from registration.signals import create_admin_registration, \
set_username, send_email_link
pre_save.connect(set_username, "auth.User")
pre_save.connect(send_email_link, "auth.User")
post_save.connect(create_admin_registration, "auth.User")
post_save.connect(create_payment, "registration.Registration")
post_save.connect(create_payment, "registration.StudentRegistration")
post_save.connect(create_payment, "registration.CoachRegistration")

View File

@ -227,25 +227,25 @@ class PaymentForm(forms.ModelForm):
super().__init__(*args, **kwargs)
self.fields["valid"].widget.choices[0] = ('unknown', _("Pending"))
def clean_scholarship_file(self):
def clean_receipt(self):
print(self.files)
if "scholarship_file" in self.files:
file = self.files["scholarship_file"]
if "receipt" in self.files:
file = self.files["receipt"]
if file.size > 2e6:
raise ValidationError(_("The uploaded file size must be under 2 Mo."))
if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]:
raise ValidationError(_("The uploaded file must be a PDF, PNG of JPEG file."))
return self.cleaned_data["scholarship_file"]
return self.cleaned_data["receipt"]
def clean(self):
cleaned_data = super().clean()
if "type" in cleaned_data and cleaned_data["type"] == "scholarship" \
and "scholarship_file" not in self.files and not self.instance.scholarship_file:
self.add_error("scholarship_file", _("You must upload your scholarship attestation."))
if "type" in cleaned_data and cleaned_data['type'] in ["scholarship", "bank_transfer"] \
and "receipt" not in self.files and not self.instance.scholarship_file:
self.add_error("receipt", _("You must upload your receipt."))
return cleaned_data
class Meta:
model = Payment
fields = ('type', 'scholarship_file', 'additional_information', 'valid',)
fields = ('type', 'receipt', 'additional_information', 'valid',)

View File

@ -0,0 +1,76 @@
# Generated by Django 5.0.1 on 2024-02-12 20:40
import registration.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registration", "0010_coachregistration_last_degree"),
]
operations = [
migrations.RemoveField(
model_name="payment",
name="registration",
),
migrations.RemoveField(
model_name="payment",
name="scholarship_file",
),
migrations.AddField(
model_name="payment",
name="amount",
field=models.PositiveSmallIntegerField(
default=0,
help_text="Corresponds to the total required amount to pay, in euros.",
verbose_name="total amount",
),
),
migrations.AddField(
model_name="payment",
name="checkout_intent_id",
field=models.IntegerField(
blank=True,
default=None,
null=True,
verbose_name="Hello Asso checkout intent ID",
),
),
migrations.AddField(
model_name="payment",
name="final",
field=models.BooleanField(
default=False, verbose_name="for final tournament"
),
),
migrations.AddField(
model_name="payment",
name="grouped",
field=models.BooleanField(
default=False,
help_text="If set to true, then one payment is made for the full team, for example if the school pays for all.",
verbose_name="grouped",
),
),
migrations.AddField(
model_name="payment",
name="receipt",
field=models.FileField(
blank=True,
default="",
help_text="only if you have a scholarship or if you chose a bank transfer.",
upload_to=registration.models.get_scholarship_filename,
verbose_name="receipt",
),
),
migrations.AddField(
model_name="payment",
name="registrations",
field=models.ManyToManyField(
related_name="payments",
to="registration.participantregistration",
verbose_name="registrations",
),
),
]

View File

@ -364,27 +364,28 @@ class StudentRegistration(ParticipantRegistration):
})
if self.team and self.team.participation.valid:
if self.payment.valid is False:
text = _("You have to pay {amount} € for your registration, or send a scholarship "
"notification or a payment proof. "
"You can do it on <a href=\"{url}\">the payment page</a>.")
url = reverse_lazy("registration:update_payment", args=(self.payment.id,))
content = format_lazy(text, amount=self.team.participation.tournament.price, url=url)
informations.append({
'title': _("Payment"),
'type': "danger",
'priority': 3,
'content': content,
})
elif self.payment.valid is None:
text = _("Your payment is under approval.")
content = text
informations.append({
'title': _("Payment"),
'type': "warning",
'priority': 3,
'content': content,
})
for payment in self.payments.all():
if payment.valid is False:
text = _("You have to pay {amount} € for your registration, or send a scholarship "
"notification or a payment proof. "
"You can do it on <a href=\"{url}\">the payment page</a>.")
url = reverse_lazy("registration:update_payment", args=(payment.id,))
content = format_lazy(text, amount=payment.amount, url=url)
informations.append({
'title': _("Payment"),
'type': "danger",
'priority': 3,
'content': content,
})
elif self.payment.valid is None:
text = _("Your payment is under approval.")
content = text
informations.append({
'title': _("Payment"),
'type': "warning",
'priority': 3,
'content': content,
})
return informations
@ -496,11 +497,28 @@ def get_scholarship_filename(instance, filename):
class Payment(models.Model):
registration = models.OneToOneField(
registrations = models.ManyToManyField(
ParticipantRegistration,
on_delete=models.CASCADE,
related_name="payment",
verbose_name=_("registration"),
related_name="payments",
verbose_name=_("registrations"),
)
grouped = models.BooleanField(
verbose_name=_("grouped"),
default=False,
help_text=_("If set to true, then one payment is made for the full team, "
"for example if the school pays for all."),
)
amount = models.PositiveSmallIntegerField(
verbose_name=_("total amount"),
help_text=_("Corresponds to the total required amount to pay, in euros."),
default=0,
)
final = models.BooleanField(
verbose_name=_("for final tournament"),
default=False,
)
type = models.CharField(
@ -518,9 +536,16 @@ class Payment(models.Model):
default="",
)
scholarship_file = models.FileField(
verbose_name=_("scholarship file"),
help_text=_("only if you have a scholarship."),
checkout_intent_id = models.IntegerField(
verbose_name=_("Hello Asso checkout intent ID"),
blank=True,
null=True,
default=None,
)
receipt = models.FileField(
verbose_name=_("receipt"),
help_text=_("only if you have a scholarship or if you chose a bank transfer."),
upload_to=get_scholarship_filename,
blank=True,
default="",
@ -540,10 +565,10 @@ class Payment(models.Model):
)
def get_absolute_url(self):
return reverse_lazy("registration:user_detail", args=(self.registration.user.id,))
return reverse_lazy("registration:update_payment", args=(self.pk,))
def __str__(self):
return _("Payment of {registration}").format(registration=self.registration)
return _("Payment of {registrations}").format(registrations=", ".join(map(str, self.registrations.all())))
class Meta:
verbose_name = _("payment")

View File

@ -4,7 +4,7 @@
from django.contrib.auth.models import User
from tfjm.lists import get_sympa_client
from .models import Payment, Registration, VolunteerRegistration
from .models import Registration, VolunteerRegistration
def set_username(instance, **_):
@ -41,16 +41,3 @@ def create_admin_registration(instance, **_):
"""
if instance.is_superuser:
VolunteerRegistration.objects.get_or_create(user=instance, admin=True)
def create_payment(instance: Registration, raw, **_):
"""
When a user is saved, create the associated payment.
For a free tournament, the payment is valid.
"""
if instance.participates and not raw:
payment = Payment.objects.get_or_create(registration=instance)[0]
if instance.team and instance.team.participation.valid and instance.team.participation.tournament.price == 0:
payment.valid = True
payment.type = "free"
payment.save()

View File

@ -143,35 +143,42 @@
</dl>
{% if user_object.registration.participates and user_object.registration.team.participation.valid %}
<hr>
<dl class="row">
<dt class="col-sm-6 text-end">{% trans "Payment information:" %}</dt>
<dd class="col-sm-6">
{% trans "yes,no,pending" as yesnodefault %}
{% with info=user_object.registration.payment.additional_information %}
{% if info %}
<abbr title="{{ info }}">
{{ user_object.registration.payment.get_type_display }}, {% trans "valid:" %} {{ user_object.registration.payment.valid|yesno:yesnodefault }}
</abbr>
{% else %}
{{ user_object.registration.payment.get_type_display }}, {% trans "valid:" %} {{ user_object.registration.payment.valid|yesno:yesnodefault }}
{% endif %}
{% if user.registration.is_admin or user_object.registration.payment.valid is False %}
<button class="btn-sm btn-secondary" data-bs-toggle="modal" data-bs-target="#updatePaymentModal">
<i class="fas fa-money-bill-wave"></i> {% trans "Update payment" %}
</button>
{% endif %}
{% if user_object.registration.payment.type == "scholarship" %}
{% if user.registration.is_admin or user == user_object %}
<a href="{{ user_object.registration.payment.scholarship_file.url }}" class="btn btn-info">
<i class="fas fa-file-pdf"></i> {% trans "Download scholarship attestation" %}
{% for payment in user_object.registration.payments.all %}
<hr>
<dl class="row">
<dt class="col-sm-6 text-end">{% trans "Payment information:" %}</dt>
<dd class="col-sm-6">
{% trans "yes,no,pending" as yesnodefault %}
{% with info=payment.additional_information %}
{% if info %}
<abbr title="{{ info }}">
{{ payment.get_type_display }}, {% trans "valid:" %} {{ payment.valid|yesno:yesnodefault }}
</abbr>
{% else %}
{{ payment.get_type_display }}, {% trans "valid:" %} {{ payment.valid|yesno:yesnodefault }}
{% endif %}
{% if user.registration.is_admin or payment.valid is False %}
<a href="{% url "registration:update_payment" pk=payment.pk %}" class="btn btn-secondary">
<i class="fas fa-money-bill-wave"></i> {% trans "Update payment" %}
</a>
{% endif %}
{% endif %}
{% endwith %}
</dd>
</dl>
{% if payment.type == "scholarship" or payment.type == "bank_transfer" %}
{% if user.registration.is_admin or user == user_object %}
<a href="{{ payment.receipt.url }}" class="btn btn-info">
<i class="fas fa-file-pdf"></i>
{% if payment.type == "scholarship" %}
{% trans "Download scholarship attestation" %}
{% elif payment.type == "bank_transfer" %}
{% trans "Download bank transfer receipt" %}
{% endif %}
</a>
{% endif %}
{% endif %}
{% endwith %}
</dd>
</dl>
{% endfor %}
{% endif %}
</div>
{% if user.pk == user_object.pk or user.registration.is_admin %}
@ -210,13 +217,6 @@
{% url "registration:upload_user_parental_authorization" pk=user_object.registration.pk as modal_action %}
{% include "base_modal.html" with modal_id="uploadParentalAuthorization" modal_enctype="multipart/form-data" %}
{% endif %}
{% if user_object.registration.team.participation.valid %}
{% trans "Update payment" as modal_title %}
{% trans "Update" as modal_button %}
{% url "registration:update_payment" pk=user_object.registration.payment.pk as modal_action %}
{% include "base_modal.html" with modal_id="updatePayment" modal_additional_class="modal-xl" modal_enctype="multipart/form-data" %}
{% endif %}
{% endblock %}
{% block extrajavascript %}
@ -228,10 +228,6 @@
initModal("uploadVaccineSheet", "{% url "registration:upload_user_vaccine_sheet" pk=user_object.registration.pk %}")
initModal("uploadParentalAuthorization", "{% url "registration:upload_user_parental_authorization" pk=user_object.registration.pk %}")
{% endif %}
{% if user_object.registration.team.participation.valid %}
initModal("updatePayment", "{% url "registration:update_payment" pk=user_object.registration.payment.pk %}")
{% endif %}
});
</script>
{% endblock %}

View File

@ -448,7 +448,7 @@ class PaymentUpdateView(LoginRequiredMixin, UpdateView):
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated or \
not self.request.user.registration.is_admin \
and (self.request.user != self.get_object().registration.user
and (self.request.user.registration not in self.get_object().registrations.all()
or self.get_object().valid is not False):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
@ -464,8 +464,8 @@ class PaymentUpdateView(LoginRequiredMixin, UpdateView):
if not self.request.user.registration.is_admin:
form.instance.valid = None
old_instance = Payment.objects.get(pk=self.object.pk)
if old_instance.scholarship_file:
old_instance.scholarship_file.delete()
if old_instance.receipt:
old_instance.receipt.delete()
old_instance.save()
return super().form_valid(form)
@ -568,12 +568,12 @@ class ScholarshipView(LoginRequiredMixin, View):
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
path = f"media/authorization/scholarship/{filename}"
path = f"media/authorization/receipt/{filename}"
if not os.path.exists(path):
raise Http404
payment = Payment.objects.get(scholarship_file__endswith=filename)
payment = Payment.objects.get(receipt__endswith=filename)
user = request.user
if not (payment.registration.user == user or user.registration.is_admin):
if not (user.registration in payment.registrations or user.registration.is_admin):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)