mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
Compare commits
3 Commits
5e62ee5100
...
7486f6c5ce
| Author | SHA1 | Date | |
|---|---|---|---|
| 7486f6c5ce | |||
| 1e5bc6ccba | |||
| 0fb668b212 |
@ -236,7 +236,6 @@ def makeToken(user):
|
|||||||
"establishment__name": role.establishment.name,
|
"establishment__name": role.establishment.name,
|
||||||
"establishment__evaluation_frequency": role.establishment.evaluation_frequency,
|
"establishment__evaluation_frequency": role.establishment.evaluation_frequency,
|
||||||
"establishment__total_capacity": role.establishment.total_capacity,
|
"establishment__total_capacity": role.establishment.total_capacity,
|
||||||
"establishment__api_docuseal": role.establishment.api_docuseal,
|
|
||||||
"establishment__logo": logo_url,
|
"establishment__logo": logo_url,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
# This file is intentionally left blank to make this directory a Python package.
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
from django.urls import path, re_path
|
|
||||||
from .views import generate_jwt_token, clone_template, remove_template, download_template
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
re_path(r'generateToken$', generate_jwt_token, name='generate_jwt_token'),
|
|
||||||
re_path(r'cloneTemplate$', clone_template, name='clone_template'),
|
|
||||||
re_path(r'removeTemplate/(?P<id>[0-9]+)$', remove_template, name='remove_template'),
|
|
||||||
re_path(r'downloadTemplate/(?P<slug>[\w-]+)$', download_template, name='download_template')
|
|
||||||
]
|
|
||||||
@ -1,200 +0,0 @@
|
|||||||
from django.conf import settings
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from rest_framework.decorators import api_view
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework import status
|
|
||||||
import jwt
|
|
||||||
import datetime
|
|
||||||
import requests
|
|
||||||
from Establishment.models import Establishment
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
@api_view(['POST'])
|
|
||||||
def generate_jwt_token(request):
|
|
||||||
# Récupérer l'établissement concerné (par ID ou autre info transmise)
|
|
||||||
establishment_id = request.data.get('establishment_id')
|
|
||||||
if not establishment_id:
|
|
||||||
return Response({'error': 'establishment_id requis'}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
try:
|
|
||||||
establishment = Establishment.objects.get(id=establishment_id)
|
|
||||||
except Establishment.DoesNotExist:
|
|
||||||
return Response({'error': "Établissement introuvable"}, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
# Vérifier la clé API reçue dans le header
|
|
||||||
api_key = request.headers.get('X-Auth-Token')
|
|
||||||
if not api_key or not establishment.api_docuseal or api_key != establishment.api_docuseal:
|
|
||||||
return Response({'error': 'Clé API invalide'}, status=status.HTTP_401_UNAUTHORIZED)
|
|
||||||
|
|
||||||
# Récupérer les données de la requête
|
|
||||||
user_email = request.data.get('user_email')
|
|
||||||
documents_urls = request.data.get('documents_urls', [])
|
|
||||||
template_id = request.data.get('id')
|
|
||||||
|
|
||||||
if not user_email:
|
|
||||||
return Response({'error': 'User email is required'}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
# Utiliser la clé API de l'établissement comme secret JWT
|
|
||||||
jwt_secret = establishment.api_docuseal
|
|
||||||
jwt_algorithm = settings.DOCUSEAL_JWT['ALGORITHM']
|
|
||||||
expiration_delta = settings.DOCUSEAL_JWT['EXPIRATION_DELTA']
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
'user_email': user_email,
|
|
||||||
'documents_urls': documents_urls,
|
|
||||||
'template_id': template_id,
|
|
||||||
'exp': datetime.datetime.utcnow() + expiration_delta
|
|
||||||
}
|
|
||||||
|
|
||||||
token = jwt.encode(payload, jwt_secret, algorithm=jwt_algorithm)
|
|
||||||
return Response({'token': token}, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
@api_view(['POST'])
|
|
||||||
def clone_template(request):
|
|
||||||
# Récupérer l'établissement concerné
|
|
||||||
establishment_id = request.data.get('establishment_id')
|
|
||||||
print(f"establishment_id : {establishment_id}")
|
|
||||||
if not establishment_id:
|
|
||||||
return Response({'error': 'establishment_id requis'}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
try:
|
|
||||||
establishment = Establishment.objects.get(id=establishment_id)
|
|
||||||
except Establishment.DoesNotExist:
|
|
||||||
return Response({'error': "Établissement introuvable"}, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
# Vérifier la clé API reçue dans le header
|
|
||||||
api_key = request.headers.get('X-Auth-Token')
|
|
||||||
if not api_key or not establishment.api_docuseal or api_key != establishment.api_docuseal:
|
|
||||||
return Response({'error': 'Clé API invalide'}, status=status.HTTP_401_UNAUTHORIZED)
|
|
||||||
|
|
||||||
# Récupérer les données de la requête
|
|
||||||
document_id = request.data.get('templateId')
|
|
||||||
email = request.data.get('email')
|
|
||||||
is_required = request.data.get('is_required')
|
|
||||||
|
|
||||||
# Vérifier les données requises
|
|
||||||
if not document_id:
|
|
||||||
return Response({'error': 'template ID is required'}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
# URL de l'API de DocuSeal pour cloner le template
|
|
||||||
clone_url = f'https://docuseal.com/api/templates/{document_id}/clone'
|
|
||||||
|
|
||||||
# Faire la requête pour cloner le template
|
|
||||||
try:
|
|
||||||
response = requests.post(clone_url, headers={
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Auth-Token': establishment.api_docuseal
|
|
||||||
})
|
|
||||||
|
|
||||||
if response.status_code != status.HTTP_200_OK:
|
|
||||||
return Response({'error': 'Failed to clone template'}, status=response.status_code)
|
|
||||||
|
|
||||||
data = response.json()
|
|
||||||
|
|
||||||
if is_required:
|
|
||||||
# URL de l'API de DocuSeal pour créer une submission
|
|
||||||
submission_url = f'https://docuseal.com/api/submissions'
|
|
||||||
|
|
||||||
try:
|
|
||||||
clone_id = data['id']
|
|
||||||
response = requests.post(submission_url, json={
|
|
||||||
'template_id': clone_id,
|
|
||||||
'send_email': False,
|
|
||||||
'submitters': [{'email': email}]
|
|
||||||
}, headers={
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Auth-Token': establishment.api_docuseal
|
|
||||||
})
|
|
||||||
|
|
||||||
if response.status_code != status.HTTP_200_OK:
|
|
||||||
return Response({'error': 'Failed to create submission'}, status=response.status_code)
|
|
||||||
|
|
||||||
data = response.json()
|
|
||||||
data[0]['id'] = clone_id
|
|
||||||
return Response(data[0], status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
except requests.RequestException as e:
|
|
||||||
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
||||||
else:
|
|
||||||
print(f'NOT REQUIRED -> on ne crée pas de submission')
|
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
except requests.RequestException as e:
|
|
||||||
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
@api_view(['DELETE'])
|
|
||||||
def remove_template(request, id):
|
|
||||||
# Récupérer l'établissement concerné
|
|
||||||
establishment_id = request.GET.get('establishment_id')
|
|
||||||
if not establishment_id:
|
|
||||||
return Response({'error': 'establishment_id requis'}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
try:
|
|
||||||
establishment = Establishment.objects.get(id=establishment_id)
|
|
||||||
except Establishment.DoesNotExist:
|
|
||||||
return Response({'error': "Établissement introuvable"}, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
# Vérifier la clé API reçue dans le header
|
|
||||||
api_key = request.headers.get('X-Auth-Token')
|
|
||||||
if not api_key or not establishment.api_docuseal or api_key != establishment.api_docuseal:
|
|
||||||
return Response({'error': 'Clé API invalide'}, status=status.HTTP_401_UNAUTHORIZED)
|
|
||||||
|
|
||||||
# URL de l'API de DocuSeal pour supprimer le template
|
|
||||||
|
|
||||||
clone_url = f'https://docuseal.com/api/templates/{id}'
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.delete(clone_url, headers={
|
|
||||||
'X-Auth-Token': establishment.api_docuseal
|
|
||||||
})
|
|
||||||
|
|
||||||
if response.status_code != status.HTTP_200_OK:
|
|
||||||
return Response({'error': 'Failed to remove template'}, status=response.status_code)
|
|
||||||
|
|
||||||
data = response.json()
|
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
except requests.RequestException as e:
|
|
||||||
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
@api_view(['GET'])
|
|
||||||
def download_template(request, slug):
|
|
||||||
# Récupérer l'établissement concerné
|
|
||||||
establishment_id = request.GET.get('establishment_id')
|
|
||||||
if not establishment_id:
|
|
||||||
return Response({'error': 'establishment_id requis'}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
try:
|
|
||||||
establishment = Establishment.objects.get(id=establishment_id)
|
|
||||||
except Establishment.DoesNotExist:
|
|
||||||
return Response({'error': "Établissement introuvable"}, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
# Vérifier la clé API reçue dans le header
|
|
||||||
api_key = request.headers.get('X-Auth-Token')
|
|
||||||
if not api_key or not establishment.api_docuseal or api_key != establishment.api_docuseal:
|
|
||||||
return Response({'error': 'Clé API invalide'}, status=status.HTTP_401_UNAUTHORIZED)
|
|
||||||
|
|
||||||
# Vérifier les données requises
|
|
||||||
if not slug:
|
|
||||||
return Response({'error': 'slug is required'}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
# URL de l'API de DocuSeal pour télécharger le template
|
|
||||||
download_url = f'https://docuseal.com/submitters/{slug}/download'
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.get(download_url, headers={
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Auth-Token': establishment.api_docuseal
|
|
||||||
})
|
|
||||||
|
|
||||||
if response.status_code != status.HTTP_200_OK:
|
|
||||||
return Response({'error': 'Failed to download template'}, status=response.status_code)
|
|
||||||
|
|
||||||
data = response.json()
|
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
except requests.RequestException as e:
|
|
||||||
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
||||||
@ -27,7 +27,6 @@ class Establishment(models.Model):
|
|||||||
licence_code = models.CharField(max_length=100, blank=True)
|
licence_code = models.CharField(max_length=100, blank=True)
|
||||||
is_active = models.BooleanField(default=True)
|
is_active = models.BooleanField(default=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
api_docuseal = models.CharField(max_length=255, blank=True, null=True)
|
|
||||||
logo = models.FileField(
|
logo = models.FileField(
|
||||||
upload_to=registration_logo_upload_to,
|
upload_to=registration_logo_upload_to,
|
||||||
null=True,
|
null=True,
|
||||||
|
|||||||
@ -349,13 +349,6 @@ SIMPLE_JWT = {
|
|||||||
'TOKEN_TYPE_CLAIM': 'token_type',
|
'TOKEN_TYPE_CLAIM': 'token_type',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Configuration for DocuSeal JWT
|
|
||||||
DOCUSEAL_JWT = {
|
|
||||||
'ALGORITHM': 'HS256',
|
|
||||||
'SIGNING_KEY': SECRET_KEY,
|
|
||||||
'EXPIRATION_DELTA': timedelta(hours=1)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Django Channels Configuration
|
# Django Channels Configuration
|
||||||
ASGI_APPLICATION = 'N3wtSchool.asgi.application'
|
ASGI_APPLICATION = 'N3wtSchool.asgi.application'
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,6 @@ urlpatterns = [
|
|||||||
path("GestionEmail/", include(("GestionEmail.urls", 'GestionEmail'), namespace='GestionEmail')),
|
path("GestionEmail/", include(("GestionEmail.urls", 'GestionEmail'), namespace='GestionEmail')),
|
||||||
path("GestionNotification/", include(("GestionNotification.urls", 'GestionNotification'), namespace='GestionNotification')),
|
path("GestionNotification/", include(("GestionNotification.urls", 'GestionNotification'), namespace='GestionNotification')),
|
||||||
path("School/", include(("School.urls", 'School'), namespace='School')),
|
path("School/", include(("School.urls", 'School'), namespace='School')),
|
||||||
path("DocuSeal/", include(("DocuSeal.urls", 'DocuSeal'), namespace='DocuSeal')),
|
|
||||||
path("Planning/", include(("Planning.urls", 'Planning'), namespace='Planning')),
|
path("Planning/", include(("Planning.urls", 'Planning'), namespace='Planning')),
|
||||||
path("Establishment/", include(("Establishment.urls", 'Establishment'), namespace='Establishment')),
|
path("Establishment/", include(("Establishment.urls", 'Establishment'), namespace='Establishment')),
|
||||||
path("Settings/", include(("Settings.urls", 'Settings'), namespace='Settings')),
|
path("Settings/", include(("Settings.urls", 'Settings'), namespace='Settings')),
|
||||||
|
|||||||
@ -277,6 +277,16 @@ class RegistrationForm(models.Model):
|
|||||||
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
# Préparer le flag de création / changement de fileGroup
|
||||||
|
was_new = self.pk is None
|
||||||
|
old_fileGroup = None
|
||||||
|
if not was_new:
|
||||||
|
try:
|
||||||
|
old_instance = RegistrationForm.objects.get(pk=self.pk)
|
||||||
|
old_fileGroup = old_instance.fileGroup
|
||||||
|
except RegistrationForm.DoesNotExist:
|
||||||
|
old_fileGroup = None
|
||||||
|
|
||||||
# Vérifier si un fichier existant doit être remplacé
|
# Vérifier si un fichier existant doit être remplacé
|
||||||
if self.pk: # Si l'objet existe déjà dans la base de données
|
if self.pk: # Si l'objet existe déjà dans la base de données
|
||||||
try:
|
try:
|
||||||
@ -290,16 +300,27 @@ class RegistrationForm(models.Model):
|
|||||||
# Appeler la méthode save originale
|
# Appeler la méthode save originale
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
# Après save : si nouveau ou changement de fileGroup -> créer les templates
|
||||||
|
fileGroup_changed = (self.fileGroup is not None) and (old_fileGroup is None or (old_fileGroup and old_fileGroup.id != self.fileGroup.id))
|
||||||
|
if was_new or fileGroup_changed:
|
||||||
|
try:
|
||||||
|
import Subscriptions.util as util
|
||||||
|
created = util.create_templates_for_registration_form(self)
|
||||||
|
if created:
|
||||||
|
logger.info("Created %d templates for RegistrationForm %s", len(created), self.pk)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Error creating templates for RegistrationForm %s: %s", self.pk, e)
|
||||||
|
|
||||||
#############################################################
|
#############################################################
|
||||||
####################### MASTER FILES ########################
|
####################### MASTER FILES ########################
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
####### DocuSeal masters (documents école, à signer ou pas) #######
|
####### Formulaires masters (documents école, à signer ou pas) #######
|
||||||
class RegistrationSchoolFileMaster(models.Model):
|
class RegistrationSchoolFileMaster(models.Model):
|
||||||
groups = models.ManyToManyField(RegistrationFileGroup, related_name='school_file_masters', blank=True)
|
groups = models.ManyToManyField(RegistrationFileGroup, related_name='school_file_masters', blank=True)
|
||||||
id = models.IntegerField(primary_key=True)
|
|
||||||
name = models.CharField(max_length=255, default="")
|
name = models.CharField(max_length=255, default="")
|
||||||
is_required = models.BooleanField(default=False)
|
is_required = models.BooleanField(default=False)
|
||||||
|
formMasterData = models.JSONField(default=list, blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.group.name} - {self.id}'
|
return f'{self.group.name} - {self.id}'
|
||||||
@ -321,14 +342,14 @@ def registration_school_file_upload_to(instance, filename):
|
|||||||
def registration_parent_file_upload_to(instance, filename):
|
def registration_parent_file_upload_to(instance, filename):
|
||||||
return f"registration_files/dossier_rf_{instance.registration_form.pk}/parent/{filename}"
|
return f"registration_files/dossier_rf_{instance.registration_form.pk}/parent/{filename}"
|
||||||
|
|
||||||
####### DocuSeal templates (par dossier d'inscription) #######
|
####### Formulaires templates (par dossier d'inscription) #######
|
||||||
class RegistrationSchoolFileTemplate(models.Model):
|
class RegistrationSchoolFileTemplate(models.Model):
|
||||||
master = models.ForeignKey(RegistrationSchoolFileMaster, on_delete=models.CASCADE, related_name='school_file_templates', blank=True)
|
master = models.ForeignKey(RegistrationSchoolFileMaster, on_delete=models.CASCADE, related_name='school_file_templates', blank=True)
|
||||||
id = models.IntegerField(primary_key=True)
|
|
||||||
slug = models.CharField(max_length=255, default="")
|
slug = models.CharField(max_length=255, default="")
|
||||||
name = models.CharField(max_length=255, default="")
|
name = models.CharField(max_length=255, default="")
|
||||||
registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='school_file_templates', blank=True)
|
registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='school_file_templates', blank=True)
|
||||||
file = models.FileField(null=True,blank=True, upload_to=registration_school_file_upload_to)
|
file = models.FileField(null=True,blank=True, upload_to=registration_school_file_upload_to)
|
||||||
|
formTemplateData = models.JSONField(default=list, blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|||||||
@ -24,7 +24,12 @@ from .views import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||||
from .views import registration_file_views, get_school_file_templates_by_rf, get_parent_file_templates_by_rf
|
from .views import (
|
||||||
|
registration_school_file_masters_views,
|
||||||
|
registration_school_file_templates_views,
|
||||||
|
get_school_file_templates_by_rf,
|
||||||
|
get_parent_file_templates_by_rf
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r'^registerForms/(?P<id>[0-9]+)/archive$', archive, name="archive"),
|
re_path(r'^registerForms/(?P<id>[0-9]+)/archive$', archive, name="archive"),
|
||||||
|
|||||||
@ -21,8 +21,161 @@ from PyPDF2 import PdfMerger
|
|||||||
import shutil
|
import shutil
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import json
|
||||||
|
from django.http import QueryDict
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def build_payload_from_request(request):
|
||||||
|
"""
|
||||||
|
Normalise la request en payload prêt à être donné au serializer.
|
||||||
|
- supporte multipart/form-data où le front envoie 'data' (JSON string) ou un fichier JSON + fichiers
|
||||||
|
- supporte application/json ou form-data simple
|
||||||
|
Retour: (payload_dict, None) ou (None, Response erreur)
|
||||||
|
"""
|
||||||
|
data_field = request.data.get('data') if hasattr(request.data, 'get') else None
|
||||||
|
if data_field:
|
||||||
|
try:
|
||||||
|
# Si 'data' est un fichier (InMemoryUploadedFile ou fichier similaire), lire et décoder
|
||||||
|
if hasattr(data_field, 'read'):
|
||||||
|
raw = data_field.read()
|
||||||
|
if isinstance(raw, (bytes, bytearray)):
|
||||||
|
text = raw.decode('utf-8')
|
||||||
|
else:
|
||||||
|
text = raw
|
||||||
|
payload = json.loads(text)
|
||||||
|
# Si 'data' est bytes déjà
|
||||||
|
elif isinstance(data_field, (bytes, bytearray)):
|
||||||
|
payload = json.loads(data_field.decode('utf-8'))
|
||||||
|
# Si 'data' est une string JSON
|
||||||
|
elif isinstance(data_field, str):
|
||||||
|
payload = json.loads(data_field)
|
||||||
|
else:
|
||||||
|
# type inattendu
|
||||||
|
raise ValueError(f"Unsupported 'data' type: {type(data_field)}")
|
||||||
|
except (json.JSONDecodeError, ValueError, UnicodeDecodeError) as e:
|
||||||
|
logger.error(f'Invalid JSON in "data": {e}')
|
||||||
|
return None, Response({'error': "Invalid JSON in 'data'", 'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
else:
|
||||||
|
payload = request.data.copy() if hasattr(request.data, 'copy') else dict(request.data)
|
||||||
|
if isinstance(payload, QueryDict):
|
||||||
|
payload = payload.dict()
|
||||||
|
|
||||||
|
# Attacher les fichiers présents (ex: photo, files.*, etc.), sauf 'data' (déjà traité)
|
||||||
|
for f_key, f_val in request.FILES.items():
|
||||||
|
if f_key == 'data':
|
||||||
|
# remettre le pointeur au début si besoin (déjà lu) — non indispensable ici mais sûr
|
||||||
|
try:
|
||||||
|
f_val.seek(0)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# ne pas mettre le fichier 'data' dans le payload (c'est le JSON)
|
||||||
|
continue
|
||||||
|
payload[f_key] = f_val
|
||||||
|
|
||||||
|
return payload, None
|
||||||
|
|
||||||
|
def create_templates_for_registration_form(register_form):
|
||||||
|
"""
|
||||||
|
Idempotent:
|
||||||
|
- supprime les templates existants qui ne correspondent pas
|
||||||
|
aux masters du fileGroup courant du register_form (et supprime leurs fichiers).
|
||||||
|
- crée les templates manquants pour les masters du fileGroup courant.
|
||||||
|
Retourne la liste des templates créés.
|
||||||
|
"""
|
||||||
|
from Subscriptions.models import (
|
||||||
|
RegistrationSchoolFileMaster,
|
||||||
|
RegistrationSchoolFileTemplate,
|
||||||
|
# RegistrationParentFileMaster,
|
||||||
|
# RegistrationParentFileTemplate,
|
||||||
|
)
|
||||||
|
|
||||||
|
created = []
|
||||||
|
|
||||||
|
# Récupérer les masters du fileGroup courant
|
||||||
|
current_group = getattr(register_form, "fileGroup", None)
|
||||||
|
if not current_group:
|
||||||
|
# Si plus de fileGroup, supprimer tous les templates existants pour ce RF
|
||||||
|
school_existing = RegistrationSchoolFileTemplate.objects.filter(registration_form=register_form)
|
||||||
|
for t in school_existing:
|
||||||
|
try:
|
||||||
|
if getattr(t, "file", None):
|
||||||
|
t.file.delete(save=False)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Erreur suppression fichier school template %s", getattr(t, "pk", None))
|
||||||
|
t.delete()
|
||||||
|
# parent_existing = RegistrationParentFileTemplate.objects.filter(registration_form=register_form)
|
||||||
|
# for t in parent_existing:
|
||||||
|
# try:
|
||||||
|
# if getattr(t, "file", None):
|
||||||
|
# t.file.delete(save=False)
|
||||||
|
# except Exception:
|
||||||
|
# logger.exception("Erreur suppression fichier parent template %s", getattr(t, "pk", None))
|
||||||
|
# t.delete()
|
||||||
|
return created
|
||||||
|
|
||||||
|
school_masters = RegistrationSchoolFileMaster.objects.filter(groups=current_group).distinct()
|
||||||
|
# parent_masters = RegistrationParentFileMaster.objects.filter(groups=current_group).distinct()
|
||||||
|
|
||||||
|
school_master_ids = {m.pk for m in school_masters}
|
||||||
|
#parent_master_ids = {m.pk for m in parent_masters}
|
||||||
|
|
||||||
|
# Supprimer les school templates obsolètes
|
||||||
|
for tmpl in RegistrationSchoolFileTemplate.objects.filter(registration_form=register_form):
|
||||||
|
if not tmpl.master_id or tmpl.master_id not in school_master_ids:
|
||||||
|
try:
|
||||||
|
if getattr(tmpl, "file", None):
|
||||||
|
tmpl.file.delete(save=False)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Erreur suppression fichier school template obsolète %s", getattr(tmpl, "pk", None))
|
||||||
|
tmpl.delete()
|
||||||
|
logger.info("Deleted obsolete school template %s for RF %s", getattr(tmpl, "pk", None), register_form.pk)
|
||||||
|
|
||||||
|
# Supprimer les parent templates obsolètes
|
||||||
|
# for tmpl in RegistrationParentFileTemplate.objects.filter(registration_form=register_form):
|
||||||
|
# if not tmpl.master_id or tmpl.master_id not in parent_master_ids:
|
||||||
|
# try:
|
||||||
|
# if getattr(tmpl, "file", None):
|
||||||
|
# tmpl.file.delete(save=False)
|
||||||
|
# except Exception:
|
||||||
|
# logger.exception("Erreur suppression fichier parent template obsolète %s", getattr(tmpl, "pk", None))
|
||||||
|
# tmpl.delete()
|
||||||
|
# logger.info("Deleted obsolete parent template %s for RF %s", getattr(tmpl, "pk", None), register_form.pk)
|
||||||
|
|
||||||
|
# Créer les school templates manquants
|
||||||
|
for m in school_masters:
|
||||||
|
exists = RegistrationSchoolFileTemplate.objects.filter(master=m, registration_form=register_form).exists()
|
||||||
|
if exists:
|
||||||
|
continue
|
||||||
|
base_slug = (m.name or "master").strip().replace(" ", "_")[:40]
|
||||||
|
slug = f"{base_slug}_{register_form.pk}_{m.pk}"
|
||||||
|
tmpl = RegistrationSchoolFileTemplate.objects.create(
|
||||||
|
master=m,
|
||||||
|
registration_form=register_form,
|
||||||
|
name=m.name or "",
|
||||||
|
formTemplateData=m.formMasterData or [],
|
||||||
|
slug=slug,
|
||||||
|
)
|
||||||
|
created.append(tmpl)
|
||||||
|
logger.info("Created school template %s from master %s for RF %s", tmpl.pk, m.pk, register_form.pk)
|
||||||
|
|
||||||
|
# Créer les parent templates manquants
|
||||||
|
# for m in parent_masters:
|
||||||
|
# exists = RegistrationParentFileTemplate.objects.filter(master=m, registration_form=register_form).exists()
|
||||||
|
# if exists:
|
||||||
|
# continue
|
||||||
|
# tmpl = RegistrationParentFileTemplate.objects.create(
|
||||||
|
# master=m,
|
||||||
|
# registration_form=register_form,
|
||||||
|
# file=None,
|
||||||
|
# )
|
||||||
|
# created.append(tmpl)
|
||||||
|
# logger.info("Created parent template %s from master %s for RF %s", tmpl.pk, m.pk, register_form.pk)
|
||||||
|
|
||||||
|
return created
|
||||||
|
|
||||||
def recupereListeFichesInscription():
|
def recupereListeFichesInscription():
|
||||||
"""
|
"""
|
||||||
Retourne la liste complète des fiches d’inscription.
|
Retourne la liste complète des fiches d’inscription.
|
||||||
|
|||||||
@ -1,14 +1,24 @@
|
|||||||
from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive, get_school_file_templates_by_rf, get_parent_file_templates_by_rf
|
from .register_form_views import (
|
||||||
from .registration_file_views import (
|
RegisterFormView,
|
||||||
|
RegisterFormWithIdView,
|
||||||
|
send,
|
||||||
|
resend,
|
||||||
|
archive,
|
||||||
|
get_school_file_templates_by_rf,
|
||||||
|
get_parent_file_templates_by_rf
|
||||||
|
)
|
||||||
|
from .registration_school_file_masters_views import (
|
||||||
RegistrationSchoolFileMasterView,
|
RegistrationSchoolFileMasterView,
|
||||||
RegistrationSchoolFileMasterSimpleView,
|
RegistrationSchoolFileMasterSimpleView,
|
||||||
RegistrationSchoolFileTemplateView,
|
|
||||||
RegistrationSchoolFileTemplateSimpleView,
|
|
||||||
RegistrationParentFileMasterView,
|
RegistrationParentFileMasterView,
|
||||||
RegistrationParentFileMasterSimpleView,
|
RegistrationParentFileMasterSimpleView,
|
||||||
RegistrationParentFileTemplateSimpleView,
|
RegistrationParentFileTemplateSimpleView,
|
||||||
RegistrationParentFileTemplateView
|
RegistrationParentFileTemplateView
|
||||||
)
|
)
|
||||||
|
from .registration_school_file_templates_views import (
|
||||||
|
RegistrationSchoolFileTemplateView,
|
||||||
|
RegistrationSchoolFileTemplateSimpleView,
|
||||||
|
)
|
||||||
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||||
from .student_views import StudentView, StudentListView, ChildrenListView, search_students
|
from .student_views import StudentView, StudentListView, ChildrenListView, search_students
|
||||||
from .guardian_views import GuardianView, DissociateGuardianView
|
from .guardian_views import GuardianView, DissociateGuardianView
|
||||||
@ -33,7 +43,7 @@ __all__ = [
|
|||||||
'RegistrationFileGroupSimpleView',
|
'RegistrationFileGroupSimpleView',
|
||||||
'get_registration_files_by_group',
|
'get_registration_files_by_group',
|
||||||
'get_school_file_templates_by_rf',
|
'get_school_file_templates_by_rf',
|
||||||
'get_parent_file_templates_by_rf'
|
'get_parent_file_templates_by_rf',
|
||||||
'StudentView',
|
'StudentView',
|
||||||
'StudentListView',
|
'StudentListView',
|
||||||
'ChildrenListView',
|
'ChildrenListView',
|
||||||
|
|||||||
@ -0,0 +1,363 @@
|
|||||||
|
from django.http.response import JsonResponse
|
||||||
|
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 json
|
||||||
|
from django.http import QueryDict
|
||||||
|
|
||||||
|
from Subscriptions.serializers import RegistrationSchoolFileMasterSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileMasterSerializer, RegistrationParentFileTemplateSerializer
|
||||||
|
from Subscriptions.models import (
|
||||||
|
RegistrationForm,
|
||||||
|
RegistrationSchoolFileMaster,
|
||||||
|
RegistrationSchoolFileTemplate,
|
||||||
|
RegistrationParentFileMaster,
|
||||||
|
RegistrationParentFileTemplate
|
||||||
|
)
|
||||||
|
from N3wtSchool import bdd
|
||||||
|
import logging
|
||||||
|
import Subscriptions.util as util
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class RegistrationSchoolFileMasterView(APIView):
|
||||||
|
parser_classes = [MultiPartParser, FormParser]
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère tous les masters de templates d'inscription pour un établissement donné",
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
'establishment_id',
|
||||||
|
openapi.IN_QUERY,
|
||||||
|
description="ID de l'établissement",
|
||||||
|
type=openapi.TYPE_INTEGER,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
],
|
||||||
|
responses={200: RegistrationSchoolFileMasterSerializer(many=True)}
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
establishment_id = request.GET.get('establishment_id')
|
||||||
|
if not establishment_id:
|
||||||
|
return Response({'error': "Paramètre 'establishment_id' requis"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Filtrer les masters liés à l'établissement via groups.establishment
|
||||||
|
masters = RegistrationSchoolFileMaster.objects.filter(
|
||||||
|
groups__establishment__id=establishment_id
|
||||||
|
).distinct()
|
||||||
|
serializer = RegistrationSchoolFileMasterSerializer(masters, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Crée un nouveau master de template d'inscription",
|
||||||
|
request_body=RegistrationSchoolFileMasterSerializer,
|
||||||
|
responses={
|
||||||
|
201: RegistrationSchoolFileMasterSerializer,
|
||||||
|
400: "Données invalides"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
logger.info(f"raw request.data: {request.data}")
|
||||||
|
|
||||||
|
payload, resp = util.build_payload_from_request(request)
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
|
|
||||||
|
logger.info(f"payload for serializer: {payload}")
|
||||||
|
serializer = RegistrationSchoolFileMasterSerializer(data=payload, partial=True)
|
||||||
|
if serializer.is_valid():
|
||||||
|
obj = serializer.save()
|
||||||
|
|
||||||
|
# Propager la création des templates côté serveur pour les RegistrationForm
|
||||||
|
try:
|
||||||
|
groups_qs = obj.groups.all()
|
||||||
|
if groups_qs.exists():
|
||||||
|
# Tous les RegistrationForm dont fileGroup est dans les groups du master
|
||||||
|
rfs = RegistrationForm.objects.filter(fileGroup__in=groups_qs).distinct()
|
||||||
|
for rf in rfs:
|
||||||
|
try:
|
||||||
|
util.create_templates_for_registration_form(rf)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Error creating templates for RF %s from master %s: %s", getattr(rf, 'pk', None), getattr(obj, 'pk', None), e)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error while propagating templates after master creation %s", getattr(obj, 'pk', None))
|
||||||
|
|
||||||
|
|
||||||
|
return Response(RegistrationSchoolFileMasterSerializer(obj).data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
logger.error(f"serializer errors: {serializer.errors}")
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
class RegistrationSchoolFileMasterSimpleView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère un master de template d'inscription spécifique",
|
||||||
|
responses={
|
||||||
|
200: RegistrationSchoolFileMasterSerializer,
|
||||||
|
404: "Master non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get(self, request, id):
|
||||||
|
master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _columnName='id', _value=id)
|
||||||
|
if master is None:
|
||||||
|
return JsonResponse({"errorMessage":'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
serializer = RegistrationSchoolFileMasterSerializer(master)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Met à jour un master de template d'inscription existant",
|
||||||
|
request_body=RegistrationSchoolFileMasterSerializer,
|
||||||
|
responses={
|
||||||
|
200: RegistrationSchoolFileMasterSerializer,
|
||||||
|
400: "Données invalides",
|
||||||
|
404: "Master non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def put(self, request, id):
|
||||||
|
master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _columnName='id', _value=id)
|
||||||
|
if master is None:
|
||||||
|
return JsonResponse({'erreur': "Le master de template n'a pas été trouvé"}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
# snapshot des groups avant update
|
||||||
|
old_group_ids = set(master.groups.values_list('id', flat=True))
|
||||||
|
|
||||||
|
# Normaliser payload (supporte form-data avec champ 'data' JSON ou fichier JSON)
|
||||||
|
payload, resp = util.build_payload_from_request(request)
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
|
|
||||||
|
logger.info(f"payload for update serializer: {payload}")
|
||||||
|
serializer = RegistrationSchoolFileMasterSerializer(master, data=payload, partial=True)
|
||||||
|
if serializer.is_valid():
|
||||||
|
obj = serializer.save()
|
||||||
|
|
||||||
|
# groups après update
|
||||||
|
new_group_ids = set(obj.groups.values_list('id', flat=True))
|
||||||
|
|
||||||
|
removed_group_ids = old_group_ids - new_group_ids
|
||||||
|
added_group_ids = new_group_ids - old_group_ids
|
||||||
|
|
||||||
|
# Pour chaque RF appartenant aux groupes retirés -> nettoyer les templates (idempotent)
|
||||||
|
if removed_group_ids:
|
||||||
|
try:
|
||||||
|
rfs_removed = RegistrationForm.objects.filter(fileGroup__in=list(removed_group_ids)).distinct()
|
||||||
|
for rf in rfs_removed:
|
||||||
|
try:
|
||||||
|
util.create_templates_for_registration_form(rf) # supprimera les templates obsolètes
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Error cleaning templates for RF %s after master %s group removal: %s", getattr(rf, 'pk', None), getattr(obj, 'pk', None), e)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error while processing RFs for removed groups after master update %s", getattr(obj, 'pk', None))
|
||||||
|
|
||||||
|
# Pour chaque RF appartenant aux groupes ajoutés -> créer les templates manquants
|
||||||
|
if added_group_ids:
|
||||||
|
try:
|
||||||
|
rfs_added = RegistrationForm.objects.filter(fileGroup__in=list(added_group_ids)).distinct()
|
||||||
|
for rf in rfs_added:
|
||||||
|
try:
|
||||||
|
util.create_templates_for_registration_form(rf) # créera les templates manquants
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Error creating templates for RF %s after master %s group addition: %s", getattr(rf, 'pk', None), getattr(obj, 'pk', None), e)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error while processing RFs for added groups after master update %s", getattr(obj, 'pk', None))
|
||||||
|
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
logger.error(f"serializer errors on put: {serializer.errors}")
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Supprime un master de template d'inscription",
|
||||||
|
responses={
|
||||||
|
204: "Suppression réussie",
|
||||||
|
404: "Master non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def delete(self, request, id):
|
||||||
|
master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _columnName='id', _value=id)
|
||||||
|
if master is not None:
|
||||||
|
master.delete()
|
||||||
|
return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
class RegistrationParentFileMasterView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère tous les fichiers parents pour un établissement donné",
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
'establishment_id',
|
||||||
|
openapi.IN_QUERY,
|
||||||
|
description="ID de l'établissement",
|
||||||
|
type=openapi.TYPE_INTEGER,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
],
|
||||||
|
responses={200: RegistrationParentFileMasterSerializer(many=True)}
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
establishment_id = request.GET.get('establishment_id')
|
||||||
|
if not establishment_id:
|
||||||
|
return Response({'error': "Paramètre 'establishment_id' requis"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Filtrer les fichiers parents liés à l'établissement
|
||||||
|
templates = RegistrationParentFileMaster.objects.filter(
|
||||||
|
groups__establishment__id=establishment_id
|
||||||
|
).distinct()
|
||||||
|
serializer = RegistrationParentFileMasterSerializer(templates, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Crée un nouveau fichier parent",
|
||||||
|
request_body=RegistrationParentFileMasterSerializer,
|
||||||
|
responses={
|
||||||
|
201: RegistrationParentFileMasterSerializer,
|
||||||
|
400: "Données invalides"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
serializer = RegistrationParentFileMasterSerializer(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 RegistrationParentFileMasterSimpleView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère un fichier parent spécifique",
|
||||||
|
responses={
|
||||||
|
200: RegistrationParentFileMasterSerializer,
|
||||||
|
404: "Fichier parent non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileMaster, _columnName='id', _value=id)
|
||||||
|
if template is None:
|
||||||
|
return JsonResponse({"errorMessage":'Le fichier parent n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
serializer = RegistrationParentFileMasterSerializer(template)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Met à jour un fichier parent existant",
|
||||||
|
request_body=RegistrationParentFileMasterSerializer,
|
||||||
|
responses={
|
||||||
|
200: RegistrationParentFileMasterSerializer,
|
||||||
|
400: "Données invalides",
|
||||||
|
404: "Fichier parent non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def put(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileMaster, _columnName='id', _value=id)
|
||||||
|
if template is None:
|
||||||
|
return JsonResponse({'erreur': 'Le fichier parent n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
serializer = RegistrationParentFileMasterSerializer(template, data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response({'message': 'Fichier parent 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 parent",
|
||||||
|
responses={
|
||||||
|
204: "Suppression réussie",
|
||||||
|
404: "Fichier parent non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def delete(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileMaster, _columnName='id', _value=id)
|
||||||
|
if template is not None:
|
||||||
|
template.delete()
|
||||||
|
return JsonResponse({'message': 'La suppression du fichier parent a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
else:
|
||||||
|
return JsonResponse({'erreur': 'Le fichier parent n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
class RegistrationParentFileTemplateView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère tous les templates parents pour un établissement donné",
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
'establishment_id',
|
||||||
|
openapi.IN_QUERY,
|
||||||
|
description="ID de l'établissement",
|
||||||
|
type=openapi.TYPE_INTEGER,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
],
|
||||||
|
responses={200: RegistrationParentFileTemplateSerializer(many=True)}
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
establishment_id = request.GET.get('establishment_id')
|
||||||
|
if not establishment_id:
|
||||||
|
return Response({'error': "Paramètre 'establishment_id' requis"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Filtrer les templates parents liés à l'établissement via master.groups.establishment
|
||||||
|
templates = RegistrationParentFileTemplate.objects.filter(
|
||||||
|
master__groups__establishment__id=establishment_id
|
||||||
|
).distinct()
|
||||||
|
serializer = RegistrationParentFileTemplateSerializer(templates, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Crée un nouveau template d'inscription",
|
||||||
|
request_body=RegistrationParentFileTemplateSerializer,
|
||||||
|
responses={
|
||||||
|
201: RegistrationParentFileTemplateSerializer,
|
||||||
|
400: "Données invalides"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
serializer = RegistrationParentFileTemplateSerializer(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 RegistrationParentFileTemplateSimpleView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère un template d'inscription spécifique",
|
||||||
|
responses={
|
||||||
|
200: RegistrationParentFileTemplateSerializer,
|
||||||
|
404: "Template non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileTemplate, _columnName='id', _value=id)
|
||||||
|
if template is None:
|
||||||
|
return JsonResponse({"errorMessage":'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
serializer = RegistrationParentFileTemplateSerializer(template)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Met à jour un template d'inscription existant",
|
||||||
|
request_body=RegistrationParentFileTemplateSerializer,
|
||||||
|
responses={
|
||||||
|
200: RegistrationParentFileTemplateSerializer,
|
||||||
|
400: "Données invalides",
|
||||||
|
404: "Template non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def put(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileTemplate, _columnName='id', _value=id)
|
||||||
|
if template is None:
|
||||||
|
return JsonResponse({'erreur': 'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
serializer = RegistrationParentFileTemplateSerializer(template, data=request.data, partial=True)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response({'message': 'Template 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 template d'inscription",
|
||||||
|
responses={
|
||||||
|
204: "Suppression réussie",
|
||||||
|
404: "Template non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def delete(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileTemplate, _columnName='id', _value=id)
|
||||||
|
if template is not None:
|
||||||
|
template.delete()
|
||||||
|
return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
else:
|
||||||
|
return JsonResponse({'erreur': 'Le template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
@ -5,12 +5,19 @@ from rest_framework.parsers import MultiPartParser, FormParser
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
import json
|
||||||
|
from django.http import QueryDict
|
||||||
|
|
||||||
from Subscriptions.serializers import RegistrationSchoolFileMasterSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileMasterSerializer, RegistrationParentFileTemplateSerializer
|
from Subscriptions.serializers import RegistrationSchoolFileMasterSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileMasterSerializer, RegistrationParentFileTemplateSerializer
|
||||||
from Subscriptions.models import RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster, RegistrationParentFileTemplate
|
from Subscriptions.models import RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster, RegistrationParentFileTemplate
|
||||||
from N3wtSchool import bdd
|
from N3wtSchool import bdd
|
||||||
|
import logging
|
||||||
|
import Subscriptions.util as util
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class RegistrationSchoolFileMasterView(APIView):
|
class RegistrationSchoolFileMasterView(APIView):
|
||||||
|
parser_classes = [MultiPartParser, FormParser]
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère tous les masters de templates d'inscription pour un établissement donné",
|
operation_description="Récupère tous les masters de templates d'inscription pour un établissement donné",
|
||||||
manual_parameters=[
|
manual_parameters=[
|
||||||
@ -45,10 +52,19 @@ class RegistrationSchoolFileMasterView(APIView):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = RegistrationSchoolFileMasterSerializer(data=request.data)
|
logger.info(f"raw request.data: {request.data}")
|
||||||
|
|
||||||
|
payload, resp = util.build_payload_from_request(request)
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
|
|
||||||
|
logger.info(f"payload for serializer: {payload}")
|
||||||
|
serializer = RegistrationSchoolFileMasterSerializer(data=payload, partial=True)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
obj = serializer.save()
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(RegistrationSchoolFileMasterSerializer(obj).data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
logger.error(f"serializer errors: {serializer.errors}")
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
class RegistrationSchoolFileMasterSimpleView(APIView):
|
class RegistrationSchoolFileMasterSimpleView(APIView):
|
||||||
@ -78,11 +94,19 @@ class RegistrationSchoolFileMasterSimpleView(APIView):
|
|||||||
def put(self, request, id):
|
def put(self, request, id):
|
||||||
master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _columnName='id', _value=id)
|
master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _columnName='id', _value=id)
|
||||||
if master is None:
|
if master is None:
|
||||||
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
return JsonResponse({'erreur': "Le master de template n'a pas été trouvé"}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
serializer = RegistrationSchoolFileMasterSerializer(master, data=request.data)
|
|
||||||
|
# Normaliser payload (supporte form-data avec champ 'data' JSON ou fichier JSON)
|
||||||
|
payload, resp = util.build_payload_from_request(request)
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
|
|
||||||
|
logger.info(f"payload for update serializer: {payload}")
|
||||||
|
serializer = RegistrationSchoolFileMasterSerializer(master, data=payload, partial=True)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
logger.error(f"serializer errors on put: {serializer.errors}")
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
@ -96,7 +120,7 @@ class RegistrationSchoolFileMasterSimpleView(APIView):
|
|||||||
master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _columnName='id', _value=id)
|
master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _columnName='id', _value=id)
|
||||||
if master is not None:
|
if master is not None:
|
||||||
master.delete()
|
master.delete()
|
||||||
return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_200_OK)
|
||||||
else:
|
else:
|
||||||
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
Binary file not shown.
@ -42,14 +42,9 @@ const nextConfig = {
|
|||||||
NEXT_PUBLIC_USE_FAKE_DATA: process.env.NEXT_PUBLIC_USE_FAKE_DATA || 'false',
|
NEXT_PUBLIC_USE_FAKE_DATA: process.env.NEXT_PUBLIC_USE_FAKE_DATA || 'false',
|
||||||
AUTH_SECRET: process.env.AUTH_SECRET || 'false',
|
AUTH_SECRET: process.env.AUTH_SECRET || 'false',
|
||||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL || 'http://localhost:3000',
|
NEXTAUTH_URL: process.env.NEXTAUTH_URL || 'http://localhost:3000',
|
||||||
DOCUSEAL_API_KEY: process.env.DOCUSEAL_API_KEY,
|
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
return [
|
return [
|
||||||
{
|
|
||||||
source: '/api/documents/:path*',
|
|
||||||
destination: 'https://api.docuseal.com/v1/documents/:path*',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
source: '/api/auth/:path*',
|
source: '/api/auth/:path*',
|
||||||
destination: '/api/auth/:path*', // Exclure les routes NextAuth des réécritures de proxy
|
destination: '/api/auth/:path*', // Exclure les routes NextAuth des réécritures de proxy
|
||||||
|
|||||||
11
Front-End/package-lock.json
generated
11
Front-End/package-lock.json
generated
@ -8,7 +8,6 @@
|
|||||||
"name": "n3wt-school-front-end",
|
"name": "n3wt-school-front-end",
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docuseal/react": "^1.0.56",
|
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@ -537,11 +536,6 @@
|
|||||||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@docuseal/react": {
|
|
||||||
"version": "1.0.66",
|
|
||||||
"resolved": "https://registry.npmjs.org/@docuseal/react/-/react-1.0.66.tgz",
|
|
||||||
"integrity": "sha512-rYG58gv8Uw1cTtjbHdgWgWBWpLMbIwDVsS3kN27w4sz/eDJilZieePUDS4eLKJ8keBN05BSjxD/iWQpaTBKZLg=="
|
|
||||||
},
|
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
||||||
@ -11269,11 +11263,6 @@
|
|||||||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@docuseal/react": {
|
|
||||||
"version": "1.0.66",
|
|
||||||
"resolved": "https://registry.npmjs.org/@docuseal/react/-/react-1.0.66.tgz",
|
|
||||||
"integrity": "sha512-rYG58gv8Uw1cTtjbHdgWgWBWpLMbIwDVsS3kN27w4sz/eDJilZieePUDS4eLKJ8keBN05BSjxD/iWQpaTBKZLg=="
|
|
||||||
},
|
|
||||||
"@eslint-community/eslint-utils": {
|
"@eslint-community/eslint-utils": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
"test:coverage": "jest --coverage"
|
"test:coverage": "jest --coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docuseal/react": "^1.0.56",
|
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
|||||||
@ -36,7 +36,6 @@ export default function DashboardPage() {
|
|||||||
const {
|
const {
|
||||||
selectedEstablishmentId,
|
selectedEstablishmentId,
|
||||||
selectedEstablishmentTotalCapacity,
|
selectedEstablishmentTotalCapacity,
|
||||||
apiDocuseal,
|
|
||||||
} = useEstablishment();
|
} = useEstablishment();
|
||||||
|
|
||||||
const [statusDistribution, setStatusDistribution] = useState([
|
const [statusDistribution, setStatusDistribution] = useState([
|
||||||
@ -165,25 +164,6 @@ export default function DashboardPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={selectedEstablishmentId} className="p-6">
|
<div key={selectedEstablishmentId} className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-6">
|
|
||||||
<span
|
|
||||||
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold ${
|
|
||||||
apiDocuseal
|
|
||||||
? 'bg-green-100 text-green-700 border border-green-300'
|
|
||||||
: 'bg-red-100 text-red-700 border border-red-300'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{apiDocuseal ? (
|
|
||||||
<CheckCircle2 className="w-4 h-4 mr-2 text-green-500" />
|
|
||||||
) : (
|
|
||||||
<AlertTriangle className="w-4 h-4 mr-2 text-red-500" />
|
|
||||||
)}
|
|
||||||
{apiDocuseal
|
|
||||||
? 'Clé API Docuseal renseignée'
|
|
||||||
: 'Clé API Docuseal manquante'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Statistiques principales */}
|
{/* Statistiques principales */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||||
<StatCard
|
<StatCard
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export default function Page() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEstablishmentId) {
|
if (selectedEstablishmentId) {
|
||||||
@ -353,7 +353,6 @@ export default function Page() {
|
|||||||
<FilesGroupsManagement
|
<FilesGroupsManagement
|
||||||
csrfToken={csrfToken}
|
csrfToken={csrfToken}
|
||||||
selectedEstablishmentId={selectedEstablishmentId}
|
selectedEstablishmentId={selectedEstablishmentId}
|
||||||
apiDocuseal={apiDocuseal}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -35,7 +35,6 @@ import {
|
|||||||
fetchRegistrationFileGroups,
|
fetchRegistrationFileGroups,
|
||||||
fetchRegistrationSchoolFileMasters,
|
fetchRegistrationSchoolFileMasters,
|
||||||
fetchRegistrationParentFileMasters,
|
fetchRegistrationParentFileMasters,
|
||||||
cloneTemplate,
|
|
||||||
createRegistrationSchoolFileTemplate,
|
createRegistrationSchoolFileTemplate,
|
||||||
createRegistrationParentFileTemplate,
|
createRegistrationParentFileTemplate,
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
@ -96,7 +95,7 @@ export default function CreateSubscriptionPage() {
|
|||||||
const { getNiveauLabel } = useClasses();
|
const { getNiveauLabel } = useClasses();
|
||||||
|
|
||||||
const formDataRef = useRef(formData);
|
const formDataRef = useRef(formData);
|
||||||
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -522,101 +521,7 @@ export default function CreateSubscriptionPage() {
|
|||||||
} else {
|
} else {
|
||||||
// Création du dossier d'inscription
|
// Création du dossier d'inscription
|
||||||
createRegisterForm(data, csrfToken)
|
createRegisterForm(data, csrfToken)
|
||||||
.then((data) => {
|
.then((response) => {
|
||||||
// Clonage des schoolFileTemplates
|
|
||||||
const masters = schoolFileMasters.filter((file) =>
|
|
||||||
file.groups.includes(selectedFileGroup)
|
|
||||||
);
|
|
||||||
const parentMasters = parentFileMasters.filter((file) =>
|
|
||||||
file.groups.includes(selectedFileGroup)
|
|
||||||
);
|
|
||||||
|
|
||||||
const clonePromises = masters.map((templateMaster) =>
|
|
||||||
cloneTemplate(
|
|
||||||
templateMaster.id,
|
|
||||||
formDataRef.current.guardianEmail,
|
|
||||||
templateMaster.is_required,
|
|
||||||
selectedEstablishmentId,
|
|
||||||
apiDocuseal
|
|
||||||
)
|
|
||||||
.then((clonedDocument) => {
|
|
||||||
const cloneData = {
|
|
||||||
name: `${templateMaster.name}_${formDataRef.current.studentFirstName}_${formDataRef.current.studentLastName}`,
|
|
||||||
slug: clonedDocument.slug,
|
|
||||||
id: clonedDocument.id,
|
|
||||||
master: templateMaster.id,
|
|
||||||
registration_form: data.student.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
return createRegistrationSchoolFileTemplate(
|
|
||||||
cloneData,
|
|
||||||
csrfToken
|
|
||||||
)
|
|
||||||
.then((response) =>
|
|
||||||
logger.debug('Template enregistré avec succès:', response)
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
setIsLoading(false);
|
|
||||||
logger.error(
|
|
||||||
"Erreur lors de l'enregistrement du template:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
showNotification(
|
|
||||||
"Erreur lors de la création du dossier d'inscription",
|
|
||||||
'error',
|
|
||||||
'Erreur',
|
|
||||||
'ERR_ADM_SUB_03'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setIsLoading(false);
|
|
||||||
logger.error('Error during cloning or sending:', error);
|
|
||||||
showNotification(
|
|
||||||
"Erreur lors de la création du dossier d'inscription",
|
|
||||||
'error',
|
|
||||||
'Erreur',
|
|
||||||
'ERR_ADM_SUB_05'
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clonage des parentFileTemplates
|
|
||||||
const parentClonePromises = parentMasters.map((parentMaster) => {
|
|
||||||
const parentTemplateData = {
|
|
||||||
master: parentMaster.id,
|
|
||||||
registration_form: data.student.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
return createRegistrationParentFileTemplate(
|
|
||||||
parentTemplateData,
|
|
||||||
csrfToken
|
|
||||||
)
|
|
||||||
.then((response) =>
|
|
||||||
logger.debug(
|
|
||||||
'Parent template enregistré avec succès:',
|
|
||||||
response
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
setIsLoading(false);
|
|
||||||
logger.error(
|
|
||||||
"Erreur lors de l'enregistrement du parent template:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
showNotification(
|
|
||||||
"Erreur lors de la création du dossier d'inscription",
|
|
||||||
'error',
|
|
||||||
'Erreur',
|
|
||||||
'ERR_ADM_SUB_02'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Attendre que tous les clones soient créés
|
|
||||||
Promise.all([...clonePromises, ...parentClonePromises])
|
|
||||||
.then(() => {
|
|
||||||
// Redirection après succès
|
|
||||||
showNotification(
|
showNotification(
|
||||||
"Dossier d'inscription créé avec succès",
|
"Dossier d'inscription créé avec succès",
|
||||||
'success',
|
'success',
|
||||||
@ -626,24 +531,13 @@ export default function CreateSubscriptionPage() {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
showNotification(
|
logger.error('Erreur lors de la mise à jour du dossier:', error);
|
||||||
"Erreur lors de la création du dossier d'inscription",
|
|
||||||
'error',
|
|
||||||
'Erreur',
|
|
||||||
'ERR_ADM_SUB_04'
|
|
||||||
);
|
|
||||||
logger.error('Error during cloning or sending:', error);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setIsLoading(false);
|
|
||||||
showNotification(
|
showNotification(
|
||||||
"Erreur lors de la création du dossier d'inscription",
|
"Erreur lors de la création du dossier d'inscription",
|
||||||
'error',
|
'error',
|
||||||
'Erreur',
|
'Erreur',
|
||||||
'ERR_ADM_SUB_01'
|
'ERR_ADM_SUB_01'
|
||||||
);
|
);
|
||||||
logger.error('Error during register form creation:', error);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const [formErrors, setFormErrors] = useState({});
|
const [formErrors, setFormErrors] = useState({});
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = (data) => {
|
const handleSubmit = (data) => {
|
||||||
@ -59,7 +59,6 @@ export default function Page() {
|
|||||||
studentId={studentId}
|
studentId={studentId}
|
||||||
csrfToken={csrfToken}
|
csrfToken={csrfToken}
|
||||||
selectedEstablishmentId={selectedEstablishmentId}
|
selectedEstablishmentId={selectedEstablishmentId}
|
||||||
apiDocuseal = {apiDocuseal}
|
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
cancelUrl={FE_ADMIN_SUBSCRIPTIONS_URL}
|
cancelUrl={FE_ADMIN_SUBSCRIPTIONS_URL}
|
||||||
errors={formErrors}
|
errors={formErrors}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export default function Page() {
|
|||||||
const enable = searchParams.get('enabled') === 'true';
|
const enable = searchParams.get('enabled') === 'true';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = (data) => {
|
const handleSubmit = (data) => {
|
||||||
@ -53,7 +53,6 @@ export default function Page() {
|
|||||||
studentId={studentId}
|
studentId={studentId}
|
||||||
csrfToken={csrfToken}
|
csrfToken={csrfToken}
|
||||||
selectedEstablishmentId={selectedEstablishmentId}
|
selectedEstablishmentId={selectedEstablishmentId}
|
||||||
apiDocuseal = {apiDocuseal}
|
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
cancelUrl={FE_PARENTS_HOME_URL}
|
cancelUrl={FE_PARENTS_HOME_URL}
|
||||||
enable={enable}
|
enable={enable}
|
||||||
|
|||||||
@ -3,11 +3,7 @@ import {
|
|||||||
BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL,
|
BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL,
|
||||||
BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL,
|
BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL,
|
||||||
BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL,
|
BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL,
|
||||||
BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL,
|
BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL
|
||||||
FE_API_DOCUSEAL_CLONE_URL,
|
|
||||||
FE_API_DOCUSEAL_DOWNLOAD_URL,
|
|
||||||
FE_API_DOCUSEAL_GENERATE_TOKEN,
|
|
||||||
FE_API_DOCUSEAL_DELETE_URL
|
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
import { errorHandler, requestResponseHandler } from './actionsHandlers';
|
import { errorHandler, requestResponseHandler } from './actionsHandlers';
|
||||||
|
|
||||||
@ -327,62 +323,3 @@ export const deleteRegistrationParentFileTemplate = (id, csrfToken) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// API requests
|
|
||||||
export const removeTemplate = (templateId, selectedEstablishmentId, apiDocuseal) => {
|
|
||||||
return fetch(`${FE_API_DOCUSEAL_DELETE_URL}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
templateId,
|
|
||||||
establishment_id :selectedEstablishmentId,
|
|
||||||
apiDocuseal
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then(requestResponseHandler)
|
|
||||||
.catch(errorHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const cloneTemplate = (templateId, email, is_required, selectedEstablishmentId, apiDocuseal) => {
|
|
||||||
return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
templateId,
|
|
||||||
email,
|
|
||||||
is_required,
|
|
||||||
establishment_id :selectedEstablishmentId,
|
|
||||||
apiDocuseal
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then(requestResponseHandler)
|
|
||||||
.catch(errorHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const downloadTemplate = (slug, selectedEstablishmentId, apiDocuseal) => {
|
|
||||||
const url = `${FE_API_DOCUSEAL_DOWNLOAD_URL}/${slug}?establishment_id=${selectedEstablishmentId}&apiDocuseal=${apiDocuseal}`;
|
|
||||||
return fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(requestResponseHandler)
|
|
||||||
.catch(errorHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const generateToken = (email, id = null, selectedEstablishmentId, apiDocuseal) => {
|
|
||||||
return fetch(`${FE_API_DOCUSEAL_GENERATE_TOKEN}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ user_email: email, id, establishment_id :selectedEstablishmentId, apiDocuseal }),
|
|
||||||
})
|
|
||||||
.then(requestResponseHandler)
|
|
||||||
.catch(errorHandler);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -24,8 +24,7 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
|
|||||||
setSelectedEstablishmentEvaluationFrequency,
|
setSelectedEstablishmentEvaluationFrequency,
|
||||||
setSelectedEstablishmentTotalCapacity,
|
setSelectedEstablishmentTotalCapacity,
|
||||||
selectedEstablishmentLogo,
|
selectedEstablishmentLogo,
|
||||||
setSelectedEstablishmentLogo,
|
setSelectedEstablishmentLogo
|
||||||
setApiDocuseal
|
|
||||||
} = useEstablishment();
|
} = useEstablishment();
|
||||||
const { isConnected, connectionStatus } = useChatConnection();
|
const { isConnected, connectionStatus } = useChatConnection();
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
@ -41,8 +40,6 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
|
|||||||
user.roles[roleId].establishment__total_capacity;
|
user.roles[roleId].establishment__total_capacity;
|
||||||
const establishmentLogo =
|
const establishmentLogo =
|
||||||
user.roles[roleId].establishment__logo;
|
user.roles[roleId].establishment__logo;
|
||||||
const establishmentApiDocuseal =
|
|
||||||
user.roles[roleId].establishment__api_docuseal;
|
|
||||||
setProfileRole(role);
|
setProfileRole(role);
|
||||||
setSelectedEstablishmentId(establishmentId);
|
setSelectedEstablishmentId(establishmentId);
|
||||||
setSelectedEstablishmentEvaluationFrequency(
|
setSelectedEstablishmentEvaluationFrequency(
|
||||||
@ -50,7 +47,6 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
|
|||||||
);
|
);
|
||||||
setSelectedEstablishmentTotalCapacity(establishmentTotalCapacity);
|
setSelectedEstablishmentTotalCapacity(establishmentTotalCapacity);
|
||||||
setSelectedEstablishmentLogo(establishmentLogo);
|
setSelectedEstablishmentLogo(establishmentLogo);
|
||||||
setApiDocuseal(establishmentApiDocuseal);
|
|
||||||
setSelectedRoleId(roleId);
|
setSelectedRoleId(roleId);
|
||||||
if (onRoleChange) {
|
if (onRoleChange) {
|
||||||
onRoleChange(roleId);
|
onRoleChange(roleId);
|
||||||
|
|||||||
@ -2,10 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import {
|
import {
|
||||||
fetchRegistrationFileGroups,
|
fetchRegistrationFileGroups,
|
||||||
createRegistrationSchoolFileTemplate,
|
createRegistrationSchoolFileTemplate,
|
||||||
cloneTemplate,
|
|
||||||
generateToken,
|
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
import { DocusealBuilder } from '@docuseal/react';
|
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import MultiSelect from '@/components/Form/MultiSelect'; // Import du composant MultiSelect
|
import MultiSelect from '@/components/Form/MultiSelect'; // Import du composant MultiSelect
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
@ -19,18 +16,13 @@ export default function FileUploadDocuSeal({
|
|||||||
onSuccess,
|
onSuccess,
|
||||||
}) {
|
}) {
|
||||||
const [groups, setGroups] = useState([]);
|
const [groups, setGroups] = useState([]);
|
||||||
const [token, setToken] = useState(null);
|
|
||||||
const [templateMaster, setTemplateMaster] = useState(null);
|
const [templateMaster, setTemplateMaster] = useState(null);
|
||||||
const [uploadedFileName, setUploadedFileName] = useState('');
|
const [uploadedFileName, setUploadedFileName] = useState('');
|
||||||
const [selectedGroups, setSelectedGroups] = useState([]);
|
const [selectedGroups, setSelectedGroups] = useState([]);
|
||||||
const [guardianDetails, setGuardianDetails] = useState([]);
|
|
||||||
|
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
const [popupMessage, setPopupMessage] = useState('');
|
const [popupMessage, setPopupMessage] = useState('');
|
||||||
|
|
||||||
const csrfToken = useCsrfToken();
|
const { selectedEstablishmentId, user } = useEstablishment();
|
||||||
|
|
||||||
const { selectedEstablishmentId, user, apiDocuseal } = useEstablishment();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchRegistrationFileGroups(selectedEstablishmentId).then((data) =>
|
fetchRegistrationFileGroups(selectedEstablishmentId).then((data) =>
|
||||||
@ -47,31 +39,10 @@ export default function FileUploadDocuSeal({
|
|||||||
if (!user && !user?.email) {
|
if (!user && !user?.email) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = fileToEdit ? fileToEdit.id : null;
|
|
||||||
|
|
||||||
generateToken(user?.email, id, selectedEstablishmentId, apiDocuseal)
|
|
||||||
.then((data) => {
|
|
||||||
setToken(data.token);
|
|
||||||
})
|
|
||||||
.catch((error) =>
|
|
||||||
logger.error('Erreur lors de la génération du token:', error)
|
|
||||||
);
|
|
||||||
}, [fileToEdit]);
|
}, [fileToEdit]);
|
||||||
|
|
||||||
const handleGroupChange = (selectedGroups) => {
|
const handleGroupChange = (selectedGroups) => {
|
||||||
setSelectedGroups(selectedGroups);
|
setSelectedGroups(selectedGroups);
|
||||||
|
|
||||||
const details = selectedGroups.flatMap((group) =>
|
|
||||||
group.registration_forms.flatMap((form) =>
|
|
||||||
form.guardians.map((guardian) => ({
|
|
||||||
email: guardian.associated_profile_email,
|
|
||||||
last_name: form.last_name,
|
|
||||||
first_name: form.first_name,
|
|
||||||
registration_form: form.student_id,
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
setGuardianDetails(details); // Mettre à jour la variable d'état avec les détails des guardians
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoad = (detail) => {
|
const handleLoad = (detail) => {
|
||||||
@ -118,45 +89,6 @@ export default function FileUploadDocuSeal({
|
|||||||
id: templateMaster?.id,
|
id: templateMaster?.id,
|
||||||
is_required: is_required,
|
is_required: is_required,
|
||||||
});
|
});
|
||||||
|
|
||||||
guardianDetails.forEach((guardian, index) => {
|
|
||||||
logger.debug('creation du clone avec required : ', is_required);
|
|
||||||
cloneTemplate(
|
|
||||||
templateMaster?.id,
|
|
||||||
guardian.email,
|
|
||||||
is_required,
|
|
||||||
selectedEstablishmentId,
|
|
||||||
apiDocuseal
|
|
||||||
)
|
|
||||||
.then((clonedDocument) => {
|
|
||||||
// Sauvegarde des schoolFileTemplates clonés dans la base de données
|
|
||||||
const data = {
|
|
||||||
name: `${uploadedFileName}_${guardian.first_name}_${guardian.last_name}`,
|
|
||||||
slug: clonedDocument.slug,
|
|
||||||
id: clonedDocument.id,
|
|
||||||
master: templateMaster?.id,
|
|
||||||
registration_form: guardian.registration_form,
|
|
||||||
};
|
|
||||||
logger.debug('creation : ', data);
|
|
||||||
createRegistrationSchoolFileTemplate(data, csrfToken)
|
|
||||||
.then((response) => {
|
|
||||||
logger.debug('Template enregistré avec succès:', response);
|
|
||||||
onSuccess();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error(
|
|
||||||
"Erreur lors de l'enregistrement du template:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Logique pour envoyer chaque template au submitter
|
|
||||||
logger.debug('Sending template to:', guardian.email);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error('Error during cloning or sending:', error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -212,32 +144,7 @@ export default function FileUploadDocuSeal({
|
|||||||
|
|
||||||
{/* Zone de configuration des documents */}
|
{/* Zone de configuration des documents */}
|
||||||
<div className="col-span-8 bg-white p-6 rounded-lg shadow-md border border-gray-200">
|
<div className="col-span-8 bg-white p-6 rounded-lg shadow-md border border-gray-200">
|
||||||
{token && (
|
|
||||||
<div className="h-full overflow-auto">
|
|
||||||
{/* Description de l'étape */}
|
|
||||||
<p className="text-gray-700 text-base font-medium mb-4">
|
|
||||||
Étape 2 - Sélectionnez un document
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<DocusealBuilder
|
|
||||||
token={token}
|
|
||||||
headers={{
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
}}
|
|
||||||
withSendButton={false}
|
|
||||||
withSignYourselfButton={false}
|
|
||||||
autosave={false}
|
|
||||||
withDocumentsList={false}
|
|
||||||
language={'fr'}
|
|
||||||
onLoad={handleLoad}
|
|
||||||
onUpload={handleUpload}
|
|
||||||
onChange={handleChange}
|
|
||||||
onSave={handleSubmit}
|
|
||||||
className="h-full overflow-auto"
|
|
||||||
style={{ maxHeight: '65vh' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Download, Edit3, Trash2, FolderPlus, Signature, AlertTriangle } from 'lucide-react';
|
import { Download, Edit3, Trash2, FolderPlus, Signature, AlertTriangle } from 'lucide-react';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import FileUploadDocuSeal from '@/components/Structure/Files/FileUploadDocuSeal';
|
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import {
|
import {
|
||||||
// GET
|
// GET
|
||||||
@ -21,9 +20,7 @@ import {
|
|||||||
// DELETE
|
// DELETE
|
||||||
deleteRegistrationFileGroup,
|
deleteRegistrationFileGroup,
|
||||||
deleteRegistrationSchoolFileMaster,
|
deleteRegistrationSchoolFileMaster,
|
||||||
deleteRegistrationParentFileMaster,
|
deleteRegistrationParentFileMaster
|
||||||
|
|
||||||
removeTemplate
|
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
|
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
@ -33,11 +30,11 @@ import Popup from '@/components/Popup';
|
|||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
import { useNotification } from '@/context/NotificationContext';
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
import AlertMessage from '@/components/AlertMessage';
|
import AlertMessage from '@/components/AlertMessage';
|
||||||
|
import FileUploadDocuSeal from '@/components/Structure/Files/FileUploadDocuSeal';
|
||||||
|
|
||||||
export default function FilesGroupsManagement({
|
export default function FilesGroupsManagement({
|
||||||
csrfToken,
|
csrfToken,
|
||||||
selectedEstablishmentId,
|
selectedEstablishmentId
|
||||||
apiDocuseal
|
|
||||||
}) {
|
}) {
|
||||||
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
|
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
|
||||||
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
|
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
|
||||||
@ -116,23 +113,6 @@ export default function FilesGroupsManagement({
|
|||||||
);
|
);
|
||||||
setRemovePopupOnConfirm(() => () => {
|
setRemovePopupOnConfirm(() => () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
// Supprimer les clones associés via l'API DocuSeal
|
|
||||||
const removeClonesPromises = [
|
|
||||||
...schoolFileTemplates
|
|
||||||
.filter((template) => template.master === templateMaster.id)
|
|
||||||
.map((template) =>
|
|
||||||
removeTemplate(template.id, selectedEstablishmentId, apiDocuseal)
|
|
||||||
),
|
|
||||||
removeTemplate(templateMaster.id, selectedEstablishmentId, apiDocuseal),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Attendre que toutes les suppressions dans DocuSeal soient terminées
|
|
||||||
Promise.all(removeClonesPromises)
|
|
||||||
.then((responses) => {
|
|
||||||
const allSuccessful = responses.every((response) => response && response.id);
|
|
||||||
if (allSuccessful) {
|
|
||||||
logger.debug('Master et clones supprimés avec succès de DocuSeal.');
|
|
||||||
|
|
||||||
// Supprimer le template master de la base de données
|
// Supprimer le template master de la base de données
|
||||||
deleteRegistrationSchoolFileMaster(templateMaster.id, csrfToken)
|
deleteRegistrationSchoolFileMaster(templateMaster.id, csrfToken)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@ -170,26 +150,6 @@ export default function FilesGroupsManagement({
|
|||||||
setRemovePopupVisible(false);
|
setRemovePopupVisible(false);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
showNotification(
|
|
||||||
`Erreur lors de la suppression du document "${templateMaster.name}".`,
|
|
||||||
'error',
|
|
||||||
'Erreur'
|
|
||||||
);
|
|
||||||
setRemovePopupVisible(false);
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error('Error removing template from DocuSeal:', error);
|
|
||||||
showNotification(
|
|
||||||
`Erreur lors de la suppression du document "${templateMaster.name}".`,
|
|
||||||
'error',
|
|
||||||
'Erreur'
|
|
||||||
);
|
|
||||||
setRemovePopupVisible(false);
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -542,25 +502,13 @@ export default function FilesGroupsManagement({
|
|||||||
icon={Signature}
|
icon={Signature}
|
||||||
title="Formulaires à remplir"
|
title="Formulaires à remplir"
|
||||||
description="Gérez les formulaires nécessitant une signature électronique."
|
description="Gérez les formulaires nécessitant une signature électronique."
|
||||||
button={apiDocuseal}
|
button={true}
|
||||||
buttonOpeningModal={true}
|
buttonOpeningModal={true}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="mb-4">
|
|
||||||
<span
|
|
||||||
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold ${
|
|
||||||
!apiDocuseal && 'bg-red-100 text-red-700 border border-red-300'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{!apiDocuseal && (
|
|
||||||
<AlertTriangle className="w-4 h-4 mr-2 text-red-500" />
|
|
||||||
)}
|
|
||||||
{!apiDocuseal && 'Clé API Docuseal manquante'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Table
|
<Table
|
||||||
data={filteredFiles}
|
data={filteredFiles}
|
||||||
columns={columnsFiles}
|
columns={columnsFiles}
|
||||||
|
|||||||
@ -46,10 +46,6 @@ export const EstablishmentProvider = ({ children }) => {
|
|||||||
const storedUser = sessionStorage.getItem('user');
|
const storedUser = sessionStorage.getItem('user');
|
||||||
return storedUser ? JSON.parse(storedUser) : null;
|
return storedUser ? JSON.parse(storedUser) : null;
|
||||||
});
|
});
|
||||||
const [apiDocuseal, setApiDocusealState] = useState(() => {
|
|
||||||
const storedApiDocuseal = sessionStorage.getItem('apiDocuseal');
|
|
||||||
return storedApiDocuseal ? JSON.parse(storedApiDocuseal) : null;
|
|
||||||
});
|
|
||||||
const [selectedEstablishmentLogo, setSelectedEstablishmentLogoState] = useState(() => {
|
const [selectedEstablishmentLogo, setSelectedEstablishmentLogoState] = useState(() => {
|
||||||
const storedLogo = sessionStorage.getItem('selectedEstablishmentLogo');
|
const storedLogo = sessionStorage.getItem('selectedEstablishmentLogo');
|
||||||
return storedLogo ? JSON.parse(storedLogo) : null;
|
return storedLogo ? JSON.parse(storedLogo) : null;
|
||||||
@ -94,11 +90,6 @@ export const EstablishmentProvider = ({ children }) => {
|
|||||||
sessionStorage.setItem('user', JSON.stringify(user));
|
sessionStorage.setItem('user', JSON.stringify(user));
|
||||||
};
|
};
|
||||||
|
|
||||||
const setApiDocuseal = (api) => {
|
|
||||||
setApiDocusealState(api);
|
|
||||||
sessionStorage.setItem('apiDocuseal', JSON.stringify(api));
|
|
||||||
};
|
|
||||||
|
|
||||||
const setSelectedEstablishmentLogo = (logo) => {
|
const setSelectedEstablishmentLogo = (logo) => {
|
||||||
setSelectedEstablishmentLogoState(logo);
|
setSelectedEstablishmentLogoState(logo);
|
||||||
sessionStorage.setItem('selectedEstablishmentLogo', JSON.stringify(logo));
|
sessionStorage.setItem('selectedEstablishmentLogo', JSON.stringify(logo));
|
||||||
@ -122,7 +113,6 @@ export const EstablishmentProvider = ({ children }) => {
|
|||||||
name: role.establishment__name,
|
name: role.establishment__name,
|
||||||
evaluation_frequency: role.establishment__evaluation_frequency,
|
evaluation_frequency: role.establishment__evaluation_frequency,
|
||||||
total_capacity: role.establishment__total_capacity,
|
total_capacity: role.establishment__total_capacity,
|
||||||
api_docuseal: role.establishment__api_docuseal,
|
|
||||||
logo: role.establishment__logo,
|
logo: role.establishment__logo,
|
||||||
role_id: i,
|
role_id: i,
|
||||||
role_type: role.role_type,
|
role_type: role.role_type,
|
||||||
@ -143,9 +133,6 @@ export const EstablishmentProvider = ({ children }) => {
|
|||||||
setSelectedEstablishmentTotalCapacity(
|
setSelectedEstablishmentTotalCapacity(
|
||||||
userEstablishments[roleIndexDefault].total_capacity
|
userEstablishments[roleIndexDefault].total_capacity
|
||||||
);
|
);
|
||||||
setApiDocuseal(
|
|
||||||
userEstablishments[roleIndexDefault].api_docuseal
|
|
||||||
);
|
|
||||||
setSelectedEstablishmentLogo(
|
setSelectedEstablishmentLogo(
|
||||||
userEstablishments[roleIndexDefault].logo
|
userEstablishments[roleIndexDefault].logo
|
||||||
);
|
);
|
||||||
@ -168,7 +155,6 @@ export const EstablishmentProvider = ({ children }) => {
|
|||||||
setUserState(null);
|
setUserState(null);
|
||||||
setSelectedEstablishmentEvaluationFrequencyState(null);
|
setSelectedEstablishmentEvaluationFrequencyState(null);
|
||||||
setSelectedEstablishmentTotalCapacityState(null);
|
setSelectedEstablishmentTotalCapacityState(null);
|
||||||
setApiDocusealState(null);
|
|
||||||
setSelectedEstablishmentLogoState(null);
|
setSelectedEstablishmentLogoState(null);
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
};
|
};
|
||||||
@ -184,8 +170,6 @@ export const EstablishmentProvider = ({ children }) => {
|
|||||||
setSelectedEstablishmentEvaluationFrequency,
|
setSelectedEstablishmentEvaluationFrequency,
|
||||||
selectedEstablishmentTotalCapacity,
|
selectedEstablishmentTotalCapacity,
|
||||||
setSelectedEstablishmentTotalCapacity,
|
setSelectedEstablishmentTotalCapacity,
|
||||||
apiDocuseal,
|
|
||||||
setApiDocuseal,
|
|
||||||
selectedEstablishmentLogo,
|
selectedEstablishmentLogo,
|
||||||
setSelectedEstablishmentLogo,
|
setSelectedEstablishmentLogo,
|
||||||
selectedRoleId,
|
selectedRoleId,
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
import logger from '@/utils/logger';
|
|
||||||
import { BE_DOCUSEAL_CLONE_TEMPLATE } from '@/utils/Url';
|
|
||||||
|
|
||||||
export default function handler(req, res) {
|
|
||||||
if (req.method === 'POST') {
|
|
||||||
const { templateId, email, is_required, establishment_id, apiDocuseal } = req.body;
|
|
||||||
|
|
||||||
fetch(BE_DOCUSEAL_CLONE_TEMPLATE, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Auth-Token': apiDocuseal,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
templateId,
|
|
||||||
email,
|
|
||||||
is_required,
|
|
||||||
establishment_id,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.ok) {
|
|
||||||
return response.json().then((err) => {
|
|
||||||
throw new Error(err.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
logger.debug('Template cloned successfully:', data);
|
|
||||||
res.status(200).json(data);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error('Error cloning template:', error);
|
|
||||||
res.status(500).json({ error: 'Internal Server Error' });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.setHeader('Allow', ['POST']);
|
|
||||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import logger from '@/utils/logger';
|
|
||||||
import { BE_DOCUSEAL_DOWNLOAD_TEMPLATE } from '@/utils/Url';
|
|
||||||
|
|
||||||
export default function handler(req, res) {
|
|
||||||
if (req.method === 'GET') {
|
|
||||||
const { slug, establishment_id, apiDocuseal } = req.query;
|
|
||||||
logger.debug('slug : ', slug);
|
|
||||||
|
|
||||||
fetch(`${BE_DOCUSEAL_DOWNLOAD_TEMPLATE}/${slug}?establishment_id=${establishment_id}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'X-Auth-Token': apiDocuseal,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.ok) {
|
|
||||||
return response.json().then((err) => {
|
|
||||||
throw new Error(err.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
logger.debug('Template downloaded successfully:', data);
|
|
||||||
res.status(200).json(data);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error('Error downloading template:', error);
|
|
||||||
res.status(500).json({ error: 'Internal Server Error' });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.setHeader('Allow', ['GET']);
|
|
||||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import logger from '@/utils/logger';
|
|
||||||
import { BE_DOCUSEAL_GET_JWT } from '@/utils/Url';
|
|
||||||
|
|
||||||
export default function handler(req, res) {
|
|
||||||
if (req.method === 'POST') {
|
|
||||||
const { apiDocuseal, ...rest } = req.body;
|
|
||||||
|
|
||||||
fetch(BE_DOCUSEAL_GET_JWT, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Auth-Token': apiDocuseal,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(rest),
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
logger.debug('Response status:', response.status);
|
|
||||||
return response
|
|
||||||
.json()
|
|
||||||
.then((data) => ({ status: response.status, data }));
|
|
||||||
})
|
|
||||||
.then(({ status, data }) => {
|
|
||||||
logger.debug('Response data:', data);
|
|
||||||
res.status(status).json(data);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error('Error:', error);
|
|
||||||
res.status(500).json({ error: 'Internal Server Error' });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.setHeader('Allow', ['POST']);
|
|
||||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import logger from '@/utils/logger';
|
|
||||||
import { BE_DOCUSEAL_REMOVE_TEMPLATE } from '@/utils/Url';
|
|
||||||
|
|
||||||
export default function handler(req, res) {
|
|
||||||
if (req.method === 'DELETE') {
|
|
||||||
const { templateId, establishment_id, apiDocuseal } = req.body;
|
|
||||||
|
|
||||||
fetch(`${BE_DOCUSEAL_REMOVE_TEMPLATE}/${templateId}?establishment_id=${establishment_id}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'X-Auth-Token': apiDocuseal,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.ok) {
|
|
||||||
return response.json().then((err) => {
|
|
||||||
throw new Error(err.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
logger.debug('Template removed successfully:', data);
|
|
||||||
res.status(200).json(data);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error('Error removing template:', error);
|
|
||||||
res.status(500).json({ error: 'Internal Server Error' });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.setHeader('Allow', ['DELETE']);
|
|
||||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,12 +4,6 @@ export const WS_BASE_URL = process.env.NEXT_PUBLIC_WSAPI_URL;
|
|||||||
|
|
||||||
//URL-Back-End
|
//URL-Back-End
|
||||||
|
|
||||||
// GESTION DocuSeal
|
|
||||||
export const BE_DOCUSEAL_GET_JWT = `${BASE_URL}/DocuSeal/generateToken`;
|
|
||||||
export const BE_DOCUSEAL_CLONE_TEMPLATE = `${BASE_URL}/DocuSeal/cloneTemplate`;
|
|
||||||
export const BE_DOCUSEAL_REMOVE_TEMPLATE = `${BASE_URL}/DocuSeal/removeTemplate`;
|
|
||||||
export const BE_DOCUSEAL_DOWNLOAD_TEMPLATE = `${BASE_URL}/DocuSeal/downloadTemplate`;
|
|
||||||
|
|
||||||
// GESTION LOGIN
|
// GESTION LOGIN
|
||||||
export const BE_AUTH_NEW_PASSWORD_URL = `${BASE_URL}/Auth/newPassword`;
|
export const BE_AUTH_NEW_PASSWORD_URL = `${BASE_URL}/Auth/newPassword`;
|
||||||
export const BE_AUTH_REGISTER_URL = `${BASE_URL}/Auth/subscribe`;
|
export const BE_AUTH_REGISTER_URL = `${BASE_URL}/Auth/subscribe`;
|
||||||
@ -131,12 +125,6 @@ export const FE_PARENTS_HOME_URL = '/parents';
|
|||||||
export const FE_PARENTS_MESSAGERIE_URL = '/parents/messagerie';
|
export const FE_PARENTS_MESSAGERIE_URL = '/parents/messagerie';
|
||||||
export const FE_PARENTS_EDIT_SUBSCRIPTION_URL = '/parents/editSubscription';
|
export const FE_PARENTS_EDIT_SUBSCRIPTION_URL = '/parents/editSubscription';
|
||||||
|
|
||||||
// API DOCUSEAL
|
|
||||||
export const FE_API_DOCUSEAL_GENERATE_TOKEN = '/api/docuseal/generateToken';
|
|
||||||
export const FE_API_DOCUSEAL_CLONE_URL = '/api/docuseal/cloneTemplate';
|
|
||||||
export const FE_API_DOCUSEAL_DOWNLOAD_URL = '/api/docuseal/downloadTemplate';
|
|
||||||
export const FE_API_DOCUSEAL_DELETE_URL = '/api/docuseal/removeTemplate';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fonction pour obtenir l'URL de redirection en fonction du rôle
|
* Fonction pour obtenir l'URL de redirection en fonction du rôle
|
||||||
* @param {RIGHTS} role
|
* @param {RIGHTS} role
|
||||||
|
|||||||
@ -10,10 +10,10 @@ services:
|
|||||||
|
|
||||||
database:
|
database:
|
||||||
image: "postgres:latest"
|
image: "postgres:latest"
|
||||||
expose:
|
ports:
|
||||||
- 5432
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres-data:/var/lib/postgresql/data
|
- postgres-data:/var/lib/postgresql
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
|
|||||||
Reference in New Issue
Block a user