feat: Gestion des documents parent

This commit is contained in:
N3WT DE COMPET
2025-04-17 19:06:28 +02:00
parent 7564865d8f
commit 59aee80c2e
15 changed files with 402 additions and 206 deletions

View File

@ -283,8 +283,7 @@ class RegistrationSchoolFileTemplate(models.Model):
####### Parent files templates (par dossier d'inscription) #######
class RegistrationParentFileTemplate(models.Model):
master = models.ForeignKey(RegistrationSchoolFileMaster, on_delete=models.CASCADE, related_name='parent_file_templates', blank=True)
name = models.CharField(max_length=255, default="")
master = models.ForeignKey(RegistrationParentFileMaster, on_delete=models.CASCADE, related_name='parent_file_templates', blank=True)
registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='parent_file_templates', blank=True)
file = models.FileField(null=True,blank=True, upload_to=registration_file_upload_to)

View File

@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster
from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster, RegistrationParentFileTemplate
from School.models import SchoolClass, Fee, Discount, FeeType
from School.serializers import FeeSerializer, DiscountSerializer
from Auth.models import ProfileRole, Profile
@ -27,6 +27,7 @@ class RegistrationParentFileMasterSerializer(serializers.ModelSerializer):
class RegistrationSchoolFileTemplateSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
file_url = serializers.SerializerMethodField()
class Meta:
model = RegistrationSchoolFileTemplate
fields = '__all__'
@ -38,8 +39,10 @@ class RegistrationSchoolFileTemplateSerializer(serializers.ModelSerializer):
class RegistrationParentFileTemplateSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
file = serializers.SerializerMethodField()
master_name = serializers.CharField(source='master.name', read_only=True)
master_description = serializers.CharField(source='master.description', read_only=True)
class Meta:
model = RegistrationParentFileMaster
model = RegistrationParentFileTemplate
fields = '__all__'
def get_file(self, obj):

View File

