mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Ajout des Bundles de fichiers [#24]
This commit is contained in:
@ -161,6 +161,13 @@ class Student(models.Model):
|
||||
return self.birth_date.strftime('%d-%m-%Y')
|
||||
return None
|
||||
|
||||
class RegistrationFileGroup(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def registration_file_path(instance, filename):
|
||||
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
|
||||
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
|
||||
@ -196,6 +203,11 @@ class RegistrationForm(models.Model):
|
||||
|
||||
# Many-to-Many Relationship
|
||||
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
|
||||
fileGroup = models.ForeignKey(RegistrationFileGroup,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='file_group',
|
||||
null=True,
|
||||
blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
||||
@ -209,6 +221,7 @@ class RegistrationFileTemplate(models.Model):
|
||||
order = models.PositiveIntegerField(default=0) # Ajout du champ order
|
||||
date_added = models.DateTimeField(auto_now_add=True)
|
||||
is_required = models.BooleanField(default=False)
|
||||
group = models.ForeignKey(RegistrationFileGroup, on_delete=models.CASCADE, related_name='file_templates')
|
||||
|
||||
@property
|
||||
def formatted_date_added(self):
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from rest_framework import serializers
|
||||
from .models import RegistrationFileTemplate, RegistrationFile, RegistrationForm, Student, Guardian, Sibling, Language
|
||||
from .models import RegistrationFileTemplate, RegistrationFile, RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language
|
||||
from School.models import SchoolClass, Fee, Discount, FeeType
|
||||
from School.serializers import FeeSerializer, DiscountSerializer
|
||||
from Auth.models import Profile
|
||||
@ -11,6 +11,11 @@ from django.utils import timezone
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
|
||||
class RegistrationFileGroupSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = RegistrationFileGroup
|
||||
fields = '__all__'
|
||||
|
||||
class RegistrationFileSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = RegistrationFile
|
||||
|
||||
@ -3,95 +3,205 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ pdf_title }}</title>
|
||||
<style type="text/css">
|
||||
<style>
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 2cm;
|
||||
}
|
||||
body {
|
||||
font-weight: 200;
|
||||
font-size: 14px;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 11pt;
|
||||
line-height: 1.3;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color:#333;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
.header {
|
||||
font-size: 20px;
|
||||
font-weight: 100;
|
||||
text-align: center;
|
||||
color: #007cae;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.title {
|
||||
font-size: 22px;
|
||||
font-weight: 100;
|
||||
/* text-align: right;*/
|
||||
padding: 10px 20px 0px 20px;
|
||||
font-size: 18pt;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.title span {
|
||||
color: #007cae;
|
||||
.section {
|
||||
margin-bottom: 15px; /* Réduit de 25px à 15px */
|
||||
padding: 10px; /* Réduit de 15px à 10px */
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.details {
|
||||
padding: 10px 20px 0px 20px;
|
||||
text-align: left !important;
|
||||
/*margin-left: 40%;*/
|
||||
.section-title {
|
||||
font-size: 16pt;
|
||||
font-weight: bold;
|
||||
margin: 0 0 10px 0; /* Réduit de 15px à 10px */
|
||||
padding-bottom: 3px; /* Réduit de 5px à 3px */
|
||||
border-bottom: 1px solid #333;
|
||||
text-align: center;
|
||||
}
|
||||
.hrItem {
|
||||
border: none;
|
||||
height: 1px;
|
||||
/* Set the hr color */
|
||||
color: #333; /* old IE */
|
||||
background-color: #fff; /* Modern Browsers */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
td {
|
||||
padding: 4px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.label-cell {
|
||||
font-weight: bold;
|
||||
width: 150px;
|
||||
}
|
||||
.value-cell {
|
||||
width: auto;
|
||||
}
|
||||
.half-row td {
|
||||
width: 50%;
|
||||
}
|
||||
.subsection-title {
|
||||
font-size: 12pt;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px; /* Réduit de 10px à 5px */
|
||||
}
|
||||
.signature {
|
||||
margin-top: 30px;
|
||||
text-align: right;
|
||||
font-style: italic;
|
||||
}
|
||||
.signature-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
.clearfix::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
p {
|
||||
margin: 0; /* Ajout de margin 0 pour les paragraphes */
|
||||
padding: 0; /* Ajout de padding 0 pour les paragraphes */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% load myTemplateTag %}
|
||||
<div class='wrapper'>
|
||||
<div class='header'>
|
||||
<p class='title'>{{ pdf_title }}</p>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">{{ pdf_title }}</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div class='details'>
|
||||
Signé le : <b>{{ signatureDate }}</b> <br/>
|
||||
A : <b>{{ signatureTime }}</b>
|
||||
<hr class='hrItem' />
|
||||
<h1>ELEVE</h1>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">ÉLÈVE</h2>
|
||||
{% with level=student|getStudentLevel %}
|
||||
{% with gender=student|getStudentGender %}
|
||||
NOM : <b>{{ student.last_name }}</b> <br/>
|
||||
PRENOM : <b>{{ student.first_name }}</b> <br/>
|
||||
ADRESSE : <b>{{ student.address }}</b> <br/>
|
||||
GENRE : <b>{{ gender }}</b> <br/>
|
||||
NE(E) LE : <b>{{ student.birth_date }}</b> <br/>
|
||||
A : <b>{{ student.birth_place }} ({{ student.birth_postal_code }})</b> <br/>
|
||||
NATIONALITE : <b>{{ student.nationality }}</b> <br/>
|
||||
NIVEAU : <b>{{ level }}</b> <br/>
|
||||
MEDECIN TRAITANT : <b>{{ student.attending_physician }}</b> <br/>
|
||||
<table>
|
||||
<tr class="half-row">
|
||||
<td class="label-cell">NOM :</td>
|
||||
<td>{{ student.last_name }}</td>
|
||||
<td class="label-cell">PRÉNOM :</td>
|
||||
<td>{{ student.first_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">ADRESSE :</td>
|
||||
<td colspan="3">{{ student.address }}</td>
|
||||
</tr>
|
||||
<tr class="half-row">
|
||||
<td class="label-cell">GENRE :</td>
|
||||
<td>{{ gender }}</td>
|
||||
<td class="label-cell">NÉ(E) LE :</td>
|
||||
<td>{{ student.birth_date }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">À :</td>
|
||||
<td colspan="3">{{ student.birth_place }} ({{ student.birth_postal_code }})</td>
|
||||
</tr>
|
||||
<tr class="half-row">
|
||||
<td class="label-cell">NATIONALITÉ :</td>
|
||||
<td>{{ student.nationality }}</td>
|
||||
<td class="label-cell">NIVEAU :</td>
|
||||
<td>{{ level }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">MÉDECIN TRAITANT :</td>
|
||||
<td colspan="3">{{ student.attending_physician }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
<hr class='hrItem' />
|
||||
<h1>RESPONSABLES</h1>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">RESPONSABLES</h2>
|
||||
{% with guardians=student.getGuardians %}
|
||||
{% with siblings=student.getGuardians %}
|
||||
{% for guardian in guardians%}
|
||||
<h2>Guardian {{ forloop.counter }}</h2>
|
||||
NOM : <b>{{ guardian.last_name }}</b> <br/>
|
||||
PRENOM : <b>{{ guardian.first_name }}</b> <br/>
|
||||
ADRESSE : <b>{{ guardian.address }}</b> <br/>
|
||||
NE(E) LE : <b>{{ guardian.birth_date }}</b> <br/>
|
||||
MAIL : <b>{{ guardian.email }}</b> <br/>
|
||||
TEL : <b>{{ guardian.phone }}</b> <br/>
|
||||
PROFESSION : <b>{{ guardian.profession }}</b> <br/>
|
||||
<div class="subsection">
|
||||
<h3 class="subsection-title">Responsable {{ forloop.counter }}</h3>
|
||||
<table>
|
||||
<tr class="half-row">
|
||||
<td class="label-cell">NOM :</td>
|
||||
<td>{{ guardian.last_name }}</td>
|
||||
<td class="label-cell">PRÉNOM :</td>
|
||||
<td>{{ guardian.first_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">ADRESSE :</td>
|
||||
<td colspan="3">{{ guardian.address }}</td>
|
||||
</tr>
|
||||
<tr class="half-row">
|
||||
<td class="label-cell">NÉ(E) LE :</td>
|
||||
<td>{{ guardian.birth_date }}</td>
|
||||
<td class="label-cell">EMAIL :</td>
|
||||
<td>{{ guardian.email }}</td>
|
||||
</tr>
|
||||
<tr class="half-row">
|
||||
<td class="label-cell">TÉLÉPHONE :</td>
|
||||
<td>{{ guardian.phone }}</td>
|
||||
<td class="label-cell">PROFESSION :</td>
|
||||
<td>{{ guardian.profession }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<hr class='hrItem' />
|
||||
<h1>FRATRIE</h1>
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">FRATRIE</h2>
|
||||
{% with siblings=student.getGuardians %}
|
||||
{% for sibling in siblings%}
|
||||
<h2>Frère - Soeur {{ forloop.counter }}</h2>
|
||||
NOM : <b>{{ sibling.last_name }}</b> <br/>
|
||||
PRENOM : <b>{{ sibling.first_name }}</b> <br/>
|
||||
NE(E) LE : <b>{{ sibling.birth_date }}</b> <br/>
|
||||
<div class="subsection">
|
||||
<h3 class="subsection-title">Frère/Sœur {{ forloop.counter }}</h3>
|
||||
<table>
|
||||
<tr class="half-row">
|
||||
<td class="label-cell">NOM :</td>
|
||||
<td>{{ sibling.last_name }}</td>
|
||||
<td class="label-cell">PRÉNOM :</td>
|
||||
<td>{{ sibling.first_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">NÉ(E) LE :</td>
|
||||
<td colspan="3">{{ sibling.birth_date }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<hr class='hrItem' />
|
||||
<h1>MODALITES DE PAIEMENT</h1>
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">MODALITÉS DE PAIEMENT</h2>
|
||||
{% with paymentMethod=student|getStudentPaymentMethod %}
|
||||
<b>{{ paymentMethod }}</b> <br/>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
<p>{{ paymentMethod }}</p>
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<div class="signature">
|
||||
Fait le <span class="signature-text">{{ signatureDate }}</span> à <span class="signature-text">{{ signatureTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,41 +1,41 @@
|
||||
from django.urls import path, re_path
|
||||
|
||||
from . import views
|
||||
from .views import RegistrationFileTemplateView, RegisterFormListView, RegisterFormView, StudentView, GuardianView, ChildrenListView, StudentListView, RegistrationFileView
|
||||
|
||||
# RF
|
||||
from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archive
|
||||
# SubClasses
|
||||
from .views import StudentView, GuardianView, ChildrenListView, StudentListView
|
||||
# Files
|
||||
from .views import RegistrationFileTemplateView, RegistrationFileTemplateSimpleView, RegistrationFileView, RegistrationFileSimpleView
|
||||
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^registerForms/(?P<_filter>[a-zA-z]+)$', RegisterFormListView.as_view(), name="registerForms"),
|
||||
|
||||
re_path(r'^registerForm$', RegisterFormView.as_view(), name="registerForm"),
|
||||
re_path(r'^registerForm/(?P<_id>[0-9]+)$', RegisterFormView.as_view(), name="registerForm"),
|
||||
|
||||
# Page de formulaire d'inscription - ELEVE
|
||||
re_path(r'^student/(?P<_id>[0-9]+)$', StudentView.as_view(), name="students"),
|
||||
|
||||
# Page de formulaire d'inscription - RESPONSABLE
|
||||
re_path(r'^lastGuardian$', GuardianView.as_view(), name="lastGuardian"),
|
||||
|
||||
# Envoi d'un dossier d'inscription
|
||||
re_path(r'^send/(?P<_id>[0-9]+)$', views.send, name="send"),
|
||||
|
||||
# Archivage d'un dossier d'inscription
|
||||
re_path(r'^archive/(?P<_id>[0-9]+)$', views.archive, name="archive"),
|
||||
|
||||
# Envoi d'une relance de dossier d'inscription
|
||||
re_path(r'^sendRelance/(?P<_id>[0-9]+)$', views.relance, name="sendRelance"),
|
||||
|
||||
# Page PARENT - Liste des children
|
||||
re_path(r'^children/(?P<_id>[0-9]+)$', ChildrenListView.as_view(), name="children"),
|
||||
re_path(r'^registerForms/(?P<id>[0-9]+)/archive$', archive, name="archive"),
|
||||
re_path(r'^registerForms/(?P<id>[0-9]+)/resend$', resend, name="resend"),
|
||||
re_path(r'^registerForms/(?P<id>[0-9]+)/send$', send, name="send"),
|
||||
re_path(r'^registerForms/(?P<id>[0-9]+)$', RegisterFormWithIdView.as_view(), name="registerForm"),
|
||||
re_path(r'^registerForms$', RegisterFormView.as_view(), name="registerForms"),
|
||||
|
||||
# Page INSCRIPTION - Liste des élèves
|
||||
re_path(r'^students$', StudentListView.as_view(), name="students"),
|
||||
# Page de formulaire d'inscription - ELEVE
|
||||
re_path(r'^students/(?P<id>[0-9]+)$', StudentView.as_view(), name="students"),
|
||||
# Page PARENT - Liste des children
|
||||
re_path(r'^children/(?P<id>[0-9]+)$', ChildrenListView.as_view(), name="children"),
|
||||
|
||||
# Page de formulaire d'inscription - RESPONSABLE
|
||||
re_path(r'^lastGuardianId$', GuardianView.as_view(), name="lastGuardianId"),
|
||||
|
||||
# modèles de fichiers d'inscription
|
||||
re_path(r'^registrationFileTemplates/(?P<id>[0-9]+)$', RegistrationFileTemplateSimpleView.as_view(), name="registrationFileTemplate"),
|
||||
re_path(r'^registrationFileTemplates$', RegistrationFileTemplateView.as_view(), name='registrationFileTemplates'),
|
||||
re_path(r'^registrationFileTemplates/(?P<_id>[0-9]+)$', RegistrationFileTemplateView.as_view(), name="registrationFileTemplate"),
|
||||
|
||||
# fichiers d'inscription
|
||||
re_path(r'^registrationFiles/(?P<_id>[0-9]+)$', RegistrationFileView.as_view(), name='registrationFiles'),
|
||||
re_path(r'^registrationFiles', RegistrationFileView.as_view(), name="registrationFiles"),
|
||||
re_path(r'^registrationFiles/(?P<id>[0-9]+)$', RegistrationFileSimpleView.as_view(), name='registrationFiles'),
|
||||
re_path(r'^registrationFiles$', RegistrationFileView.as_view(), name="registrationFiles"),
|
||||
|
||||
re_path(r'^registrationFileGroups/(?P<id>[0-9]+)$', RegistrationFileGroupSimpleView.as_view(), name='registrationFileGroupDetail'),
|
||||
re_path(r'^registrationFileGroups/(?P<id>[0-9]+)/registrationFiles$', get_registration_files_by_group, name="get_registration_files_by_group"),
|
||||
re_path(r'^registrationFileGroups$', RegistrationFileGroupView.as_view(), name='registrationFileGroups'),
|
||||
]
|
||||
@ -1,450 +0,0 @@
|
||||
from django.http.response import JsonResponse
|
||||
from django.contrib.auth import login, authenticate, get_user_model
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.core.cache import cache
|
||||
from django.core.paginator import Paginator
|
||||
from django.core.files import File
|
||||
from django.db.models import Q # Ajout de cet import
|
||||
from rest_framework.parsers import JSONParser,MultiPartParser, FormParser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg import openapi
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
import Subscriptions.mailManager as mailer
|
||||
import Subscriptions.util as util
|
||||
|
||||
from Subscriptions.automate import Automate_RF_Register, load_config, getStateMachineObjectState, updateStateMachine
|
||||
from .serializers import RegistrationFormSerializer, StudentSerializer, RegistrationFormByParentSerializer, StudentByRFCreationSerializer, RegistrationFileSerializer, RegistrationFileTemplateSerializer, RegistrationFormByParentSerializer, StudentByRFCreationSerializer
|
||||
from .pagination import CustomPagination
|
||||
from .signals import clear_cache
|
||||
from .models import Student, Guardian, RegistrationForm, RegistrationFileTemplate, RegistrationFile
|
||||
from .automate import Automate_RF_Register, load_config, getStateMachineObjectState, updateStateMachine
|
||||
|
||||
from Auth.models import Profile
|
||||
|
||||
from N3wtSchool import settings, renderers, bdd
|
||||
|
||||
class RegisterFormListView(APIView):
|
||||
"""
|
||||
Gère la liste des dossiers d’inscription, lecture et création.
|
||||
"""
|
||||
pagination_class = CustomPagination
|
||||
|
||||
def get_register_form(self, _filter, search=None):
|
||||
"""
|
||||
Récupère les fiches d'inscriptions en fonction du filtre passé.
|
||||
_filter: Filtre pour déterminer l'état des fiches ('pending', 'archived', 'subscribed')
|
||||
search: Terme de recherche (optionnel)
|
||||
"""
|
||||
if _filter == 'pending':
|
||||
exclude_states = [RegistrationForm.RegistrationFormStatus.RF_VALIDATED, RegistrationForm.RegistrationFormStatus.RF_ARCHIVED]
|
||||
return bdd.searchObjects(RegistrationForm, search, _excludeStates=exclude_states)
|
||||
elif _filter == 'archived':
|
||||
return bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_ARCHIVED)
|
||||
elif _filter == 'subscribed':
|
||||
return bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_VALIDATED)
|
||||
return None
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[
|
||||
openapi.Parameter('_filter', openapi.IN_PATH, description="filtre", type=openapi.TYPE_STRING, enum=['pending', 'archived', 'subscribed'], required=True),
|
||||
openapi.Parameter('search', openapi.IN_QUERY, description="search", type=openapi.TYPE_STRING, required=False),
|
||||
openapi.Parameter('page_size', openapi.IN_QUERY, description="limite de page lors de la pagination", type=openapi.TYPE_INTEGER, required=False),
|
||||
],
|
||||
responses={200: RegistrationFormSerializer(many=True)}
|
||||
)
|
||||
def get(self, request, _filter):
|
||||
"""
|
||||
Récupère les fiches d'inscriptions en fonction du filtre passé.
|
||||
"""
|
||||
# Récupération des paramètres
|
||||
search = request.GET.get('search', '').strip()
|
||||
page_size = request.GET.get('page_size', None)
|
||||
|
||||
# Gestion du page_size
|
||||
if page_size is not None:
|
||||
try:
|
||||
page_size = int(page_size)
|
||||
except ValueError:
|
||||
page_size = settings.NB_RESULT_PER_PAGE
|
||||
|
||||
# Définir le cache_key en fonction du filtre
|
||||
page_number = request.GET.get('page', 1)
|
||||
cache_key = f'N3WT_ficheInscriptions_{_filter}_page_{page_number}_search_{search if _filter == "pending" else ""}'
|
||||
cached_page = cache.get(cache_key)
|
||||
if cached_page:
|
||||
return JsonResponse(cached_page, safe=False)
|
||||
|
||||
# Récupérer les fiches d'inscriptions en fonction du filtre
|
||||
registerForms_List = self.get_register_form(_filter, search)
|
||||
|
||||
if not registerForms_List:
|
||||
return JsonResponse({'error' : 'aucune donnée trouvée', 'count' :0}, safe=False)
|
||||
|
||||
# Pagination
|
||||
paginator = self.pagination_class()
|
||||
page = paginator.paginate_queryset(registerForms_List, request)
|
||||
if page is not None:
|
||||
registerForms_serializer = RegistrationFormSerializer(page, many=True)
|
||||
response_data = paginator.get_paginated_response(registerForms_serializer.data)
|
||||
cache.set(cache_key, response_data, timeout=60*15)
|
||||
return JsonResponse(response_data, safe=False)
|
||||
|
||||
return JsonResponse({'error' : 'aucune donnée trouvée', 'count' :0}, safe=False)
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[
|
||||
],
|
||||
responses={200: RegistrationFormSerializer(many=True)}
|
||||
)
|
||||
def post(self, request):
|
||||
studentFormList_serializer=JSONParser().parse(request)
|
||||
for studentForm_data in studentFormList_serializer:
|
||||
# Ajout de la date de mise à jour
|
||||
studentForm_data["last_update"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
|
||||
json.dumps(studentForm_data)
|
||||
# Ajout du code d'inscription
|
||||
code = util.genereRandomCode(12)
|
||||
studentForm_data["codeLienInscription"] = code
|
||||
studentForm_serializer = RegistrationFormSerializer(data=studentForm_data)
|
||||
|
||||
if studentForm_serializer.is_valid():
|
||||
studentForm_serializer.save()
|
||||
|
||||
return JsonResponse(studentForm_serializer.errors, safe=False)
|
||||
|
||||
|
||||
@method_decorator(csrf_protect, name='dispatch')
|
||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||
class RegisterFormView(APIView):
|
||||
"""
|
||||
Gère la lecture, création, modification et suppression d’un dossier d’inscription.
|
||||
"""
|
||||
pagination_class = CustomPagination
|
||||
|
||||
def get(self, request, _id):
|
||||
"""
|
||||
Récupère un dossier d'inscription donné.
|
||||
"""
|
||||
registerForm=bdd.getObject(RegistrationForm, "student__id", _id)
|
||||
registerForm_serializer=RegistrationFormSerializer(registerForm)
|
||||
return JsonResponse(registerForm_serializer.data, safe=False)
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
Crée un dossier d'inscription.
|
||||
"""
|
||||
studentForm_data=JSONParser().parse(request)
|
||||
# Ajout de la date de mise à jour
|
||||
studentForm_data["last_update"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
|
||||
json.dumps(studentForm_data)
|
||||
# Ajout du code d'inscription
|
||||
code = util.genereRandomCode(12)
|
||||
studentForm_data["codeLienInscription"] = code
|
||||
|
||||
guardiansId = studentForm_data.pop('idGuardians', [])
|
||||
studentForm_serializer = RegistrationFormSerializer(data=studentForm_data)
|
||||
|
||||
if studentForm_serializer.is_valid():
|
||||
di = studentForm_serializer.save()
|
||||
|
||||
# Mise à jour de l'automate
|
||||
updateStateMachine(di, 'creationDI')
|
||||
|
||||
# Récupération du reponsable associé
|
||||
for guardianId in guardiansId:
|
||||
guardian = Guardian.objects.get(id=guardianId)
|
||||
di.student.guardians.add(guardian)
|
||||
di.save()
|
||||
|
||||
return JsonResponse(studentForm_serializer.data, safe=False)
|
||||
|
||||
return JsonResponse(studentForm_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def put(self, request, _id):
|
||||
"""
|
||||
Modifie un dossier d'inscription donné.
|
||||
"""
|
||||
studentForm_data=JSONParser().parse(request)
|
||||
_status = studentForm_data.pop('status', 0)
|
||||
studentForm_data["last_update"] = str(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'))
|
||||
registerForm = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=_id)
|
||||
|
||||
if _status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW:
|
||||
try:
|
||||
# Génération de la fiche d'inscription au format PDF
|
||||
base_dir = f"registration_files/dossier_rf_{registerForm.pk}"
|
||||
os.makedirs(base_dir, exist_ok=True)
|
||||
|
||||
# Fichier PDF initial
|
||||
initial_pdf = f"{base_dir}/rf_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf"
|
||||
registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
|
||||
registerForm.save()
|
||||
|
||||
# Récupération des fichiers d'inscription
|
||||
fileNames = RegistrationFile.get_files_from_rf(registerForm.pk)
|
||||
if registerForm.registration_file:
|
||||
fileNames.insert(0, registerForm.registration_file.path)
|
||||
|
||||
# Création du fichier PDF Fusionné
|
||||
merged_pdf = f"{base_dir}/dossier_complet_{registerForm.pk}.pdf"
|
||||
util.merge_files_pdf(fileNames, merged_pdf)
|
||||
|
||||
# Mise à jour du champ registration_file avec le fichier fusionné
|
||||
with open(merged_pdf, 'rb') as f:
|
||||
registerForm.registration_file.save(
|
||||
os.path.basename(merged_pdf),
|
||||
File(f),
|
||||
save=True
|
||||
)
|
||||
|
||||
# Mise à jour de l'automate
|
||||
updateStateMachine(registerForm, 'saisiDI')
|
||||
except Exception as e:
|
||||
return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
elif _status == RegistrationForm.RegistrationFormStatus.RF_VALIDATED:
|
||||
# L'école a validé le dossier d'inscription
|
||||
# Mise à jour de l'automate
|
||||
updateStateMachine(registerForm, 'valideDI')
|
||||
|
||||
|
||||
studentForm_serializer = RegistrationFormSerializer(registerForm, data=studentForm_data)
|
||||
if studentForm_serializer.is_valid():
|
||||
studentForm_serializer.save()
|
||||
return JsonResponse(studentForm_serializer.data, safe=False)
|
||||
|
||||
return JsonResponse(studentForm_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, id):
|
||||
"""
|
||||
Supprime un dossier d'inscription donné.
|
||||
"""
|
||||
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
|
||||
if register_form != None:
|
||||
student = register_form.student
|
||||
student.guardians.clear()
|
||||
student.profiles.clear()
|
||||
student.registration_files.clear()
|
||||
student.delete()
|
||||
clear_cache()
|
||||
|
||||
return JsonResponse("La suppression du dossier a été effectuée avec succès", safe=False)
|
||||
|
||||
return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
class StudentView(APIView):
|
||||
"""
|
||||
Gère la lecture d’un élève donné.
|
||||
"""
|
||||
def get(self, request, _id):
|
||||
student = bdd.getObject(_objectName=Student, _columnName='id', _value=_id)
|
||||
if student is None:
|
||||
return JsonResponse({"errorMessage":'Aucun élève trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
student_serializer = StudentSerializer(student)
|
||||
return JsonResponse(student_serializer.data, safe=False)
|
||||
|
||||
class GuardianView(APIView):
|
||||
"""
|
||||
Récupère le dernier ID de responsable légal créé.
|
||||
"""
|
||||
def get(self, request):
|
||||
lastGuardian = bdd.getLastId(Guardian)
|
||||
return JsonResponse({"lastid":lastGuardian}, safe=False)
|
||||
|
||||
def send(request, _id):
|
||||
"""
|
||||
Envoie le dossier d’inscription par e-mail.
|
||||
"""
|
||||
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=_id)
|
||||
if register_form != None:
|
||||
student = register_form.student
|
||||
guardian = student.getMainGuardian()
|
||||
email = guardian.email
|
||||
errorMessage = mailer.sendRegisterForm(email)
|
||||
if errorMessage == '':
|
||||
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
|
||||
# Mise à jour de l'automate
|
||||
updateStateMachine(register_form, 'envoiDI')
|
||||
return JsonResponse({"message": f"Le dossier d'inscription a bien été envoyé à l'addresse {email}"}, safe=False)
|
||||
|
||||
return JsonResponse({"errorMessage":errorMessage}, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def archive(request, _id):
|
||||
"""
|
||||
Archive le dossier d’inscription visé.
|
||||
"""
|
||||
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=_id)
|
||||
if register_form != None:
|
||||
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
|
||||
# Mise à jour de l'automate
|
||||
updateStateMachine(register_form, 'archiveDI')
|
||||
|
||||
return JsonResponse({"errorMessage":''}, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def relance(request, _id):
|
||||
"""
|
||||
Relance un dossier d’inscription par e-mail.
|
||||
"""
|
||||
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=_id)
|
||||
if register_form != None:
|
||||
student = register_form.student
|
||||
guardian = student.getMainGuardian()
|
||||
email = guardian.email
|
||||
errorMessage = mailer.envoieRelanceDossierInscription(email, register_form.codeLienInscription)
|
||||
if errorMessage == '':
|
||||
register_form.status=RegistrationForm.RegistrationFormStatus.RF_SENT
|
||||
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
|
||||
register_form.save()
|
||||
|
||||
return JsonResponse({"errorMessage":errorMessage}, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# API utilisée pour la vue parent
|
||||
class ChildrenListView(APIView):
|
||||
"""
|
||||
Pour la vue parent : liste les élèves rattachés à un profil donné.
|
||||
"""
|
||||
# Récupération des élèves d'un parent
|
||||
# idProfile : identifiant du profil connecté rattaché aux fiches d'élèves
|
||||
def get(self, request, _id):
|
||||
students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__associated_profile__id', _value=_id)
|
||||
students_serializer = RegistrationFormByParentSerializer(students, many=True)
|
||||
return JsonResponse(students_serializer.data, safe=False)
|
||||
|
||||
# API utilisée pour la vue de création d'un DI
|
||||
class StudentListView(APIView):
|
||||
"""
|
||||
Pour la vue de création d’un dossier d’inscription : liste les élèves disponibles.
|
||||
"""
|
||||
# Récupération de la liste des élèves inscrits ou en cours d'inscriptions
|
||||
def get(self, request):
|
||||
students = bdd.getAllObjects(_objectName=Student)
|
||||
students_serializer = StudentByRFCreationSerializer(students, many=True)
|
||||
return JsonResponse(students_serializer.data, safe=False)
|
||||
|
||||
class RegistrationFileTemplateView(APIView):
|
||||
"""
|
||||
Gère les fichiers templates pour les dossiers d’inscription.
|
||||
"""
|
||||
parser_classes = (MultiPartParser, FormParser)
|
||||
|
||||
def get(self, request, _id=None):
|
||||
"""
|
||||
Récupère les fichiers templates pour les dossiers d’inscription.
|
||||
"""
|
||||
if _id is None:
|
||||
files = RegistrationFileTemplate.objects.all()
|
||||
serializer = RegistrationFileTemplateSerializer(files, many=True)
|
||||
return Response(serializer.data)
|
||||
else :
|
||||
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=_id)
|
||||
if registationFileTemplate is None:
|
||||
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileTemplateSerializer(registationFileTemplate)
|
||||
return JsonResponse(serializer.data, safe=False)
|
||||
|
||||
def put(self, request, _id):
|
||||
"""
|
||||
Met à jour un fichier template existant.
|
||||
"""
|
||||
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=_id)
|
||||
if registationFileTemplate is None:
|
||||
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileTemplateSerializer(registationFileTemplate,data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
Crée un fichier template pour les dossiers d’inscription.
|
||||
"""
|
||||
serializer = RegistrationFileTemplateSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, _id):
|
||||
"""
|
||||
Supprime un fichier template existant.
|
||||
"""
|
||||
registrationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=_id)
|
||||
if registrationFileTemplate is not None:
|
||||
registrationFileTemplate.file.delete() # Supprimer le fichier uploadé
|
||||
registrationFileTemplate.delete()
|
||||
return JsonResponse({'message': 'La suppression du fichier d\'inscription a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||
else:
|
||||
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
class RegistrationFileView(APIView):
|
||||
"""
|
||||
Gère la création, mise à jour et suppression de fichiers liés à un dossier d’inscription.
|
||||
"""
|
||||
parser_classes = (MultiPartParser, FormParser)
|
||||
|
||||
def get(self, request, _id=None):
|
||||
"""
|
||||
Récupère les fichiers liés à un dossier d’inscription donné.
|
||||
"""
|
||||
if (_id is None):
|
||||
files = RegistrationFile.objects.all()
|
||||
serializer = RegistrationFileSerializer(files, many=True)
|
||||
return Response(serializer.data)
|
||||
else:
|
||||
registationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=_id)
|
||||
if registationFile is None:
|
||||
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileSerializer(registationFile)
|
||||
return JsonResponse(serializer.data, safe=False)
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
Crée un RegistrationFile pour le RegistrationForm associé.
|
||||
"""
|
||||
serializer = RegistrationFileSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
def put(self, request, fileId):
|
||||
"""
|
||||
Met à jour un RegistrationFile existant.
|
||||
"""
|
||||
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=fileId)
|
||||
if registrationFile is None:
|
||||
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileSerializer(registrationFile, data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response({'message': 'Fichier mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, _id):
|
||||
"""
|
||||
Supprime un RegistrationFile existant.
|
||||
"""
|
||||
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=_id)
|
||||
if registrationFile is not None:
|
||||
registrationFile.file.delete() # Supprimer le fichier uploadé
|
||||
registrationFile.delete()
|
||||
return JsonResponse({'message': 'La suppression du fichier a été effectuée avec succès'}, safe=False)
|
||||
else:
|
||||
return JsonResponse({'erreur': 'Le fichier n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
24
Back-End/Subscriptions/views/__init__.py
Normal file
24
Back-End/Subscriptions/views/__init__.py
Normal file
@ -0,0 +1,24 @@
|
||||
from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive
|
||||
from .registration_file_views import RegistrationFileTemplateView, RegistrationFileTemplateSimpleView, RegistrationFileView, RegistrationFileSimpleView
|
||||
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||
from .student_views import StudentView, StudentListView, ChildrenListView
|
||||
from .guardian_views import GuardianView
|
||||
|
||||
__all__ = [
|
||||
'RegisterFormView',
|
||||
'RegisterFormWithIdView',
|
||||
'send',
|
||||
'resend',
|
||||
'archive',
|
||||
'RegistrationFileView',
|
||||
'RegistrationFileSimpleView',
|
||||
'RegistrationFileTemplateView',
|
||||
'RegistrationFileTemplateSimpleView',
|
||||
'RegistrationFileGroupView',
|
||||
'RegistrationFileGroupSimpleView',
|
||||
'get_registration_files_by_group',
|
||||
'StudentView',
|
||||
'StudentListView',
|
||||
'ChildrenListView',
|
||||
'GuardianView',
|
||||
]
|
||||
34
Back-End/Subscriptions/views/guardian_views.py
Normal file
34
Back-End/Subscriptions/views/guardian_views.py
Normal file
@ -0,0 +1,34 @@
|
||||
from django.http.response import JsonResponse
|
||||
from rest_framework.views import APIView
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg import openapi
|
||||
|
||||
from Subscriptions.models import Guardian
|
||||
from N3wtSchool import bdd
|
||||
|
||||
class GuardianView(APIView):
|
||||
"""
|
||||
Gestion des responsables légaux.
|
||||
"""
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère le dernier ID de responsable légal créé",
|
||||
operation_summary="Récupèrer le dernier ID de responsable légal créé",
|
||||
responses={
|
||||
200: openapi.Response(
|
||||
description="Dernier ID du responsable légal",
|
||||
schema=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'lastid': openapi.Schema(
|
||||
type=openapi.TYPE_INTEGER,
|
||||
description="Dernier ID créé"
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
def get(self, request):
|
||||
lastGuardian = bdd.getLastId(Guardian)
|
||||
return JsonResponse({"lastid":lastGuardian}, safe=False)
|
||||
385
Back-End/Subscriptions/views/register_form_views.py
Normal file
385
Back-End/Subscriptions/views/register_form_views.py
Normal file
@ -0,0 +1,385 @@
|
||||
from django.http.response import JsonResponse
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.core.cache import cache
|
||||
from rest_framework.parsers import JSONParser
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.decorators import action, api_view
|
||||
from rest_framework import status
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg import openapi
|
||||
|
||||
import json
|
||||
import os
|
||||
from django.core.files import File
|
||||
|
||||
import Subscriptions.mailManager as mailer
|
||||
import Subscriptions.util as util
|
||||
|
||||
from Subscriptions.serializers import RegistrationFormSerializer
|
||||
from Subscriptions.pagination import CustomPagination
|
||||
from Subscriptions.signals import clear_cache
|
||||
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationFile, RegistrationFileGroup
|
||||
from Subscriptions.automate import updateStateMachine
|
||||
|
||||
from N3wtSchool import settings, bdd
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# /Subscriptions/registerForms
|
||||
class RegisterFormView(APIView):
|
||||
"""
|
||||
Gère la liste des dossiers d’inscription, lecture et création.
|
||||
"""
|
||||
pagination_class = CustomPagination
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[
|
||||
openapi.Parameter('filter', openapi.IN_QUERY, description="filtre", type=openapi.TYPE_STRING, enum=['pending', 'archived', 'subscribed'], required=True),
|
||||
openapi.Parameter('search', openapi.IN_QUERY, description="search", type=openapi.TYPE_STRING, required=False),
|
||||
openapi.Parameter('page_size', openapi.IN_QUERY, description="limite de page lors de la pagination", type=openapi.TYPE_INTEGER, required=False),
|
||||
],
|
||||
responses={200: RegistrationFormSerializer(many=True)},
|
||||
operation_description="Récupère les dossier d'inscriptions en fonction du filtre passé.",
|
||||
operation_summary="Récupérer les dossier d'inscriptions",
|
||||
examples={
|
||||
"application/json": [
|
||||
{
|
||||
"id": 1,
|
||||
"student": {
|
||||
"id": 1,
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"date_of_birth": "2010-01-01"
|
||||
},
|
||||
"status": "pending",
|
||||
"last_update": "10-02-2025 10:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"student": {
|
||||
"id": 2,
|
||||
"first_name": "Jane",
|
||||
"last_name": "Doe",
|
||||
"date_of_birth": "2011-02-02"
|
||||
},
|
||||
"status": "archived",
|
||||
"last_update": "09-02-2025 09:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
def get(self, request):
|
||||
"""
|
||||
Récupère les fiches d'inscriptions en fonction du filtre passé.
|
||||
"""
|
||||
# Récupération des paramètres
|
||||
filter = request.GET.get('filter', '').strip()
|
||||
search = request.GET.get('search', '').strip()
|
||||
page_size = request.GET.get('page_size', None)
|
||||
|
||||
# Gestion du page_size
|
||||
if page_size is not None:
|
||||
try:
|
||||
page_size = int(page_size)
|
||||
except ValueError:
|
||||
page_size = settings.NB_RESULT_PER_PAGE
|
||||
|
||||
# Définir le cache_key en fonction du filtre
|
||||
page_number = request.GET.get('page', 1)
|
||||
cache_key = f'N3WT_ficheInscriptions_{filter}_page_{page_number}_search_{search if filter == "pending" else ""}'
|
||||
cached_page = cache.get(cache_key)
|
||||
if cached_page:
|
||||
return JsonResponse(cached_page, safe=False)
|
||||
|
||||
# Récupérer les dossier d'inscriptions en fonction du filtre
|
||||
registerForms_List = None
|
||||
if filter == 'pending':
|
||||
exclude_states = [RegistrationForm.RegistrationFormStatus.RF_VALIDATED, RegistrationForm.RegistrationFormStatus.RF_ARCHIVED]
|
||||
registerForms_List = bdd.searchObjects(RegistrationForm, search, _excludeStates=exclude_states)
|
||||
elif filter == 'archived':
|
||||
registerForms_List = bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_ARCHIVED)
|
||||
elif filter == 'subscribed':
|
||||
registerForms_List = bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_VALIDATED)
|
||||
else:
|
||||
registerForms_List = None
|
||||
|
||||
if not registerForms_List:
|
||||
return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
|
||||
|
||||
# Pagination
|
||||
paginator = self.pagination_class()
|
||||
page = paginator.paginate_queryset(registerForms_List, request)
|
||||
if page is not None:
|
||||
registerForms_serializer = RegistrationFormSerializer(page, many=True)
|
||||
response_data = paginator.get_paginated_response(registerForms_serializer.data)
|
||||
cache.set(cache_key, response_data, timeout=60 * 15)
|
||||
return JsonResponse(response_data, safe=False)
|
||||
|
||||
return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
|
||||
|
||||
@swagger_auto_schema(
|
||||
request_body=RegistrationFormSerializer,
|
||||
responses={200: RegistrationFormSerializer()},
|
||||
operation_description="Crée un dossier d'inscription.",
|
||||
operation_summary="Créer un dossier d'inscription",
|
||||
examples={
|
||||
"application/json": {
|
||||
"student": {
|
||||
"id": 1,
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"date_of_birth": "2010-01-01"
|
||||
},
|
||||
"status": "pending",
|
||||
"last_update": "10-02-2025 10:00",
|
||||
"codeLienInscription": "ABC123XYZ456"
|
||||
}
|
||||
}
|
||||
)
|
||||
@method_decorator(csrf_protect, name='dispatch')
|
||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||
def post(self, request):
|
||||
"""
|
||||
Crée un dossier d'inscription.
|
||||
"""
|
||||
regiterFormData = request.data.copy()
|
||||
logger.info(f"Création d'un dossier d'inscription {request}")
|
||||
# Ajout de la date de mise à jour
|
||||
regiterFormData["last_update"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
|
||||
# Ajout du code d'inscription
|
||||
code = util.genereRandomCode(12)
|
||||
regiterFormData["codeLienInscription"] = code
|
||||
|
||||
guardiansId = regiterFormData.pop('idGuardians', [])
|
||||
registerForm_serializer = RegistrationFormSerializer(data=regiterFormData)
|
||||
fileGroupId = regiterFormData.pop('fileGroup', None)
|
||||
|
||||
if registerForm_serializer.is_valid():
|
||||
di = registerForm_serializer.save()
|
||||
|
||||
# Mise à jour de l'automate
|
||||
updateStateMachine(di, 'creationDI')
|
||||
|
||||
# Récupération du reponsable associé
|
||||
for guardianId in guardiansId:
|
||||
guardian = Guardian.objects.get(id=guardianId)
|
||||
di.student.guardians.add(guardian)
|
||||
di.save()
|
||||
if fileGroupId:
|
||||
di.fileGroup = RegistrationFileGroup.objects.get(id=fileGroupId)
|
||||
di.save()
|
||||
|
||||
return JsonResponse(registerForm_serializer.data, safe=False)
|
||||
else:
|
||||
logger.error(f"Erreur lors de la validation des données {regiterFormData}")
|
||||
|
||||
return JsonResponse(registerForm_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# /Subscriptions/registerForms/{id}
|
||||
class RegisterFormWithIdView(APIView):
|
||||
"""
|
||||
Gère la lecture, création, modification et suppression d’un dossier d’inscription.
|
||||
"""
|
||||
pagination_class = CustomPagination
|
||||
|
||||
@swagger_auto_schema(
|
||||
responses={200: RegistrationFormSerializer()},
|
||||
operation_description="Récupère un dossier d'inscription donné.",
|
||||
operation_summary="Récupérer un dossier d'inscription",
|
||||
examples={
|
||||
"application/json": {
|
||||
"id": 1,
|
||||
"student": {
|
||||
"id": 1,
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"date_of_birth": "2010-01-01"
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
def get(self, request, id):
|
||||
"""
|
||||
Récupère un dossier d'inscription donné.
|
||||
"""
|
||||
registerForm = bdd.getObject(RegistrationForm, "student__id", id)
|
||||
if registerForm is None:
|
||||
return JsonResponse({"errorMessage":'Le dossier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
registerForm_serializer = RegistrationFormSerializer(registerForm)
|
||||
return JsonResponse(registerForm_serializer.data, safe=False)
|
||||
|
||||
@swagger_auto_schema(
|
||||
request_body=RegistrationFormSerializer,
|
||||
responses={200: RegistrationFormSerializer()},
|
||||
operation_description="Modifie un dossier d'inscription donné.",
|
||||
operation_summary="Modifier un dossier d'inscription",
|
||||
examples={
|
||||
"application/json": {
|
||||
"id": 1,
|
||||
"student": {
|
||||
"id": 1,
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"date_of_birth": "2010-01-01"
|
||||
},
|
||||
"status": "under_review",
|
||||
"last_update": "10-02-2025 10:00"
|
||||
}
|
||||
}
|
||||
)
|
||||
@method_decorator(csrf_protect, name='dispatch')
|
||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||
def put(self, request, id):
|
||||
"""
|
||||
Modifie un dossier d'inscription donné.
|
||||
"""
|
||||
studentForm_data = JSONParser().parse(request)
|
||||
_status = studentForm_data.pop('status', 0)
|
||||
studentForm_data["last_update"] = str(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'))
|
||||
registerForm = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
|
||||
|
||||
if _status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW:
|
||||
try:
|
||||
# Génération de la fiche d'inscription au format PDF
|
||||
base_dir = f"data/registration_files/dossier_rf_{registerForm.pk}"
|
||||
os.makedirs(base_dir, exist_ok=True)
|
||||
|
||||
# Fichier PDF initial
|
||||
initial_pdf = f"{base_dir}/rf_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf"
|
||||
registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
|
||||
registerForm.save()
|
||||
|
||||
# Récupération des fichiers d'inscription
|
||||
fileNames = RegistrationFile.get_files_from_rf(registerForm.pk)
|
||||
if registerForm.registration_file:
|
||||
fileNames.insert(0, registerForm.registration_file.path)
|
||||
|
||||
# Création du fichier PDF Fusionné
|
||||
merged_pdf = f"{base_dir}/dossier_complet_{registerForm.pk}.pdf"
|
||||
util.merge_files_pdf(fileNames, merged_pdf)
|
||||
|
||||
# Mise à jour du champ registration_file avec le fichier fusionné
|
||||
with open(merged_pdf, 'rb') as f:
|
||||
registerForm.registration_file.save(
|
||||
os.path.basename(merged_pdf),
|
||||
File(f),
|
||||
save=True
|
||||
)
|
||||
|
||||
# Mise à jour de l'automate
|
||||
updateStateMachine(registerForm, 'saisiDI')
|
||||
except Exception as e:
|
||||
return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
elif _status == RegistrationForm.RegistrationFormStatus.RF_VALIDATED:
|
||||
# L'école a validé le dossier d'inscription
|
||||
# Mise à jour de l'automate
|
||||
updateStateMachine(registerForm, 'valideDI')
|
||||
|
||||
studentForm_serializer = RegistrationFormSerializer(registerForm, data=studentForm_data)
|
||||
if studentForm_serializer.is_valid():
|
||||
studentForm_serializer.save()
|
||||
return JsonResponse(studentForm_serializer.data, safe=False)
|
||||
|
||||
return JsonResponse(studentForm_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@swagger_auto_schema(
|
||||
responses={204: 'No Content'},
|
||||
operation_description="Supprime un dossier d'inscription donné.",
|
||||
operation_summary="Supprimer un dossier d'inscription"
|
||||
)
|
||||
@method_decorator(csrf_protect, name='dispatch')
|
||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||
def delete(self, request, id):
|
||||
"""
|
||||
Supprime un dossier d'inscription donné.
|
||||
"""
|
||||
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
|
||||
if register_form != None:
|
||||
student = register_form.student
|
||||
student.guardians.clear()
|
||||
student.profiles.clear()
|
||||
student.registration_files.clear()
|
||||
student.delete()
|
||||
clear_cache()
|
||||
|
||||
return JsonResponse("La suppression du dossier a été effectuée avec succès", safe=False)
|
||||
|
||||
return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='get',
|
||||
responses={200: openapi.Response('Success', schema=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'message': openapi.Schema(type=openapi.TYPE_STRING)
|
||||
}
|
||||
))},
|
||||
operation_description="Envoie le dossier d'inscription par e-mail",
|
||||
operation_summary="Envoyer un dossier d'inscription"
|
||||
)
|
||||
@api_view(['GET'])
|
||||
def send(request,id):
|
||||
"""Envoie le dossier d'inscription par e-mail."""
|
||||
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
|
||||
if register_form != None:
|
||||
student = register_form.student
|
||||
guardian = student.getMainGuardian()
|
||||
email = guardian.email
|
||||
errorMessage = mailer.sendRegisterForm(email)
|
||||
if errorMessage == '':
|
||||
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
|
||||
updateStateMachine(register_form, 'envoiDI')
|
||||
return JsonResponse({"message": f"Le dossier d'inscription a bien été envoyé à l'addresse {email}"}, safe=False)
|
||||
return JsonResponse({"errorMessage":errorMessage}, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||
return JsonResponse({"errorMessage":'Dossier d\'inscription non trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='get',
|
||||
responses={200: openapi.Response('Success', schema=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'message': openapi.Schema(type=openapi.TYPE_STRING)
|
||||
}
|
||||
))},
|
||||
operation_description="Archive le dossier d'inscription",
|
||||
operation_summary="Archiver un dossier d'inscription"
|
||||
)
|
||||
@api_view(['GET'])
|
||||
def archive(request,id):
|
||||
"""Archive le dossier d'inscription."""
|
||||
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
|
||||
if register_form != None:
|
||||
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
|
||||
updateStateMachine(register_form, 'archiveDI')
|
||||
return JsonResponse({"message": "Le dossier a été archivé avec succès"}, safe=False)
|
||||
return JsonResponse({"errorMessage":'Dossier d\'inscription non trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='get',
|
||||
responses={200: openapi.Response('Success', schema=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'message': openapi.Schema(type=openapi.TYPE_STRING)
|
||||
}
|
||||
))},
|
||||
operation_description="Relance un dossier d'inscription par e-mail",
|
||||
operation_summary="Relancer un dossier d'inscription"
|
||||
)
|
||||
@api_view(['GET'])
|
||||
def resend(request,id):
|
||||
"""Relance un dossier d'inscription par e-mail."""
|
||||
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
|
||||
if register_form != None:
|
||||
student = register_form.student
|
||||
guardian = student.getMainGuardian()
|
||||
email = guardian.email
|
||||
errorMessage = mailer.envoieRelanceDossierInscription(email, register_form.codeLienInscription)
|
||||
if errorMessage == '':
|
||||
register_form.status=RegistrationForm.RegistrationFormStatus.RF_SENT
|
||||
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
|
||||
register_form.save()
|
||||
return JsonResponse({"message": f"Le dossier a été renvoyé à l'adresse {email}"}, safe=False)
|
||||
return JsonResponse({"errorMessage":errorMessage}, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||
return JsonResponse({"errorMessage":'Dossier d\'inscription non trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
125
Back-End/Subscriptions/views/registration_file_group_views.py
Normal file
125
Back-End/Subscriptions/views/registration_file_group_views.py
Normal file
@ -0,0 +1,125 @@
|
||||
from django.http.response import JsonResponse
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action, api_view
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg import openapi
|
||||
|
||||
from Subscriptions.serializers import RegistrationFileGroupSerializer
|
||||
from Subscriptions.models import RegistrationFileGroup, RegistrationFileTemplate
|
||||
from N3wtSchool import bdd
|
||||
|
||||
class RegistrationFileGroupView(APIView):
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère tous les groupes de fichiers d'inscription",
|
||||
responses={200: RegistrationFileGroupSerializer(many=True)}
|
||||
)
|
||||
def get(self, request):
|
||||
"""
|
||||
Récupère tous les groupes de fichiers d'inscription.
|
||||
"""
|
||||
groups = RegistrationFileGroup.objects.all()
|
||||
serializer = RegistrationFileGroupSerializer(groups, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Crée un nouveau groupe de fichiers d'inscription",
|
||||
request_body=RegistrationFileGroupSerializer,
|
||||
responses={
|
||||
201: RegistrationFileGroupSerializer,
|
||||
400: "Données invalides"
|
||||
}
|
||||
)
|
||||
def post(self, request):
|
||||
"""
|
||||
Crée un nouveau groupe de fichiers d'inscription.
|
||||
"""
|
||||
serializer = RegistrationFileGroupSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
class RegistrationFileGroupSimpleView(APIView):
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère un groupe de fichiers d'inscription spécifique",
|
||||
responses={
|
||||
200: RegistrationFileGroupSerializer,
|
||||
404: "Groupe non trouvé"
|
||||
}
|
||||
)
|
||||
def get(self, request, id):
|
||||
"""
|
||||
Récupère un groupe de fichiers d'inscription spécifique.
|
||||
"""
|
||||
group = bdd.getObject(_objectName=RegistrationFileGroup, _columnName='id', _value=id)
|
||||
if group is None:
|
||||
return JsonResponse({"errorMessage": "Le groupe de fichiers n'a pas été trouvé"},
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileGroupSerializer(group)
|
||||
return JsonResponse(serializer.data)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Met à jour un groupe de fichiers d'inscription",
|
||||
request_body=RegistrationFileGroupSerializer,
|
||||
responses={
|
||||
200: RegistrationFileGroupSerializer,
|
||||
400: "Données invalides",
|
||||
404: "Groupe non trouvé"
|
||||
}
|
||||
)
|
||||
def put(self, request, id):
|
||||
"""
|
||||
Met à jour un groupe de fichiers d'inscription existant.
|
||||
"""
|
||||
group = bdd.getObject(_objectName=RegistrationFileGroup, _columnName='id', _value=id)
|
||||
if group is None:
|
||||
return JsonResponse({'erreur': "Le groupe de fichiers n'a pas été trouvé"},
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileGroupSerializer(group, data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Supprime un groupe de fichiers d'inscription",
|
||||
responses={
|
||||
204: "Suppression réussie",
|
||||
404: "Groupe non trouvé"
|
||||
}
|
||||
)
|
||||
def delete(self, request, id):
|
||||
"""
|
||||
Supprime un groupe de fichiers d'inscription.
|
||||
"""
|
||||
group = bdd.getObject(_objectName=RegistrationFileGroup, _columnName='id', _value=id)
|
||||
if group is not None:
|
||||
group.delete()
|
||||
return JsonResponse({'message': 'La suppression du groupe a été effectuée avec succès'},
|
||||
status=status.HTTP_204_NO_CONTENT)
|
||||
return JsonResponse({'erreur': "Le groupe de fichiers n'a pas été trouvé"},
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='get',
|
||||
responses={200: openapi.Response('Success', schema=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'message': openapi.Schema(type=openapi.TYPE_STRING)
|
||||
}
|
||||
))},
|
||||
operation_description="Récupère les fichiers d'inscription d'un groupe donné",
|
||||
operation_summary="Récupèrer les fichiers d'inscription d'un groupe donné"
|
||||
)
|
||||
@api_view(['GET'])
|
||||
def get_registration_files_by_group(request, id):
|
||||
try:
|
||||
group = RegistrationFileGroup.objects.get(id=id)
|
||||
templates = RegistrationFileTemplate.objects.filter(group=group)
|
||||
templates_data = list(templates.values())
|
||||
return JsonResponse(templates_data, safe=False)
|
||||
except RegistrationFileGroup.DoesNotExist:
|
||||
return JsonResponse({'error': 'Le groupe de fichiers n\'a pas été trouvé'}, status=404)
|
||||
211
Back-End/Subscriptions/views/registration_file_views.py
Normal file
211
Back-End/Subscriptions/views/registration_file_views.py
Normal file
@ -0,0 +1,211 @@
|
||||
from django.http.response import JsonResponse
|
||||
from django.core.files import File
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg import openapi
|
||||
from rest_framework.parsers import MultiPartParser, FormParser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
|
||||
import os
|
||||
|
||||
from Subscriptions.serializers import RegistrationFileTemplateSerializer, RegistrationFileSerializer
|
||||
from Subscriptions.models import RegistrationFileTemplate, RegistrationFile
|
||||
from N3wtSchool import bdd
|
||||
|
||||
|
||||
class RegistrationFileTemplateView(APIView):
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère tous les fichiers templates pour les dossiers d'inscription",
|
||||
responses={200: RegistrationFileTemplateSerializer(many=True)}
|
||||
)
|
||||
def get(self, request):
|
||||
"""
|
||||
Récupère les fichiers templates pour les dossiers d’inscription.
|
||||
"""
|
||||
files = RegistrationFileTemplate.objects.all()
|
||||
serializer = RegistrationFileTemplateSerializer(files, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Crée un nouveau fichier template pour les dossiers d'inscription",
|
||||
request_body=RegistrationFileTemplateSerializer,
|
||||
responses={
|
||||
201: RegistrationFileTemplateSerializer,
|
||||
400: "Données invalides"
|
||||
}
|
||||
)
|
||||
def post(self, request):
|
||||
"""
|
||||
Crée un fichier template pour les dossiers d’inscription.
|
||||
"""
|
||||
serializer = RegistrationFileTemplateSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class RegistrationFileTemplateSimpleView(APIView):
|
||||
"""
|
||||
Gère les fichiers templates pour les dossiers d’inscription.
|
||||
"""
|
||||
parser_classes = (MultiPartParser, FormParser)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère un fichier template spécifique",
|
||||
responses={
|
||||
200: RegistrationFileTemplateSerializer,
|
||||
404: "Fichier template non trouvé"
|
||||
}
|
||||
)
|
||||
def get(self, request, id):
|
||||
"""
|
||||
Récupère les fichiers templates pour les dossiers d’inscription.
|
||||
"""
|
||||
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
|
||||
if registationFileTemplate is None:
|
||||
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileTemplateSerializer(registationFileTemplate)
|
||||
return JsonResponse(serializer.data, safe=False)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Met à jour un fichier template existant",
|
||||
request_body=RegistrationFileTemplateSerializer,
|
||||
responses={
|
||||
201: RegistrationFileTemplateSerializer,
|
||||
400: "Données invalides",
|
||||
404: "Fichier template non trouvé"
|
||||
}
|
||||
)
|
||||
def put(self, request, id):
|
||||
"""
|
||||
Met à jour un fichier template existant.
|
||||
"""
|
||||
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
|
||||
if registationFileTemplate is None:
|
||||
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileTemplateSerializer(registationFileTemplate,data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Supprime un fichier template",
|
||||
responses={
|
||||
204: "Suppression réussie",
|
||||
404: "Fichier template non trouvé"
|
||||
}
|
||||
)
|
||||
def delete(self, request, id):
|
||||
"""
|
||||
Supprime un fichier template existant.
|
||||
"""
|
||||
registrationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
|
||||
if registrationFileTemplate is not None:
|
||||
registrationFileTemplate.file.delete() # Supprimer le fichier uploadé
|
||||
registrationFileTemplate.delete()
|
||||
return JsonResponse({'message': 'La suppression du fichier d\'inscription a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||
else:
|
||||
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
class RegistrationFileView(APIView):
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère tous les fichiers d'inscription",
|
||||
responses={200: RegistrationFileSerializer(many=True)}
|
||||
)
|
||||
def get(self, request):
|
||||
"""
|
||||
Récupère les fichiers liés à un dossier d’inscription donné.
|
||||
"""
|
||||
files = RegistrationFile.objects.all()
|
||||
serializer = RegistrationFileSerializer(files, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Crée un nouveau fichier d'inscription",
|
||||
request_body=RegistrationFileSerializer,
|
||||
responses={
|
||||
201: RegistrationFileSerializer,
|
||||
400: "Données invalides"
|
||||
}
|
||||
)
|
||||
def post(self, request):
|
||||
"""
|
||||
Crée un RegistrationFile pour le RegistrationForm associé.
|
||||
"""
|
||||
serializer = RegistrationFileSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
|
||||
class RegistrationFileSimpleView(APIView):
|
||||
"""
|
||||
Gère la création, mise à jour et suppression de fichiers liés à un dossier d’inscription.
|
||||
"""
|
||||
parser_classes = (MultiPartParser, FormParser)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère un fichier d'inscription spécifique",
|
||||
responses={
|
||||
200: RegistrationFileSerializer,
|
||||
404: "Fichier non trouvé"
|
||||
}
|
||||
)
|
||||
def get(self, request, id):
|
||||
"""
|
||||
Récupère les fichiers liés à un dossier d’inscription donné.
|
||||
"""
|
||||
registationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
|
||||
if registationFile is None:
|
||||
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileSerializer(registationFile)
|
||||
return JsonResponse(serializer.data, safe=False)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Met à jour un fichier d'inscription existant",
|
||||
request_body=RegistrationFileSerializer,
|
||||
responses={
|
||||
200: openapi.Response(
|
||||
description="Fichier mis à jour avec succès",
|
||||
schema=RegistrationFileSerializer
|
||||
),
|
||||
400: "Données invalides",
|
||||
404: "Fichier non trouvé"
|
||||
}
|
||||
)
|
||||
def put(self, request, id):
|
||||
"""
|
||||
Met à jour un RegistrationFile existant.
|
||||
"""
|
||||
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
|
||||
if registrationFile is None:
|
||||
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileSerializer(registrationFile, data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response({'message': 'Fichier mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Supprime un fichier d'inscription",
|
||||
responses={
|
||||
200: "Suppression réussie",
|
||||
404: "Fichier non trouvé"
|
||||
}
|
||||
)
|
||||
def delete(self, request, id):
|
||||
"""
|
||||
Supprime un RegistrationFile existant.
|
||||
"""
|
||||
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
|
||||
if registrationFile is not None:
|
||||
registrationFile.file.delete() # Supprimer le fichier uploadé
|
||||
registrationFile.delete()
|
||||
return JsonResponse({'message': 'La suppression du fichier a été effectuée avec succès'}, safe=False)
|
||||
else:
|
||||
return JsonResponse({'erreur': 'Le fichier n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
83
Back-End/Subscriptions/views/student_views.py
Normal file
83
Back-End/Subscriptions/views/student_views.py
Normal file
@ -0,0 +1,83 @@
|
||||
from django.http.response import JsonResponse
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg import openapi
|
||||
|
||||
from Subscriptions.serializers import StudentByRFCreationSerializer, RegistrationFormByParentSerializer, StudentSerializer
|
||||
from Subscriptions.models import Student, RegistrationForm
|
||||
|
||||
from N3wtSchool import bdd
|
||||
|
||||
class StudentView(APIView):
|
||||
"""
|
||||
Gère la lecture d’un élève donné.
|
||||
"""
|
||||
@swagger_auto_schema(
|
||||
operation_summary="Récupérer les informations d'un élève",
|
||||
operation_description="Retourne les détails d'un élève spécifique à partir de son ID",
|
||||
responses={
|
||||
200: openapi.Response('Détails de l\'élève', StudentSerializer),
|
||||
404: openapi.Response('Élève non trouvé')
|
||||
},
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
'id', openapi.IN_PATH,
|
||||
description="ID de l'élève",
|
||||
type=openapi.TYPE_INTEGER,
|
||||
required=True
|
||||
)
|
||||
]
|
||||
)
|
||||
def get(self, request, id):
|
||||
student = bdd.getObject(_objectName=Student, _columnName='id', _value=id)
|
||||
if student is None:
|
||||
return JsonResponse({"errorMessage":'Aucun élève trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
student_serializer = StudentSerializer(student)
|
||||
return JsonResponse(student_serializer.data, safe=False)
|
||||
|
||||
# API utilisée pour la vue de création d'un DI
|
||||
class StudentListView(APIView):
|
||||
"""
|
||||
Pour la vue de création d’un dossier d’inscription : liste les élèves disponibles.
|
||||
"""
|
||||
@swagger_auto_schema(
|
||||
operation_summary="Lister tous les élèves",
|
||||
operation_description="Retourne la liste de tous les élèves inscrits ou en cours d'inscription",
|
||||
responses={
|
||||
200: openapi.Response('Liste des élèves', StudentByRFCreationSerializer(many=True))
|
||||
}
|
||||
)
|
||||
# Récupération de la liste des élèves inscrits ou en cours d'inscriptions
|
||||
def get(self, request):
|
||||
students = bdd.getAllObjects(_objectName=Student)
|
||||
students_serializer = StudentByRFCreationSerializer(students, many=True)
|
||||
return JsonResponse(students_serializer.data, safe=False)
|
||||
|
||||
|
||||
# API utilisée pour la vue parent
|
||||
class ChildrenListView(APIView):
|
||||
"""
|
||||
Pour la vue parent : liste les élèves rattachés à un profil donné.
|
||||
"""
|
||||
@swagger_auto_schema(
|
||||
operation_summary="Lister les élèves d'un parent",
|
||||
operation_description="Retourne la liste des élèves associés à un profil parent spécifique",
|
||||
responses={
|
||||
200: openapi.Response('Liste des élèves du parent', RegistrationFormByParentSerializer(many=True))
|
||||
},
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
'id', openapi.IN_PATH,
|
||||
description="ID du profil parent",
|
||||
type=openapi.TYPE_INTEGER,
|
||||
required=True
|
||||
)
|
||||
]
|
||||
)
|
||||
# Récupération des élèves d'un parent
|
||||
# idProfile : identifiant du profil connecté rattaché aux fiches d'élèves
|
||||
def get(self, request, id):
|
||||
students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__associated_profile__id', _value=id)
|
||||
students_serializer = RegistrationFormByParentSerializer(students, many=True)
|
||||
return JsonResponse(students_serializer.data, safe=False)
|
||||
0
Back-End/src/app.js
Normal file
0
Back-End/src/app.js
Normal file
0
Back-End/src/middleware/cors.js
Normal file
0
Back-End/src/middleware/cors.js
Normal file
@ -16,17 +16,25 @@ import { createDatas,
|
||||
fetchRegistrationDiscounts,
|
||||
fetchTuitionDiscounts,
|
||||
fetchRegistrationFees,
|
||||
fetchTuitionFees } from '@/app/lib/schoolAction';
|
||||
fetchTuitionFees,
|
||||
} from '@/app/lib/schoolAction';
|
||||
import SidebarTabs from '@/components/SidebarTabs';
|
||||
import FilesManagement from '@/components/Structure/Files/FilesManagement';
|
||||
|
||||
import { fetchRegisterFormFileTemplate } from '@/app/lib/subscriptionAction';
|
||||
|
||||
|
||||
|
||||
export default function Page() {
|
||||
const [specialities, setSpecialities] = useState([]);
|
||||
const [classes, setClasses] = useState([]);
|
||||
const [teachers, setTeachers] = useState([]);
|
||||
const [schedules, setSchedules] = useState([]); // Add this line
|
||||
const [registrationDiscounts, setRegistrationDiscounts] = useState([]);
|
||||
const [tuitionDiscounts, setTuitionDiscounts] = useState([]);
|
||||
const [registrationFees, setRegistrationFees] = useState([]);
|
||||
const [tuitionFees, setTuitionFees] = useState([]);
|
||||
const [fichiers, setFichiers] = useState([]);
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
|
||||
@ -54,6 +62,15 @@ export default function Page() {
|
||||
|
||||
// Fetch data for tuition fees
|
||||
handleTuitionFees();
|
||||
|
||||
// Fetch data for registration file templates
|
||||
fetchRegisterFormFileTemplate()
|
||||
.then((data)=> {
|
||||
setFichiers(data)
|
||||
})
|
||||
.catch(error => console.error('Error fetching files:', error));
|
||||
|
||||
|
||||
}, []);
|
||||
|
||||
const handleSpecialities = () => {
|
||||
@ -224,6 +241,11 @@ export default function Page() {
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'Files',
|
||||
label: 'Documents d\'inscription',
|
||||
content: <FilesManagement csrfToken={csrfToken} />
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@ -11,11 +11,10 @@ import Loader from '@/components/Loader';
|
||||
import AlertWithModal from '@/components/AlertWithModal';
|
||||
import DropdownMenu from "@/components/DropdownMenu";
|
||||
import { formatPhoneNumber } from '@/utils/Telephone';
|
||||
import { MoreVertical, Send, Edit, Trash2, FileText, CheckCircle, Plus, Download } from 'lucide-react';
|
||||
import { MoreVertical, Send, Edit, Trash2, FileText, CheckCircle, Plus } from 'lucide-react';
|
||||
import Modal from '@/components/Modal';
|
||||
import InscriptionForm from '@/components/Inscription/InscriptionForm'
|
||||
import AffectationClasseForm from '@/components/AffectationClasseForm'
|
||||
import FileUpload from './components/FileUpload';
|
||||
|
||||
import {
|
||||
PENDING,
|
||||
@ -26,9 +25,6 @@ import {
|
||||
sendRegisterForm,
|
||||
archiveRegisterForm,
|
||||
fetchRegisterFormFileTemplate,
|
||||
deleteRegisterFormFileTemplate,
|
||||
createRegistrationFormFileTemplate,
|
||||
editRegistrationFormFileTemplate,
|
||||
fetchStudents,
|
||||
editRegisterForm } from "@/app/lib/subscriptionAction"
|
||||
|
||||
@ -47,7 +43,7 @@ import {
|
||||
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
||||
import useCsrfToken from '@/hooks/useCsrfToken';
|
||||
import { formatDate } from '@/utils/Date';
|
||||
import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction';
|
||||
|
||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||
|
||||
@ -77,14 +73,13 @@ export default function Page({ params: { locale } }) {
|
||||
const [classes, setClasses] = useState([]);
|
||||
const [students, setEleves] = useState([]);
|
||||
const [reloadFetch, setReloadFetch] = useState(false);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [fileToEdit, setFileToEdit] = useState(null);
|
||||
|
||||
|
||||
const [registrationDiscounts, setRegistrationDiscounts] = useState([]);
|
||||
const [tuitionDiscounts, setTuitionDiscounts] = useState([]);
|
||||
const [registrationFees, setRegistrationFees] = useState([]);
|
||||
const [tuitionFees, setTuitionFees] = useState([]);
|
||||
const [groups, setGroups] = useState([]);
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
|
||||
@ -228,6 +223,11 @@ const registerFormArchivedDataHandler = (data) => {
|
||||
setTuitionFees(data);
|
||||
})
|
||||
.catch(requestErrorHandler);
|
||||
fetchRegistrationFileGroups()
|
||||
.then(data => {
|
||||
setGroups(data);
|
||||
})
|
||||
.catch(error => console.error('Error fetching file groups:', error));
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
setRegistrationFormsDataPending(mockFicheInscription);
|
||||
@ -357,7 +357,7 @@ useEffect(()=>{
|
||||
const selectedRegistrationDiscountsIds = updatedData.selectedRegistrationDiscounts.map(discountId => discountId)
|
||||
const selectedTuitionFeesIds = updatedData.selectedTuitionFees.map(feeId => feeId)
|
||||
const selectedTuitionDiscountsIds = updatedData.selectedTuitionDiscounts.map(discountId => discountId)
|
||||
|
||||
const selectedFileGroup = updatedData.selectedFileGroup
|
||||
const allFeesIds = [...selectedRegistrationFeesIds, ...selectedTuitionFeesIds];
|
||||
const allDiscountsds = [...selectedRegistrationDiscountsIds, ...selectedTuitionDiscountsIds];
|
||||
|
||||
@ -370,7 +370,8 @@ useEffect(()=>{
|
||||
},
|
||||
idGuardians: selectedGuardiansIds,
|
||||
fees: allFeesIds,
|
||||
discounts: allDiscountsds
|
||||
discounts: allDiscountsds,
|
||||
fileGroup: selectedFileGroup
|
||||
};
|
||||
|
||||
createRegisterForm(data, csrfToken)
|
||||
@ -567,89 +568,23 @@ const columnsSubscribed = [
|
||||
|
||||
];
|
||||
|
||||
const handleFileDelete = (fileId) => {
|
||||
deleteRegisterFormFileTemplate(fileId,csrfToken)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
|
||||
alert('Fichier supprimé avec succès.');
|
||||
} else {
|
||||
alert('Erreur lors de la suppression du fichier.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error deleting file:', error);
|
||||
alert('Erreur lors de la suppression du fichier.');
|
||||
});
|
||||
};
|
||||
|
||||
const handleFileEdit = (file) => {
|
||||
setIsEditing(true);
|
||||
setFileToEdit(file);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const columnsFiles = [
|
||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
||||
{ name: 'Date de création', transform: (row) => formatDate(new Date (row.date_added),"DD/MM/YYYY hh:mm:ss") },
|
||||
{ name: 'Fichier Obligatoire', transform: (row) => row.is_required ? 'Oui' : 'Non' },
|
||||
{ name: 'Ordre de fusion', transform: (row) => row.order },
|
||||
{ name: 'Actions', transform: (row) => (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
const tabs = [
|
||||
{
|
||||
row.file && (
|
||||
<a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
|
||||
<Download size={16} />
|
||||
</a>)
|
||||
id: 'pending',
|
||||
label: t('pending'),
|
||||
count: totalPending
|
||||
},
|
||||
{
|
||||
id: 'subscribed',
|
||||
label: t('subscribed'),
|
||||
count: totalSubscribed
|
||||
},
|
||||
{
|
||||
id: 'archived',
|
||||
label: t('archived'),
|
||||
count: totalArchives
|
||||
}
|
||||
<button onClick={() => handleFileEdit(row)} className="text-blue-500 hover:text-blue-700">
|
||||
<Edit size={16} />
|
||||
</button>
|
||||
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
) },
|
||||
];
|
||||
|
||||
const handleFileUpload = ({file, name, is_required, order}) => {
|
||||
if (!name) {
|
||||
alert('Veuillez entrer un nom de fichier.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
if(file){
|
||||
formData.append('file', file);
|
||||
}
|
||||
formData.append('name', name);
|
||||
formData.append('is_required', is_required);
|
||||
formData.append('order', order);
|
||||
|
||||
if (isEditing && fileToEdit) {
|
||||
editRegistrationFormFileTemplate(fileToEdit.id, formData, csrfToken)
|
||||
.then(data => {
|
||||
setFichiers(prevFichiers =>
|
||||
prevFichiers.map(f => f.id === fileToEdit.id ? data : f)
|
||||
);
|
||||
setIsModalOpen(false);
|
||||
setFileToEdit(null);
|
||||
setIsEditing(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error editing file:', error);
|
||||
});
|
||||
} else {
|
||||
createRegistrationFormFileTemplate(formData, csrfToken)
|
||||
.then(data => {
|
||||
setFichiers([...fichiers, data]);
|
||||
setIsModalOpen(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error uploading file:', error);
|
||||
});
|
||||
}
|
||||
};
|
||||
];
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader />;
|
||||
@ -699,16 +634,6 @@ const handleFileUpload = ({file, name, is_required, order}) => {
|
||||
active={activeTab === 'archived'}
|
||||
onClick={() => setActiveTab('archived')}
|
||||
/>
|
||||
<Tab
|
||||
text={(
|
||||
<>
|
||||
{t('subscribeFiles')}
|
||||
<span className="ml-2 text-sm text-gray-400">({fichiers.length})</span>
|
||||
</>
|
||||
)}
|
||||
active={activeTab === 'subscribeFiles'}
|
||||
onClick={() => setActiveTab('subscribeFiles')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b border-gray-200 mb-6 w-full">
|
||||
@ -758,41 +683,6 @@ const handleFileUpload = ({file, name, is_required, order}) => {
|
||||
</div>
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
{/*SI STATE == subscribeFiles */}
|
||||
{activeTab === 'subscribeFiles' && (
|
||||
<div>
|
||||
<div className="flex justify-end mb-4">
|
||||
<button
|
||||
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
|
||||
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
setIsOpen={setIsModalOpen}
|
||||
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
|
||||
ContentComponent={() => (
|
||||
<FileUpload
|
||||
onFileUpload={handleFileUpload}
|
||||
fileToEdit={fileToEdit}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8">
|
||||
<Table
|
||||
data={fichiers}
|
||||
columns={columnsFiles}
|
||||
itemsPerPage={itemsPerPage}
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={handlePageChange}
|
||||
className="mt-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Popup
|
||||
visible={popup.visible}
|
||||
@ -815,6 +705,7 @@ const handleFileUpload = ({file, name, is_required, order}) => {
|
||||
tuitionDiscounts={tuitionDiscounts}
|
||||
registrationFees={registrationFees.filter(fee => fee.is_active)}
|
||||
tuitionFees={tuitionFees.filter(fee => fee.is_active)}
|
||||
groups={groups}
|
||||
onSubmit={createRF}
|
||||
/>
|
||||
)}
|
||||
|
||||
74
Front-End/src/app/lib/registerFileGroupAction.js
Normal file
74
Front-End/src/app/lib/registerFileGroupAction.js
Normal file
@ -0,0 +1,74 @@
|
||||
import { BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL } from '@/utils/Url';
|
||||
|
||||
export async function fetchRegistrationFileGroups() {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch file groups');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function createRegistrationFileGroup(groupData, csrfToken) {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(groupData),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to create file group');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function deleteRegistrationFileGroup(groupId, csrfToken) {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export const editRegistrationFileGroup = async (groupId, groupData, csrfToken) => {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(groupData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la modification du groupe');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const fetchRegistrationFileFromGroup = async (groupId) => {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/registrationFiles`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la récupération des fichiers associés au groupe');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
@ -1,13 +1,9 @@
|
||||
import {
|
||||
BE_SUBSCRIPTION_STUDENTS_URL,
|
||||
BE_SUBSCRIPTION_STUDENT_URL,
|
||||
BE_SUBSCRIPTION_ARCHIVE_URL,
|
||||
BE_SUBSCRIPTION_SEND_URL,
|
||||
BE_SUBSCRIPTION_CHILDRENS_URL,
|
||||
BE_SUBSCRIPTION_REGISTERFORM_URL,
|
||||
BE_SUBSCRIPTION_REGISTERFORMS_URL,
|
||||
BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL,
|
||||
BE_SUBSCRIPTION_LAST_GUARDIAN_URL,
|
||||
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL,
|
||||
BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL
|
||||
} from '@/utils/Url';
|
||||
|
||||
@ -28,10 +24,10 @@ const requestResponseHandler = async (response) => {
|
||||
throw error;
|
||||
}
|
||||
|
||||
export const fetchRegisterForms = (type=PENDING, page='', pageSize='', search = '') => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${type}`;
|
||||
export const fetchRegisterForms = (filter=PENDING, page='', pageSize='', search = '') => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}?filter=${filter}`;
|
||||
if (page !== '' && pageSize !== '') {
|
||||
url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${type}?page=${page}&search=${search}`;
|
||||
url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}?filter=${filter}&page=${page}&search=${search}`;
|
||||
}
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
@ -41,17 +37,16 @@ export const fetchRegisterForms = (type=PENDING, page='', pageSize='', search =
|
||||
};
|
||||
|
||||
export const fetchRegisterForm = (id) =>{
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTERFORM_URL}/${id}`) // Utilisation de studentId au lieu de codeDI
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`) // Utilisation de studentId au lieu de codeDI
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
export const fetchLastGuardian = () =>{
|
||||
return fetch(`${BE_SUBSCRIPTION_LAST_GUARDIAN_URL}`)
|
||||
return fetch(`${BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const editRegisterForm=(id, data, csrfToken)=>{
|
||||
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTERFORM_URL}/${id}`, {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -61,11 +56,11 @@ export const editRegisterForm=(id, data, csrfToken)=>{
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
|
||||
};
|
||||
|
||||
|
||||
export const createRegisterForm=(data, csrfToken)=>{
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORM_URL}`;
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}`;
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -78,8 +73,26 @@ export const createRegisterForm=(data, csrfToken)=>{
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const sendRegisterForm = (id) => {
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/send`;
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const resendRegisterForm = (id) => {
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/resend`;
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler)
|
||||
|
||||
}
|
||||
export const archiveRegisterForm = (id) => {
|
||||
const url = `${BE_SUBSCRIPTION_ARCHIVE_URL}/${id}`;
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/archive`;
|
||||
return fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@ -88,18 +101,6 @@ export const archiveRegisterForm = (id) => {
|
||||
}).then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const sendRegisterForm = (id) => {
|
||||
const url = `${BE_SUBSCRIPTION_SEND_URL}/${id}`;
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const fetchRegisterFormFile = (id = null) => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}`
|
||||
if (id) {
|
||||
@ -204,9 +205,10 @@ export const editRegistrationFormFileTemplate = (fileId, data, csrfToken) => {
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const fetchStudents = () => {
|
||||
export const fetchStudents = (id) => {
|
||||
const url = (id)?`${BE_SUBSCRIPTION_STUDENTS_URL}/${id}`:`${BE_SUBSCRIPTION_STUDENTS_URL}`;
|
||||
const request = new Request(
|
||||
`${BE_SUBSCRIPTION_STUDENTS_URL}`,
|
||||
url,
|
||||
{
|
||||
method:'GET',
|
||||
headers: {
|
||||
@ -230,3 +232,16 @@ export const fetchChildren = (id) =>{
|
||||
);
|
||||
return fetch(request).then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export async function getRegisterFormFileTemplate(fileId) {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTERFORM_FILE_TEMPLATE_URL}/${fileId}`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch file template');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
@ -1,18 +1,24 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
|
||||
import DraggableFileUpload from './DraggableFileUpload';
|
||||
import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction';
|
||||
|
||||
export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
||||
const [fileName, setFileName] = useState('');
|
||||
const [file, setFile] = useState(null);
|
||||
const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired
|
||||
const [order, setOrder] = useState(0);
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [selectedGroup, setSelectedGroup] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
fetchRegistrationFileGroups().then(data => setGroups(data));
|
||||
|
||||
if (fileToEdit) {
|
||||
setFileName(fileToEdit.name || '');
|
||||
setIsRequired(fileToEdit.is_required || false);
|
||||
setOrder(fileToEdit.fusion_order || 0);
|
||||
setSelectedGroup(fileToEdit.group_id || '');
|
||||
}
|
||||
}, [fileToEdit]);
|
||||
|
||||
@ -26,11 +32,13 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
||||
name: fileName,
|
||||
is_required: isRequired,
|
||||
order: parseInt(order, 10),
|
||||
groupId: selectedGroup || null
|
||||
});
|
||||
setFile(null);
|
||||
setFileName('');
|
||||
setIsRequired(false);
|
||||
setOrder(0);
|
||||
setSelectedGroup('');
|
||||
};
|
||||
|
||||
return (
|
||||
@ -72,6 +80,19 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
||||
onChange={() => setIsRequired(!isRequired)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label className="block mb-2">Groupe</label>
|
||||
<select
|
||||
value={selectedGroup}
|
||||
onChange={(e) => setSelectedGroup(e.target.value)}
|
||||
className="w-full border rounded p-2"
|
||||
>
|
||||
<option value="">Aucun groupe</option>
|
||||
{groups.map(group => (
|
||||
<option key={group.id} value={group.id}>{group.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -9,7 +9,7 @@ import DiscountsSection from '@/components/Structure/Tarification/DiscountsSecti
|
||||
import SectionTitle from '@/components/SectionTitle';
|
||||
import ProgressStep from '@/components/ProgressStep';
|
||||
|
||||
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit, currentStep }) => {
|
||||
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit, currentStep, groups }) => {
|
||||
const [formData, setFormData] = useState({
|
||||
studentLastName: '',
|
||||
studentFirstName: '',
|
||||
@ -21,7 +21,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
selectedRegistrationDiscounts: [],
|
||||
selectedRegistrationFees: registrationFees.map(fee => fee.id),
|
||||
selectedTuitionDiscounts: [],
|
||||
selectedTuitionFees: []
|
||||
selectedTuitionFees: [],
|
||||
selectedFileGroup: null // Ajout du groupe de fichiers sélectionné
|
||||
});
|
||||
|
||||
const [step, setStep] = useState(currentStep || 1);
|
||||
@ -35,10 +36,11 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
2: 'Nouveau Responsable',
|
||||
3: "Frais d'inscription",
|
||||
4: 'Frais de scolarité',
|
||||
5: 'Récapitulatif'
|
||||
5: 'Documents requis',
|
||||
6: 'Récapitulatif'
|
||||
};
|
||||
|
||||
const steps = ['Élève', 'Responsable', 'Inscription', 'Scolarité', 'Récap'];
|
||||
const steps = ['Élève', 'Responsable', 'Inscription', 'Scolarité', 'Documents', 'Récap'];
|
||||
|
||||
const isStep1Valid = formData.studentLastName && formData.studentFirstName;
|
||||
const isStep2Valid = (
|
||||
@ -47,7 +49,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
);
|
||||
const isStep3Valid = formData.selectedRegistrationFees.length > 0;
|
||||
const isStep4Valid = formData.selectedTuitionFees.length > 0;
|
||||
const isStep5Valid = isStep1Valid && isStep2Valid && isStep3Valid && isStep4Valid;
|
||||
const isStep5Valid = formData.selectedFileGroup !== null;
|
||||
const isStep6Valid = isStep1Valid && isStep2Valid && isStep3Valid && isStep4Valid && isStep5Valid;
|
||||
|
||||
const isStepValid = (stepNumber) => {
|
||||
switch (stepNumber) {
|
||||
@ -61,6 +64,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
return isStep4Valid;
|
||||
case 5:
|
||||
return isStep5Valid;
|
||||
case 6:
|
||||
return isStep6Valid;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -464,6 +469,44 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 5 && (
|
||||
<div>
|
||||
{groups.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-bold">Sélectionnez un groupe de documents</h3>
|
||||
{groups.map((group) => (
|
||||
<div key={group.id} className="flex items-center space-x-3">
|
||||
<input
|
||||
type="radio"
|
||||
name="fileGroup"
|
||||
value={group.id}
|
||||
checked={formData.selectedFileGroup === group.id}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
selectedFileGroup: parseInt(e.target.value)
|
||||
})}
|
||||
className="form-radio h-4 w-4 text-emerald-600"
|
||||
/>
|
||||
<label className="text-gray-900">
|
||||
{group.name}
|
||||
{group.description && (
|
||||
<span className="text-sm text-gray-500 ml-2">
|
||||
({group.description})
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
||||
<strong className="font-bold">Attention!</strong>
|
||||
<span className="block sm:inline"> Aucun groupe de documents n'a été créé.</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === steps.length && (
|
||||
<div>
|
||||
<div className="space-y-4">
|
||||
@ -553,7 +596,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
(step === 1 && !isStep1Valid) ||
|
||||
(step === 2 && !isStep2Valid) ||
|
||||
(step === 3 && !isStep3Valid) ||
|
||||
(step === 4 && !isStep4Valid)
|
||||
(step === 4 && !isStep4Valid) ||
|
||||
(step === 5 && !isStep5Valid)
|
||||
)
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
@ -563,7 +607,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
(step === 1 && !isStep1Valid) ||
|
||||
(step === 2 && !isStep2Valid) ||
|
||||
(step === 3 && !isStep3Valid) ||
|
||||
(step === 4 && !isStep4Valid)
|
||||
(step === 4 && !isStep4Valid) ||
|
||||
(step === 5 && !isStep5Valid)
|
||||
)
|
||||
}
|
||||
primary
|
||||
|
||||
@ -8,9 +8,10 @@ import Button from '@/components/Button';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||
import Table from '@/components/Table';
|
||||
import { fetchRegisterFormFileTemplate, createRegistrationFormFile, fetchRegisterForm, deleteRegisterFormFile } from '@/app/lib/subscriptionAction';
|
||||
import { fetchRegistrationFileFromGroup } from '@/app/lib/registerFileGroupAction';
|
||||
import { Download, Upload, Trash2, Eye } from 'lucide-react';
|
||||
import { BASE_URL } from '@/utils/Url';
|
||||
import DraggableFileUpload from '@/app/[locale]/admin/subscriptions/components/DraggableFileUpload';
|
||||
import DraggableFileUpload from '@/components/DraggableFileUpload';
|
||||
import Modal from '@/components/Modal';
|
||||
import FileStatusLabel from '@/components/FileStatusLabel';
|
||||
|
||||
@ -57,6 +58,7 @@ export default function InscriptionFormShared({
|
||||
// États pour la gestion des fichiers
|
||||
const [uploadedFiles, setUploadedFiles] = useState([]);
|
||||
const [fileTemplates, setFileTemplates] = useState([]);
|
||||
const [fileGroup, setFileGroup] = useState(null);
|
||||
const [fileName, setFileName] = useState("");
|
||||
const [file, setFile] = useState("");
|
||||
const [showUploadModal, setShowUploadModal] = useState(false);
|
||||
@ -83,15 +85,21 @@ export default function InscriptionFormShared({
|
||||
});
|
||||
setGuardians(data?.student?.guardians || []);
|
||||
setUploadedFiles(data.registration_files || []);
|
||||
setFileGroup(data.fileGroup || null);
|
||||
});
|
||||
|
||||
fetchRegisterFormFileTemplate().then((data) => {
|
||||
setFileTemplates(data);
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [studentId]);
|
||||
|
||||
useEffect(() => {
|
||||
if(fileGroup){
|
||||
fetchRegistrationFileFromGroup(fileGroup).then((data) => {
|
||||
setFileTemplates(data);
|
||||
});
|
||||
}
|
||||
}, [fileGroup]);
|
||||
|
||||
// Fonctions de gestion du formulaire et des fichiers
|
||||
const updateFormField = (field, value) => {
|
||||
setFormData(prev => ({...prev, [field]: value}));
|
||||
|
||||
56
Front-End/src/components/RegistrationFileGroupForm.js
Normal file
56
Front-End/src/components/RegistrationFileGroupForm.js
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
export default function RegistrationFileGroupForm({ onSubmit, initialData }) {
|
||||
const [name, setName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
setName(initialData.name);
|
||||
setDescription(initialData.description);
|
||||
}
|
||||
}, [initialData]);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
onSubmit({ name, description });
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Nom du groupe
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
rows={3}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
>
|
||||
{initialData ? 'Modifier le groupe' : 'Créer le groupe'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
21
Front-End/src/components/RegistrationFileGroupList.js
Normal file
21
Front-End/src/components/RegistrationFileGroupList.js
Normal file
@ -0,0 +1,21 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction';
|
||||
|
||||
export default function RegistrationFileGroupList() {
|
||||
const [groups, setGroups] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRegistrationFileGroups().then(data => setGroups(data));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Groupes de fichiers d'inscription</h2>
|
||||
<ul>
|
||||
{groups.map(group => (
|
||||
<li key={group.id}>{group.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
302
Front-End/src/components/Structure/Files/FilesManagement.js
Normal file
302
Front-End/src/components/Structure/Files/FilesManagement.js
Normal file
@ -0,0 +1,302 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Plus, Download, Edit, Trash2, FolderPlus } from 'lucide-react';
|
||||
import Modal from '@/components/Modal';
|
||||
import Table from '@/components/Table';
|
||||
import FileUpload from '@/components/FileUpload';
|
||||
import { formatDate } from '@/utils/Date';
|
||||
import { BASE_URL } from '@/utils/Url';
|
||||
import {
|
||||
fetchRegisterFormFileTemplate,
|
||||
createRegistrationFormFileTemplate,
|
||||
editRegistrationFormFileTemplate,
|
||||
deleteRegisterFormFileTemplate,
|
||||
getRegisterFormFileTemplate
|
||||
} from '@/app/lib/subscriptionAction';
|
||||
import {
|
||||
fetchRegistrationFileGroups,
|
||||
createRegistrationFileGroup,
|
||||
deleteRegistrationFileGroup,
|
||||
editRegistrationFileGroup
|
||||
} from '@/app/lib/registerFileGroupAction';
|
||||
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
|
||||
|
||||
export default function FilesManagement({ csrfToken }) {
|
||||
const [fichiers, setFichiers] = useState([]);
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [selectedGroup, setSelectedGroup] = useState(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [fileToEdit, setFileToEdit] = useState(null);
|
||||
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
||||
const [groupToEdit, setGroupToEdit] = useState(null);
|
||||
|
||||
// Fonction pour transformer les données des fichiers avec les informations complètes du groupe
|
||||
const transformFileData = (file, groups) => {
|
||||
if (!file.group) return file;
|
||||
|
||||
const groupInfo = groups.find(g => g.id === file.group);
|
||||
return {
|
||||
...file,
|
||||
group: groupInfo || { id: file.group, name: 'Groupe inconnu' }
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
fetchRegisterFormFileTemplate(),
|
||||
fetchRegistrationFileGroups()
|
||||
]).then(([filesData, groupsData]) => {
|
||||
setGroups(groupsData);
|
||||
// Sélectionner automatiquement le premier groupe s'il existe
|
||||
if (groupsData.length > 0) {
|
||||
setSelectedGroup(groupsData[0].id.toString());
|
||||
}
|
||||
// Transformer chaque fichier pour inclure les informations complètes du groupe
|
||||
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
|
||||
setFichiers(transformedFiles);
|
||||
}).catch(err => {
|
||||
console.log(err.message);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleFileDelete = (fileId) => {
|
||||
deleteRegisterFormFileTemplate(fileId, csrfToken)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
|
||||
alert('Fichier supprimé avec succès.');
|
||||
} else {
|
||||
alert('Erreur lors de la suppression du fichier.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error deleting file:', error);
|
||||
alert('Erreur lors de la suppression du fichier.');
|
||||
});
|
||||
};
|
||||
|
||||
const handleFileEdit = (file) => {
|
||||
setIsEditing(true);
|
||||
setFileToEdit(file);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const handleFileUpload = ({file, name, is_required, order, groupId}) => {
|
||||
if (!name) {
|
||||
alert('Veuillez entrer un nom de fichier.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
if(file) {
|
||||
formData.append('file', file);
|
||||
}
|
||||
formData.append('name', name);
|
||||
formData.append('is_required', is_required);
|
||||
formData.append('order', order);
|
||||
|
||||
// Modification ici : vérifier si groupId existe et n'est pas vide
|
||||
if (groupId && groupId !== '') {
|
||||
formData.append('group', groupId); // Notez que le nom du champ est 'group' et non 'group_id'
|
||||
}
|
||||
|
||||
if (isEditing && fileToEdit) {
|
||||
editRegistrationFormFileTemplate(fileToEdit.id, formData, csrfToken)
|
||||
.then(data => {
|
||||
// Transformer le fichier mis à jour avec les informations du groupe
|
||||
const transformedFile = transformFileData(data, groups);
|
||||
setFichiers(prevFichiers =>
|
||||
prevFichiers.map(f => f.id === fileToEdit.id ? transformedFile : f)
|
||||
);
|
||||
setIsModalOpen(false);
|
||||
setFileToEdit(null);
|
||||
setIsEditing(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error editing file:', error);
|
||||
alert('Erreur lors de la modification du fichier');
|
||||
});
|
||||
} else {
|
||||
createRegistrationFormFileTemplate(formData, csrfToken)
|
||||
.then(data => {
|
||||
// Transformer le nouveau fichier avec les informations du groupe
|
||||
const transformedFile = transformFileData(data, groups);
|
||||
setFichiers(prevFiles => [...prevFiles, transformedFile]);
|
||||
setIsModalOpen(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error uploading file:', error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleGroupSubmit = async (groupData) => {
|
||||
try {
|
||||
if (groupToEdit) {
|
||||
const updatedGroup = await editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken);
|
||||
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
|
||||
setGroupToEdit(null);
|
||||
} else {
|
||||
const newGroup = await createRegistrationFileGroup(groupData, csrfToken);
|
||||
setGroups([...groups, newGroup]);
|
||||
}
|
||||
setIsGroupModalOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Error handling group:', error);
|
||||
alert('Erreur lors de l\'opération sur le groupe');
|
||||
}
|
||||
};
|
||||
|
||||
const handleGroupEdit = (group) => {
|
||||
setGroupToEdit(group);
|
||||
setIsGroupModalOpen(true);
|
||||
};
|
||||
|
||||
const handleGroupDelete = (groupId) => {
|
||||
// Vérifier si des fichiers utilisent ce groupe
|
||||
const filesInGroup = fichiers.filter(file => file.group && file.group.id === groupId);
|
||||
if (filesInGroup.length > 0) {
|
||||
alert('Impossible de supprimer ce groupe car il contient des fichiers. Veuillez d\'abord retirer tous les fichiers de ce groupe.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.confirm('Êtes-vous sûr de vouloir supprimer ce groupe ?')) {
|
||||
deleteRegistrationFileGroup(groupId, csrfToken)
|
||||
.then((response) => {
|
||||
if (response.status === 409) {
|
||||
throw new Error('Ce groupe est lié à des inscriptions existantes.');
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la suppression du groupe.');
|
||||
}
|
||||
setGroups(groups.filter(group => group.id !== groupId));
|
||||
alert('Groupe supprimé avec succès.');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error deleting group:', error);
|
||||
alert(error.message || 'Erreur lors de la suppression du groupe. Vérifiez qu\'aucune inscription n\'utilise ce groupe.');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Ajouter cette fonction de filtrage
|
||||
const filteredFiles = fichiers.filter(file => {
|
||||
if (!selectedGroup) return true;
|
||||
return file.group && file.group.id === parseInt(selectedGroup);
|
||||
});
|
||||
|
||||
const columnsFiles = [
|
||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
||||
{ name: 'Groupe', transform: (row) => row.group ? row.group.name : 'Aucun' },
|
||||
{ name: 'Date de création', transform: (row) => formatDate(new Date (row.date_added),"DD/MM/YYYY hh:mm:ss") },
|
||||
{ name: 'Fichier Obligatoire', transform: (row) => row.is_required ? 'Oui' : 'Non' },
|
||||
{ name: 'Ordre de fusion', transform: (row) => row.order },
|
||||
{ name: 'Actions', transform: (row) => (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{row.file && (
|
||||
<a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
|
||||
<Download size={16} />
|
||||
</a>
|
||||
)}
|
||||
<button onClick={() => handleFileEdit(row)} className="text-blue-500 hover:text-blue-700">
|
||||
<Edit size={16} />
|
||||
</button>
|
||||
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
];
|
||||
|
||||
const columnsGroups = [
|
||||
{ name: 'Nom du groupe', transform: (row) => row.name },
|
||||
{ name: 'Description', transform: (row) => row.description },
|
||||
{ name: 'Actions', transform: (row) => (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<button onClick={() => handleGroupEdit(row)} className="text-blue-500 hover:text-blue-700">
|
||||
<Edit size={16} />
|
||||
</button>
|
||||
<button onClick={() => handleGroupDelete(row.id)} className="text-red-500 hover:text-red-700">
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
setIsOpen={setIsModalOpen}
|
||||
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
|
||||
ContentComponent={() => (
|
||||
<FileUpload
|
||||
onFileUpload={handleFileUpload}
|
||||
fileToEdit={fileToEdit}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Modal
|
||||
isOpen={isGroupModalOpen}
|
||||
setIsOpen={setIsGroupModalOpen}
|
||||
title={groupToEdit ? "Modifier le groupe" : "Ajouter un groupe de fichiers"}
|
||||
ContentComponent={() => (
|
||||
<RegistrationFileGroupForm
|
||||
onSubmit={handleGroupSubmit}
|
||||
initialData={groupToEdit}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 mb-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-bold">Groupes de fichiers</h2>
|
||||
<button
|
||||
onClick={() => setIsGroupModalOpen(true)}
|
||||
className="flex items-center bg-blue-600 text-white p-2 rounded-full shadow hover:bg-blue-900 transition duration-200"
|
||||
>
|
||||
<FolderPlus className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<Table
|
||||
data={groups}
|
||||
columns={columnsGroups}
|
||||
itemsPerPage={5}
|
||||
currentPage={1}
|
||||
totalPages={Math.ceil(groups.length / 5)}
|
||||
/>
|
||||
</div>
|
||||
{groups.length > 0 && (
|
||||
<div className="mt-8">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-bold">Fichiers</h2>
|
||||
<div className="flex items-center gap-4">
|
||||
<select
|
||||
className="border rounded p-2"
|
||||
value={selectedGroup || ''}
|
||||
onChange={(e) => setSelectedGroup(e.target.value)}
|
||||
>
|
||||
<option value="">Tous les groupes</option>
|
||||
{groups.map(group => (
|
||||
<option key={group.id} value={group.id}>{group.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
|
||||
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
data={filteredFiles}
|
||||
columns={columnsFiles}
|
||||
itemsPerPage={10}
|
||||
currentPage={1}
|
||||
totalPages={Math.ceil(filteredFiles.length / 10)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -15,16 +15,14 @@ export const BE_AUTH_PROFILE_URL = `${BASE_URL}/Auth/profile`
|
||||
export const BE_AUTH_CSRF_URL = `${BASE_URL}/Auth/csrf`
|
||||
|
||||
// GESTION INSCRIPTION
|
||||
export const BE_SUBSCRIPTION_STUDENT_URL = `${BASE_URL}/Subscriptions/student`
|
||||
export const BE_SUBSCRIPTION_STUDENTS_URL = `${BASE_URL}/Subscriptions/students` // Récupère la liste des élèves inscrits ou en cours d'inscriptions
|
||||
export const BE_SUBSCRIPTION_CHILDRENS_URL = `${BASE_URL}/Subscriptions/children` // Récupère la liste des élèves d'un profil
|
||||
export const BE_SUBSCRIPTION_SEND_URL = `${BASE_URL}/Subscriptions/send`
|
||||
export const BE_SUBSCRIPTION_ARCHIVE_URL = `${BASE_URL}/Subscriptions/archive`
|
||||
export const BE_SUBSCRIPTION_REGISTERFORM_URL = `${BASE_URL}/Subscriptions/registerForm`
|
||||
export const BE_SUBSCRIPTION_REGISTERFORMS_URL = `${BASE_URL}/Subscriptions/registerForms`
|
||||
export const BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL = `${BASE_URL}/Subscriptions/registrationFileTemplates`
|
||||
export const BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL = `${BASE_URL}/Subscriptions/registrationFiles`
|
||||
export const BE_SUBSCRIPTION_LAST_GUARDIAN_URL = `${BASE_URL}/Subscriptions/lastGuardian`
|
||||
export const BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`
|
||||
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`
|
||||
|
||||
|
||||
//GESTION ENSEIGNANT
|
||||
export const BE_SCHOOL_SPECIALITY_URL = `${BASE_URL}/School/speciality`
|
||||
|
||||
Reference in New Issue
Block a user