@ -7,7 +7,17 @@ from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archi
# SubClasses
from .views import StudentView, GuardianView, ChildrenListView, StudentListView, DissociateGuardianView
# Files
from .views import RegistrationSchoolFileMasterView, RegistrationSchoolFileMasterSimpleView, RegistrationSchoolFileTemplateView, RegistrationSchoolFileTemplateSimpleView, RegistrationParentFileMasterSimpleView, RegistrationParentFileMasterView
from .views import (
RegistrationSchoolFileMasterView,
RegistrationSchoolFileMasterSimpleView,
RegistrationSchoolFileTemplateView,
RegistrationSchoolFileTemplateSimpleView,
RegistrationParentFileMasterSimpleView,
RegistrationParentFileMasterView,
RegistrationParentFileTemplateSimpleView,
RegistrationParentFileTemplateView
)
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
@ -17,7 +27,7 @@ urlpatterns = [
re_path(r'^registerForms/(?P<id>[0-9]+)/send$', send, name="send"),
re_path(r'^registerForms/(?P<id>[0-9]+)$', RegisterFormWithIdView.as_view(), name="registerForm"),
re_path(r'^registerForms/(?P<id>[0-9]+)/school_file_templates$', get_school_file_templates_by_rf, name="get_school_file_templates_by_rf"),
re_path(r'^registerForms/(?P<id>[0-9]+)/parent_file_templatess$', get_parent_file_templates_by_rf, name="get_parent_file_templates_by_rf"),
re_path(r'^registerForms/(?P<id>[0-9]+)/parent_file_templates$', get_parent_file_templates_by_rf, name="get_parent_file_templates_by_rf"),
re_path(r'^registerForms$', RegisterFormView.as_view(), name="registerForms"),
# Page INSCRIPTION - Liste des élèves
@ -43,6 +53,9 @@ urlpatterns = [
re_path(r'^registrationSchoolFileTemplates/(?P<id>[0-9]+)$', RegistrationSchoolFileTemplateSimpleView.as_view(), name='registrationSchoolFileTemplates'),
re_path(r'^registrationSchoolFileTemplates$', RegistrationSchoolFileTemplateView.as_view(), name="registrationSchoolFileTemplates"),
re_path(r'^registrationParentFileTemplates/(?P<id>[0-9]+)$', RegistrationParentFileTemplateSimpleView.as_view(), name='registrationParentFileTemplates'),
re_path(r'^registrationParentFileTemplates$', RegistrationParentFileTemplateView.as_view(), name="registrationSchoolFileTregistrationParentFileTemplatesemplates"),
re_path(r'^students/(?P<student_id>[0-9]+)/guardians/(?P<guardian_id>[0-9]+)/dissociate', DissociateGuardianView.as_view(), name='dissociate-guardian'),
]

View File

@ -1,5 +1,14 @@
from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive, get_school_file_templates_by_rf, get_parent_file_templates_by_rf
from .registration_file_views import RegistrationSchoolFileMasterView, RegistrationSchoolFileMasterSimpleView, RegistrationSchoolFileTemplateView, RegistrationSchoolFileTemplateSimpleView, RegistrationParentFileMasterView, RegistrationParentFileMasterSimpleView
from .registration_file_views import (
RegistrationSchoolFileMasterView,
RegistrationSchoolFileMasterSimpleView,
RegistrationSchoolFileTemplateView,
RegistrationSchoolFileTemplateSimpleView,
RegistrationParentFileMasterView,
RegistrationParentFileMasterSimpleView,
RegistrationParentFileTemplateSimpleView,
RegistrationParentFileTemplateView
)
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
from .student_views import StudentView, StudentListView, ChildrenListView
from .guardian_views import GuardianView, DissociateGuardianView
@ -16,6 +25,8 @@ __all__ = [
'RegistrationParentFileMasterView',
'RegistrationSchoolFileMasterView',
'RegistrationSchoolFileMasterSimpleView',
'RegistrationParentFileTemplateSimpleView',
'RegistrationParentFileTemplateView',
'RegistrationFileGroupView',
'RegistrationFileGroupSimpleView',
'get_registration_files_by_group',

View File

@ -14,9 +14,9 @@ from django.core.files import File
import Subscriptions.mailManager as mailer
import Subscriptions.util as util
from Subscriptions.serializers import RegistrationFormSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileMasterSerializer
from Subscriptions.serializers import RegistrationFormSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileTemplateSerializer
from Subscriptions.pagination import CustomPagination
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationSchoolFileTemplate, RegistrationFileGroup, RegistrationParentFileMaster
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationSchoolFileTemplate, RegistrationFileGroup, RegistrationParentFileTemplate
from Subscriptions.automate import updateStateMachine
from N3wtSchool import settings, bdd
@ -441,12 +441,12 @@ def get_school_file_templates_by_rf(request, id):
def get_parent_file_templates_by_rf(request, id):
try:
# Récupérer les pièces à fournir associés au RegistrationForm donné
parent_files = RegistrationParentFileMaster.objects.filter(registration_form=id)
parent_files = RegistrationParentFileTemplate.objects.filter(registration_form=id)
# Sérialiser les données
serializer = RegistrationParentFileMasterSerializer(parent_files, many=True)
serializer = RegistrationParentFileTemplateSerializer(parent_files, many=True)
# Retourner les données sérialisées
return JsonResponse(serializer.data, safe=False)
except RegistrationSchoolFileTemplate.DoesNotExist:
except RegistrationParentFileTemplate.DoesNotExist:
return JsonResponse({'error': 'Aucune pièce à fournir trouvée pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND)

View File

@ -6,8 +6,8 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from Subscriptions.serializers import RegistrationSchoolFileMasterSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileMasterSerializer
from Subscriptions.models import RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster
from Subscriptions.serializers import RegistrationSchoolFileMasterSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileMasterSerializer, RegistrationParentFileTemplateSerializer
from Subscriptions.models import RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster, RegistrationParentFileTemplate
from N3wtSchool import bdd
class RegistrationSchoolFileMasterView(APIView):
@ -183,7 +183,6 @@ class RegistrationParentFileMasterView(APIView):
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",
@ -232,3 +231,77 @@ class RegistrationParentFileMasterSimpleView(APIView):
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 d'inscription",
responses={200: RegistrationParentFileTemplateSerializer(many=True)}
)
def get(self, request):
templates = RegistrationParentFileTemplate.objects.all()
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)
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)

View File

@ -29,7 +29,9 @@ import {
import {
fetchRegistrationSchoolFileMasters,
createRegistrationTemplates,
fetchRegistrationParentFileMasters,
createRegistrationSchoolFileTemplate,
createRegistrationParentFileTemplate,
fetchRegistrationFileGroups,
cloneTemplate
} from "@/app/actions/registerFileGroupAction";
@ -73,6 +75,7 @@ export default function Page({ params: { locale } }) {
const [itemsPerPage, setItemsPerPage] = useState(10); // Définir le nombre d'éléments par page
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
const [parentFileMasters, setParentFileMasters] = useState([]);
const [isOpen, setIsOpen] = useState(false);
const [isOpenAffectationClasse, setIsOpenAffectationClasse] = useState(false);
const [student, setStudent] = useState('');
@ -220,6 +223,13 @@ useEffect(() => {
.catch(err => {
logger.debug(err.message);
}),
fetchRegistrationParentFileMasters()
.then(data => {
setParentFileMasters(data);
})
.catch(err => {
logger.debug(err.message);
}),
fetchRegistrationDiscounts(selectedEstablishmentId)
.then(data => {
setRegistrationDiscounts(data);
@ -462,6 +472,7 @@ useEffect(()=>{
.then(data => {
// Cloner les schoolFileTemplates pour chaque templateMaster du fileGroup
const masters = schoolFileMasters.filter(file => file.groups.includes(selectedFileGroup));
const parent_masters = parentFileMasters.filter(file => file.groups.includes(selectedFileGroup));
const clonePromises = masters.map((templateMaster, index) => {
return cloneTemplate(templateMaster.id, updatedData.guardianEmail, templateMaster.is_required)
.then(clonedDocument => {
@ -474,7 +485,7 @@ useEffect(()=>{
registration_form: data.student.id
};
return createRegistrationTemplates(cloneData, csrfToken)
return createRegistrationSchoolFileTemplate(cloneData, csrfToken)
.then(response => {
logger.debug('Template enregistré avec succès:', response);
})
@ -487,6 +498,22 @@ useEffect(()=>{
});
});
// Créer les parentFileTemplates pour chaque parentMaster
const parentClonePromises = parent_masters.map((parentMaster, index) => {
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 => {
logger.error('Erreur lors de l\'enregistrement du parent template:', error);
});
});
// Attendre que tous les clones soient créés
Promise.all(clonePromises)
.then(() => {

View File

@ -1,7 +1,8 @@
import { BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL,
BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL,
BE_SUBSCRIPTION_REGISTRATIONSCHOOL_FILE_MASTERS_URL,
BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL,
BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL,
BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL,
FE_API_DOCUSEAL_CLONE_URL,
FE_API_DOCUSEAL_DOWNLOAD_URL,
FE_API_DOCUSEAL_GENERATE_TOKEN
@ -17,7 +18,9 @@ const requestResponseHandler = async (response) => {
const error = new Error(body?.errorMessage || "Une erreur est survenue");
error.details = body;
throw error;
}
};
// FETCH requests
export async function fetchRegistrationFileGroups(establishment) {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}?establishment_id=${establishment}`, {
@ -29,58 +32,11 @@ export async function fetchRegistrationFileGroups(establishment) {
if (!response.ok) {
throw new Error('Failed to fetch file groups');
}
return response.json();
}
export async function createRegistrationFileGroup(groupData, csrfToken) {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
body: JSON.stringify(groupData),
credentials: 'include'
});
if (!response.ok) {
throw new Error('Failed to create file group');
}
return response.json();
}
export async function deleteRegistrationFileGroup(groupId, csrfToken) {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include'
});
return response;
}
export const editRegistrationFileGroup = async (groupId, groupData, csrfToken) => {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
body: JSON.stringify(groupData),
});
if (!response.ok) {
throw new Error('Erreur lors de la modification du groupe');
}
return response.json();
};
export const fetchRegistrationFileFromGroup = async (groupId) => {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/schoolFileTemplates`, {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/school_file_templates`, {
credentials: 'include',
headers: {
'Accept': 'application/json',
@ -90,7 +46,24 @@ export const fetchRegistrationFileFromGroup = async (groupId) => {
throw new Error('Erreur lors de la récupération des fichiers associés au groupe');
}
return response.json();
};
export const fetchRegistrationSchoolFileMasters = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}`;
if(id){
url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}/${id}`;
}
const request = new Request(
`${url}`,
{
method:'GET',
headers: {
'Content-Type':'application/json'
},
}
);
return fetch(request).then(requestResponseHandler)
};
export const fetchRegistrationParentFileMasters = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}`
@ -109,43 +82,6 @@ export const fetchRegistrationParentFileMasters = (id = null) => {
return fetch(request).then(requestResponseHandler)
};
export const createRegistrationParentFileMaster = (data,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}`, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
credentials: 'include',
})
.then(requestResponseHandler)
}
export const editRegistrationParentFileMaster = (id, data, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
credentials: 'include',
})
.then(requestResponseHandler)
}
export const deleteRegistrationParentFileMaster = (id, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include',
})
}
export const fetchRegistrationSchoolFileTemplates = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}`
if (id) {
@ -163,20 +99,53 @@ export const fetchRegistrationSchoolFileTemplates = (id = null) => {
return fetch(request).then(requestResponseHandler)
};
export const editRegistrationTemplates = (fileId, data, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${fileId}`, {
method: 'PUT',
body: data,
// CREATE requests
export async function createRegistrationFileGroup(groupData, csrfToken) {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
body: JSON.stringify(groupData),
credentials: 'include'
});
if (!response.ok) {
throw new Error('Failed to create file group');
}
return response.json();
};
export const createRegistrationSchoolFileMaster = (data,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}`, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'X-CSRFToken': csrfToken,
'Content-Type':'application/json'
},
credentials: 'include',
})
.then(requestResponseHandler)
}
};
export const createRegistrationTemplates = (data,csrfToken) => {
export const createRegistrationParentFileMaster = (data,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}`, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
credentials: 'include',
})
.then(requestResponseHandler)
};
export const createRegistrationSchoolFileTemplate = (data,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}`, {
method: 'POST',
body: JSON.stringify(data),
@ -187,61 +156,43 @@ export const createRegistrationTemplates = (data,csrfToken) => {
credentials: 'include',
})
.then(requestResponseHandler)
}
export const deleteRegistrationTemplates = (fileId,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${fileId}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include',
})
}
export const fetchRegistrationSchoolFileMasters = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATIONSCHOOL_FILE_MASTERS_URL}`;
if(id){
url = `${BE_SUBSCRIPTION_REGISTRATIONSCHOOL_FILE_MASTERS_URL}/${id}`;
}
const request = new Request(
`${url}`,
{
method:'GET',
headers: {
'Content-Type':'application/json'
},
}
);
return fetch(request).then(requestResponseHandler)
};
export const createRegistrationSchoolFileMaster = (data,csrfToken) => {
export const createRegistrationParentFileTemplate = (data,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONSCHOOL_FILE_MASTERS_URL}`, {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL}`, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'X-CSRFToken': csrfToken,
'Content-Type':'application/json'
'Content-Type': 'application/json',
},
credentials: 'include',
})
.then(requestResponseHandler)
}
};
export const deleteRegistrationSchoolFileMaster = (fileId,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONSCHOOL_FILE_MASTERS_URL}/${fileId}`, {
method: 'DELETE',
// EDIT requests
export const editRegistrationFileGroup = async (groupId, groupData, csrfToken) => {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
credentials: 'include',
})
body: JSON.stringify(groupData),
});
if (!response.ok) {
throw new Error('Erreur lors de la modification du groupe');
}
return response.json();
};
export const editRegistrationSchoolFileMaster = (fileId, data, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONSCHOOL_FILE_MASTERS_URL}/${fileId}`, {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}/${fileId}`, {
method: 'PUT',
body: JSON.stringify(data),
headers: {
@ -251,7 +202,78 @@ export const editRegistrationSchoolFileMaster = (fileId, data, csrfToken) => {
credentials: 'include',
})
.then(requestResponseHandler)
}
};
export const editRegistrationParentFileMaster = (id, data, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
credentials: 'include',
})
.then(requestResponseHandler)
};
export const editRegistrationSchoolFileTemplates = (fileId, data, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${fileId}`, {
method: 'PUT',
body: data,
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include',
})
.then(requestResponseHandler)
};
// DELETE requests
export async function deleteRegistrationFileGroup(groupId, csrfToken) {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include'
});
return response;
};
export const deleteRegistrationSchoolFileMaster = (fileId,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}/${fileId}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include',
})
};
export const deleteRegistrationParentFileMaster = (id, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include',
})
};
export const deleteRegistrationSchoolFileTemplates = (fileId,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${fileId}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include',
})
};
// API requests
export const cloneTemplate = (templateId, email, is_required) => {
return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, {
@ -266,7 +288,7 @@ export const cloneTemplate = (templateId, email, is_required) => {
})
})
.then(requestResponseHandler)
}
};
export const downloadTemplate = (slug) => {
return fetch(`${FE_API_DOCUSEAL_DOWNLOAD_URL}/${slug}`, {
@ -276,7 +298,7 @@ export const downloadTemplate = (slug) => {
}
})
.then(requestResponseHandler)
}
};
export const generateToken = (email, id = null) => {
return fetch(`${FE_API_DOCUSEAL_GENERATE_TOKEN}`, {

View File

@ -162,7 +162,20 @@ export const fetchSchoolFileTemplatesFromRegistrationFiles = async (id) => {
throw new Error('Erreur lors de la récupération des fichiers associés au groupe');
}
return response.json();
};
export const fetchParentFileTemplatesFromRegistrationFiles = async (id) => {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/parent_file_templates`, {
credentials: 'include',
headers: {
'Accept': 'application/json',
}
});
if (!response.ok) {
throw new Error('Erreur lors de la récupération des fichiers associés au groupe');
}
return response.json();
};
export const dissociateGuardian = async (studentId, guardianId) => {
const response = await fetch(`${BE_SUBSCRIPTION_STUDENTS_URL}/${studentId}/guardians/${guardianId}/dissociate`, {

View File

@ -1,12 +1,12 @@
import React from 'react';
import Table from '@/components/Table';
export default function FilesToUpload({ fileTemplates, columns }) {
export default function FilesToUpload({ parentFileTemplates, columns }) {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">Fichiers à uploader</h2>
<Table
data={fileTemplates}
data={parentFileTemplates}
columns={columns}
itemsPerPage={5}
currentPage={1}

View File

@ -3,11 +3,11 @@ import React, { useState, useEffect } from 'react';
import Loader from '@/components/Loader';
import Button from '@/components/Button';
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import { fetchRegisterForm, fetchSchoolFileTemplatesFromRegistrationFiles } from '@/app/actions/subscriptionAction';
import { fetchRegisterForm, fetchSchoolFileTemplatesFromRegistrationFiles, fetchParentFileTemplatesFromRegistrationFiles } from '@/app/actions/subscriptionAction';
import { downloadTemplate,
createRegistrationTemplates,
editRegistrationTemplates,
deleteRegistrationTemplates
createRegistrationSchoolFileTemplate,
editRegistrationSchoolFileTemplates,
deleteRegistrationSchoolFileTemplates
} from '@/app/actions/registerFileGroupAction';
import {
fetchRegistrationPaymentModes,
@ -63,7 +63,8 @@ export default function InscriptionFormShared({
// États pour la gestion des fichiers
const [uploadedFiles, setUploadedFiles] = useState([]);
const [fileTemplates, setFileTemplates] = useState([]);
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
const [parentFileTemplates, setParentFileTemplates] = useState([]);
const [fileGroup, setFileGroup] = useState(null);
const [fileName, setFileName] = useState("");
const [file, setFile] = useState("");
@ -113,11 +114,13 @@ export default function InscriptionFormShared({
useEffect(() => {
fetchSchoolFileTemplatesFromRegistrationFiles(studentId).then((data) => {
setFileTemplates(data);
setSchoolFileTemplates(data);
})
fetchParentFileTemplatesFromRegistrationFiles(studentId).then((data) => {
setParentFileTemplates(data);
})
}, []);
useEffect(() => {
if (selectedEstablishmentId) {
// Fetch data for registration payment modes
handleRegistrationPaymentModes();
@ -164,7 +167,7 @@ export default function InscriptionFormShared({
data.append('register_form', formData.id);
try {
const response = await createRegistrationTemplates(data, csrfToken);
const response = await createRegistrationSchoolFileTemplate(data, csrfToken);
if (response) {
setUploadedFiles(prev => {
const newFiles = prev.filter(f => parseInt(f.template) !== currentTemplateId);
@ -205,7 +208,7 @@ export default function InscriptionFormShared({
if (!fileToDelete) return;
try {
await deleteRegistrationTemplates(fileToDelete.id, csrfToken);
await deleteRegistrationSchoolFileTemplates(fileToDelete.id, csrfToken);
setUploadedFiles(prev => prev.filter(f => parseInt(f.template) !== templateId));
} catch (error) {
logger.error('Error deleting file:', error);
@ -249,12 +252,10 @@ export default function InscriptionFormShared({
setCurrentPage(currentPage - 1);
};
const requiredFileTemplates = fileTemplates;
// Configuration des colonnes pour le tableau des fichiers
const columns = [
{ name: 'Nom du fichier', transform: (row) => row.name },
{ name: 'Fichier à Remplir', transform: (row) => row.is_required ? 'Oui' : 'Non' },
{ name: 'Nom du fichier', transform: (row) => row.master_name },
{ name: 'Description du fichier', transform: (row) => row.master_description },
{ name: 'Fichier de référence', transform: (row) => row.file && <div className="flex items-center justify-center gap-2"> <a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
<Download size={16} />
</a> </div>},
@ -328,34 +329,34 @@ export default function InscriptionFormShared({
)}
{/* Pages suivantes : Section Fichiers d'inscription */}
{currentPage > 1 && currentPage <= requiredFileTemplates.length + 1 && (
{currentPage > 1 && currentPage <= schoolFileTemplates.length + 1 && (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
{/* Titre du document */}
<div className="mb-4">
<h2 className="text-lg font-semibold text-gray-800">
{requiredFileTemplates[currentPage - 2].name || "Document sans nom"}
{schoolFileTemplates[currentPage - 2].name || "Document sans nom"}
</h2>
<p className="text-sm text-gray-500">
{requiredFileTemplates[currentPage - 2].description || "Aucune description disponible pour ce document."}
{schoolFileTemplates[currentPage - 2].description || "Aucune description disponible pour ce document."}
</p>
</div>
{/* Affichage du formulaire ou du document */}
{requiredFileTemplates[currentPage - 2].file === null ? (
{schoolFileTemplates[currentPage - 2].file === null ? (
<DocusealForm
id="docusealForm"
src={"https://docuseal.com/s/" + requiredFileTemplates[currentPage - 2].slug}
src={"https://docuseal.com/s/" + schoolFileTemplates[currentPage - 2].slug}
withDownloadButton={false}
onComplete={() => {
downloadTemplate(requiredFileTemplates[currentPage - 2].slug)
downloadTemplate(schoolFileTemplates[currentPage - 2].slug)
.then((data) => fetch(data))
.then((response) => response.blob())
.then((blob) => {
const file = new File([blob], `${requiredFileTemplates[currentPage - 2].name}.pdf`, { type: blob.type });
const file = new File([blob], `${schoolFileTemplates[currentPage - 2].name}.pdf`, { type: blob.type });
const updateData = new FormData();
updateData.append('file', file);
return editRegistrationTemplates(requiredFileTemplates[currentPage - 2].id, updateData, csrfToken);
return editRegistrationSchoolFileTemplates(schoolFileTemplates[currentPage - 2].id, updateData, csrfToken);
})
.then((data) => {
logger.debug("EDIT TEMPLATE : ", data);
@ -367,7 +368,7 @@ export default function InscriptionFormShared({
/>
) : (
<iframe
src={`${BASE_URL}/${requiredFileTemplates[currentPage - 2].file}`}
src={`${BASE_URL}/${schoolFileTemplates[currentPage - 2].file}`}
title="Document Viewer"
className="w-full"
style={{
@ -380,10 +381,10 @@ export default function InscriptionFormShared({
)}
{/* Dernière page : Section Fichiers parents */}
{currentPage === requiredFileTemplates.length + 2 && (
{currentPage === schoolFileTemplates.length + 2 && (
<>
<FilesToUpload
fileTemplates={fileTemplates.filter(template => !template.is_required)}
parentFileTemplates={parentFileTemplates}
columns={columns}
/>
<FileUpload
@ -409,7 +410,7 @@ export default function InscriptionFormShared({
{currentPage > 1 && (
<Button text="Précédent" onClick={(e) => { e.preventDefault(); handlePreviousPage(); }} />
)}
{currentPage < requiredFileTemplates.length + 2 && (
{currentPage < schoolFileTemplates.length + 2 && (
<Button
text="Suivant"
onClick={(e) => { e.preventDefault(); handleNextPage(); }}
@ -423,12 +424,12 @@ export default function InscriptionFormShared({
name="Next"
/>
)}
{currentPage === requiredFileTemplates.length + 2 && (
{currentPage === schoolFileTemplates.length + 2 && (
<Button type="submit" text="Valider" primary />
)}
</div>
</form>
{fileTemplates.length > 0 && (
{schoolFileTemplates.length > 0 && (
<Modal
isOpen={showUploadModal}
setIsOpen={setShowUploadModal}

View File

@ -1,17 +1,12 @@
import React, { useState, useEffect } from 'react';
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
import { fetchRegistrationFileGroups, createRegistrationTemplates, cloneTemplate, generateToken } from '@/app/actions/registerFileGroupAction';
import { fetchRegistrationFileGroups, createRegistrationSchoolFileTemplate, cloneTemplate, generateToken } from '@/app/actions/registerFileGroupAction';
import { DocusealBuilder } from '@docuseal/react';
import logger from '@/utils/logger';
import { BE_DOCUSEAL_GET_JWT, BASE_URL, FE_API_DOCUSEAL_GENERATE_TOKEN } from '@/utils/Url';
import Button from '@/components/Button'; // Import du composant Button
import MultiSelect from '@/components/MultiSelect'; // Import du composant MultiSelect
import { useCsrfToken } from '@/context/CsrfContext';
import { useEstablishment } from '@/context/EstablishmentContext';
export default function FileUploadDocuSeal({ handleCreateTemplateMaster, handleEditTemplateMaster, fileToEdit = null, onSuccess }) {
const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired
const [order, setOrder] = useState(0);
const [groups, setGroups] = useState([]);
const [token, setToken] = useState(null);
const [templateMaster, setTemplateMaster] = useState(null);
@ -43,10 +38,6 @@ export default function FileUploadDocuSeal({ handleCreateTemplateMaster, handleE
.catch((error) => console.error('Erreur lors de la génération du token:', error));
}, [fileToEdit]);
const handleFileNameChange = (event) => {
setUploadedFileName(event.target.value);
};
const handleGroupChange = (selectedGroups) => {
setSelectedGroups(selectedGroups);
@ -110,7 +101,7 @@ export default function FileUploadDocuSeal({ handleCreateTemplateMaster, handleE
master: templateMaster?.id,
registration_form: guardian.registration_form
};
createRegistrationTemplates(data, csrfToken)
createRegistrationSchoolFileTemplate(data, csrfToken)
.then(response => {
logger.debug('Template enregistré avec succès:', response);
onSuccess();

View File

@ -333,14 +333,17 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
const handleCreate = (newParentFile) => {
return createRegistrationParentFileMaster(newParentFile, csrfToken)
.then((createdFile) => {
.then((response) => {
const createdFile = response;
// Ajouter le nouveau fichier parent à la liste existante
setParentFileMasters((prevFiles) => [...prevFiles, createdFile]);
logger.debug('Document parent créé avec succès:', createdFile);
return createdFile;
})
.catch((error) => {
logger.error('Erreur lors de la création du document parent:', error);
alert('Une erreur est survenue lors de la création du document parent.');
throw error;
});
};

View File

@ -5,18 +5,24 @@ import InputText from '@/components/InputText';
import MultiSelect from '@/components/MultiSelect';
import Popup from '@/components/Popup';
import logger from '@/utils/logger';
import { createRegistrationParentFileTemplate } from '@/app/actions/registerFileGroupAction';
import { useCsrfToken } from '@/context/CsrfContext';
export default function ParentFilesSection({ parentFiles, groups, handleCreate, handleEdit, handleDelete }) {
const [editingDocumentId, setEditingDocumentId] = useState(null);
const [formData, setFormData] = useState(null);
const [selectedGroups, setSelectedGroups] = useState([]); // Gestion des groupes sélectionnés
const [guardianDetails, setGuardianDetails] = useState([]);
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState("");
const [removePopupVisible, setRemovePopupVisible] = useState(false);
const [removePopupMessage, setRemovePopupMessage] = useState("");
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const csrfToken = useCsrfToken();
const handleAddEmptyRequiredDocument = () => {
setEditingDocumentId('new');
setFormData({ name: '', description: '', groups: [] });
@ -44,10 +50,27 @@ export default function ParentFilesSection({ parentFiles, groups, handleCreate,
};
if (editingDocumentId === 'new') {
handleCreate(updatedFormData).then(() => {
handleCreate(updatedFormData).then((createdDocument) => {
setEditingDocumentId(null);
setFormData(null);
setSelectedGroups([]);
guardianDetails.forEach((guardian, index) => {
// Création des templates
const data = {
master: createdDocument?.id,
registration_form: guardian.registration_form
};
console.log(guardian)
createRegistrationParentFileTemplate(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);
});
});
});
} else {
handleEdit(editingDocumentId, updatedFormData).then(() => {
@ -78,6 +101,22 @@ export default function ParentFilesSection({ parentFiles, groups, handleCreate,
const handleGroupChange = (selected) => {
setSelectedGroups(selected);
console.log('selected : ', selected)
// Extraire les guardians associés aux register_forms des groupes sélectionnés
const details = selected.flatMap(group =>
group.registration_forms.flatMap(form =>
form.guardians.map(guardian => ({
email: guardian.associated_profile_email,
last_name: form.last_name, // Extraire depuis form
first_name: form.first_name, // Extraire depuis form
registration_form: form.student_id // Utiliser student_id comme ID du register_form
}))
)
);
console.log("Guardians associés : ", details);
setGuardianDetails(details); // Mettre à jour la variable d'état avec les détails des guardians
};
const renderRequiredDocumentCell = (document, column) => {
@ -110,7 +149,7 @@ export default function ParentFilesSection({ parentFiles, groups, handleCreate,
<MultiSelect
name="groups"
label="Sélection de groupes de fichiers"
options={groups.map((group) => ({ id: group.id, name: group.name }))}
options={groups}
selectedOptions={selectedGroups}
onChange={handleGroupChange}
errorMsg={null}

View File

@ -26,10 +26,11 @@ export const BE_AUTH_INFO_SESSION = `${BASE_URL}/Auth/infoSession`
export const BE_SUBSCRIPTION_STUDENTS_URL = `${BASE_URL}/Subscriptions/students` // Récupère la liste des élèves inscrits ou en cours d'inscriptions
export const BE_SUBSCRIPTION_CHILDRENS_URL = `${BASE_URL}/Subscriptions/children` // Récupère la liste des élèves d'un profil
export const BE_SUBSCRIPTION_REGISTERFORMS_URL = `${BASE_URL}/Subscriptions/registerForms`
export const BE_SUBSCRIPTION_REGISTRATIONSCHOOL_FILE_MASTERS_URL = `${BASE_URL}/Subscriptions/registrationSchoolFileMasters`
export const BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationSchoolFileTemplates`
export const BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`
export const BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL = `${BASE_URL}/Subscriptions/registrationSchoolFileMasters`
export const BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationSchoolFileTemplates`
export const BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL = `${BASE_URL}/Subscriptions/registrationParentFileMasters`
export const BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationParentFileTemplates`
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`