diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py index e119336..4933bd1 100644 --- a/Back-End/Subscriptions/models.py +++ b/Back-End/Subscriptions/models.py @@ -200,6 +200,11 @@ class RegistrationForm(models.Model): null=True, blank=True ) + fusion_file = models.FileField( + upload_to=registration_file_path, + null=True, + blank=True + ) associated_rf = models.CharField(max_length=200, default="", blank=True) # Many-to-Many Relationship diff --git a/Back-End/Subscriptions/views/register_form_views.py b/Back-End/Subscriptions/views/register_form_views.py index 50d7d74..94ff256 100644 --- a/Back-End/Subscriptions/views/register_form_views.py +++ b/Back-End/Subscriptions/views/register_form_views.py @@ -274,33 +274,11 @@ class RegisterFormWithIdView(APIView): updateStateMachine(registerForm, 'EVENT_SIGNATURE') except Exception as e: return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - elif _status == RegistrationForm.RegistrationFormStatus.RF_VALIDATED: - updateStateMachine(registerForm, 'EVENT_VALIDATE') elif _status == RegistrationForm.RegistrationFormStatus.RF_SENT: if registerForm.status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW: updateStateMachine(registerForm, 'EVENT_REFUSE') util.delete_registration_files(registerForm) elif _status == RegistrationForm.RegistrationFormStatus.RF_SEPA_SENT: - # Vérifier si le paramètre fusion est activé via l'URL - fusion = studentForm_data.get('fusion', False) - if fusion: - # Fusion des documents - # Récupération des fichiers d'inscription - fileNames = RegistrationSchoolFileTemplate.get_files_from_rf(registerForm.pk) - if registerForm.registration_file: - fileNames.insert(0, registerForm.registration_file.path) - - # Création du fichier PDF Fusionné - merged_pdf_content = util.merge_files_pdf(fileNames) - - # Mise à jour du champ registration_file avec le fichier fusionné - registerForm.registration_file.save( - f"dossier_complet.pdf", - File(merged_pdf_content), - save=True - ) - # Sauvegarde du mandat SEPA student = registerForm.student guardian = student.getMainGuardian() @@ -314,6 +292,42 @@ class RegisterFormWithIdView(APIView): # Le parent a rempli le dossier d'inscription en sélectionnant "Prélèvement par Mandat SEPA" # L'école doit désormais envoyer le mandat SEPA pour poursuivre l'inscription updateStateMachine(registerForm, 'EVENT_WAITING_FOR_SEPA') + + elif _status == RegistrationForm.RegistrationFormStatus.RF_VALIDATED: + # Vérifier si le paramètre fusion est activé via l'URL + fusion = studentForm_data.get('fusion', False) + if fusion: + # Fusion des documents + # Récupération des fichiers schoolFileTemplates + school_file_paths = RegistrationSchoolFileTemplate.get_files_from_rf(registerForm.pk) + + # Récupération des fichiers parentFileTemplates + parent_file_templates = RegistrationParentFileTemplate.get_files_from_rf(registerForm.pk) + + # Initialisation de la liste des fichiers à fusionner + fileNames = [] + + # Ajout du fichier registration_file en première position + if registerForm.registration_file: + fileNames.append(registerForm.registration_file.path) + + # Ajout des fichiers schoolFileTemplates + fileNames.extend(school_file_paths) + + # Ajout des fichiers parentFileTemplates + fileNames.extend(parent_file_templates) + + # Création du fichier PDF fusionné + merged_pdf_content = util.merge_files_pdf(fileNames) + + # Mise à jour du champ registration_file avec le fichier fusionné + registerForm.fusion_file.save( + f"dossier_complet.pdf", + File(merged_pdf_content), + save=True + ) + + updateStateMachine(registerForm, 'EVENT_VALIDATE') # Retourner les données mises à jour return JsonResponse(studentForm_serializer.data, safe=False) diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index 37f45f5..5646f8b 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -35,7 +35,7 @@ import { archiveRegisterForm, fetchStudents, editRegisterForm, - sendSEPARegisterForm, + editRegisterFormWithBinaryFile, } from '@/app/actions/subscriptionAction'; import { @@ -379,7 +379,7 @@ export default function Page({ params: { locale } }) { formData.append('sepa_file', file); // Appeler l'API pour uploader le fichier SEPA - sendSEPARegisterForm(row.student.id, formData, csrfToken) + editRegisterFormWithBinaryFile(row.student.id, formData, csrfToken) .then((response) => { logger.debug('Mandat SEPA uploadé avec succès :', response); setPopupMessage('Le mandat SEPA a été uploadé avec succès.'); @@ -797,11 +797,7 @@ export default function Page({ params: { locale } }) { ), onClick: () => { - const paymentSepa = - row.registration_payment === 1 || row.tuition_payment === 1 - ? 1 - : 0; - const url = `${FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL}?studentId=${row.student.id}&firstName=${row.student.first_name}&lastName=${row.student.last_name}&paymentSepa=${paymentSepa}&file=${row.registration_file}`; + const url = `${FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL}?studentId=${row.student.id}&firstName=${row.student.first_name}&lastName=${row.student.last_name}&sepa_file=${row.sepa_file}&student_file=${row.registration_file}`; router.push(`${url}`); }, }, @@ -809,11 +805,11 @@ export default function Page({ params: { locale } }) { 5: [ { icon: ( - - + + ), - onClick: () => openModalAssociationEleve(row.student), + onClick: () => openFilesModal(row), }, ], 7: [ @@ -1087,7 +1083,10 @@ export default function Page({ params: { locale } }) { key={`${currentPage}-${searchTerm}`} data={ activeTab === 'pending' - ? registrationFormsDataPending + ? [ + ...registrationFormsDataPending, + ...registrationFormsDataSubscribed, + ] : activeTab === 'subscribed' ? registrationFormsDataSubscribed : registrationFormsDataArchived diff --git a/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js b/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js index 9785c6b..b9e14a6 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js @@ -1,53 +1,59 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import { useSearchParams, useRouter } from 'next/navigation'; import ValidateSubscription from '@/components/Inscription/ValidateSubscription'; -import { sendSEPARegisterForm } from '@/app/actions/subscriptionAction'; +import { editRegisterFormWithBinaryFile } from '@/app/actions/subscriptionAction'; import { useCsrfToken } from '@/context/CsrfContext'; import logger from '@/utils/logger'; +import Loader from '@/components/Loader'; import { FE_ADMIN_SUBSCRIPTIONS_URL } from '@/utils/Url'; export default function Page() { const searchParams = useSearchParams(); const router = useRouter(); + const [isLoading, setIsLoading] = useState(false); // Récupérer les paramètres de la requête const studentId = searchParams.get('studentId'); const firstName = searchParams.get('firstName'); const lastName = searchParams.get('lastName'); - const paymentSepa = searchParams.get('paymentSepa') === '1'; - const file = searchParams.get('file'); + const sepa_file = searchParams.get('sepa_file'); + const student_file = searchParams.get('student_file'); const csrfToken = useCsrfToken(); const handleAcceptRF = (data) => { - logger.debug('Mise à jour du RF avec les données:', data); - - const { status, sepa_file, fusionParam } = data; + const { status, fusionParam } = data; const formData = new FormData(); formData.append('status', status); // Ajoute le statut - formData.append('sepa_file', sepa_file); // Ajoute le fichier SEPA formData.append('fusion', fusionParam); + setIsLoading(true); // Appeler l'API pour mettre à jour le RF - sendSEPARegisterForm(studentId, formData, csrfToken) + editRegisterFormWithBinaryFile(studentId, formData, csrfToken) .then((response) => { logger.debug('RF mis à jour avec succès:', response); router.push(FE_ADMIN_SUBSCRIPTIONS_URL); + setIsLoading(false); // Logique supplémentaire après la mise à jour (par exemple, redirection ou notification) }) .catch((error) => { + setIsLoading(false); logger.error('Erreur lors de la mise à jour du RF:', error); }); }; + if (isLoading) { + return ; + } + return ( ); diff --git a/Front-End/src/app/[locale]/parents/page.js b/Front-End/src/app/[locale]/parents/page.js index 12cbf47..993ef98 100644 --- a/Front-End/src/app/[locale]/parents/page.js +++ b/Front-End/src/app/[locale]/parents/page.js @@ -8,7 +8,7 @@ import FileUpload from '@/components/FileUpload'; import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url'; import { fetchChildren, - sendSEPARegisterForm, + editRegisterFormWithBinaryFile, } from '@/app/actions/subscriptionAction'; import logger from '@/utils/logger'; import { BASE_URL } from '@/utils/Url'; @@ -68,7 +68,7 @@ export default function ParentHomePage() { formData.append('sepa_file', uploadedFile); // Ajoute le fichier SEPA formData.append('status', 3); // Statut à envoyer - sendSEPARegisterForm(uploadingStudentId, formData, csrfToken) + editRegisterFormWithBinaryFile(uploadingStudentId, formData, csrfToken) .then((response) => { logger.debug('RF mis à jour avec succès:', response); setReloadFetch(true); diff --git a/Front-End/src/app/actions/subscriptionAction.js b/Front-End/src/app/actions/subscriptionAction.js index c8cda56..4090d77 100644 --- a/Front-End/src/app/actions/subscriptionAction.js +++ b/Front-End/src/app/actions/subscriptionAction.js @@ -60,7 +60,7 @@ export const editRegisterForm = (id, data, csrfToken) => { }).then(requestResponseHandler); }; -export const sendSEPARegisterForm = (id, data, csrfToken) => { +export const editRegisterFormWithBinaryFile = (id, data, csrfToken) => { return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`, { method: 'PUT', headers: { diff --git a/Front-End/src/components/Inscription/FilesModal.js b/Front-End/src/components/Inscription/FilesModal.js index c50f641..6559750 100644 --- a/Front-End/src/components/Inscription/FilesModal.js +++ b/Front-End/src/components/Inscription/FilesModal.js @@ -14,6 +14,8 @@ const FilesModal = ({ selectedRegisterForm, }) => { const [files, setFiles] = useState({ + registrationFile: null, + fusionFile: null, schoolFiles: [], parentFiles: [], sepaFile: null, @@ -52,6 +54,18 @@ const FilesModal = ({ .then((parentFiles) => { // Construct the categorized files list const categorizedFiles = { + registrationFile: selectedRegisterForm.registration_file + ? { + name: 'Fiche élève', + url: `${BASE_URL}${selectedRegisterForm.registration_file}`, + } + : null, + fusionFile: selectedRegisterForm.fusion_file + ? { + name: 'Documents fusionnés', + url: `${BASE_URL}${selectedRegisterForm.fusion_file}`, + } + : null, schoolFiles: fetchedSchoolFiles.map((file) => ({ name: file.name || 'Document scolaire', url: file.file ? `${BASE_URL}${file.file}` : null, @@ -85,6 +99,48 @@ const FilesModal = ({ } ContentComponent={() => (
+ {/* Section Fiche élève */} + {files.registrationFile && ( +
+

+ Fiche élève +

+ +
+ )} + + {/* Section Documents fusionnés */} + {files.fusionFile && ( +
+

+ Documents fusionnés +

+ +
+ )} + +
+ {/* Section Fichiers École */}

diff --git a/Front-End/src/components/Inscription/ValidateSubscription.js b/Front-End/src/components/Inscription/ValidateSubscription.js index 44838ed..97e46e3 100644 --- a/Front-End/src/components/Inscription/ValidateSubscription.js +++ b/Front-End/src/components/Inscription/ValidateSubscription.js @@ -1,50 +1,30 @@ 'use client'; import React, { useState, useEffect } from 'react'; -import Button from '@/components/Button'; -import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch +import ToggleSwitch from '@/components/ToggleSwitch'; import { BASE_URL } from '@/utils/Url'; -import { generateToken } from '@/app/actions/registerFileGroupAction'; import { fetchSchoolFileTemplatesFromRegistrationFiles, fetchParentFileTemplatesFromRegistrationFiles, } from '@/app/actions/subscriptionAction'; import logger from '@/utils/logger'; -import { GraduationCap } from 'lucide-react'; -import FileUpload from '@/components/FileUpload'; +import { School, CheckCircle } from 'lucide-react'; import SectionHeader from '@/components/SectionHeader'; export default function ValidateSubscription({ studentId, firstName, lastName, - paymentSepa, - file, + sepa_file, + student_file, onAccept, }) { - const [token, setToken] = useState(null); - const [uploadedFileName, setUploadedFileName] = useState(''); - const [selectedFile, setSelectedFile] = useState(null); // Nouvel état pour le fichier sélectionné - const [pdfUrl, setPdfUrl] = useState(`${BASE_URL}/${file}`); - const [isSepa, setIsSepa] = useState(paymentSepa); // Vérifie si le mode de paiement est SEPA - const [currentPage, setCurrentPage] = useState(1); // Gestion des étapes - const [schoolFileTemplates, setSchoolFileTemplates] = useState([]); // Stocke les fichiers schoolFileTemplates - const [parentFileTemplates, setParentFileTemplates] = useState([]); // Stocke les fichiers parentFileTemplates - const [mergeDocuments, setMergeDocuments] = useState(false); // État pour activer/désactiver la fusion des documents + const [schoolFileTemplates, setSchoolFileTemplates] = useState([]); + const [parentFileTemplates, setParentFileTemplates] = useState([]); + const [currentTemplateIndex, setCurrentTemplateIndex] = useState(0); + const [mergeDocuments, setMergeDocuments] = useState(false); useEffect(() => { - if (isSepa) { - generateToken('n3wt.school@gmail.com') - .then((data) => { - setToken(data.token); - }) - .catch((error) => - logger.error('Erreur lors de la génération du token:', error) - ); - } - }, [isSepa]); - - useEffect(() => { - // Récupérer les fichiers schoolFileTemplates pour l'étudiant + // Récupérer les fichiers schoolFileTemplates fetchSchoolFileTemplatesFromRegistrationFiles(studentId) .then((data) => { setSchoolFileTemplates(data); @@ -57,7 +37,7 @@ export default function ValidateSubscription({ ) ); - // Récupérer les fichiers parentFileTemplates pour l'étudiant + // Récupérer les fichiers parentFileTemplates fetchParentFileTemplatesFromRegistrationFiles(studentId) .then((data) => { setParentFileTemplates(data); @@ -72,21 +52,13 @@ export default function ValidateSubscription({ }, [studentId]); const handleAccept = () => { - if (!selectedFile && isSepa) { - logger.error('Aucun fichier sélectionné pour le champ SEPA.'); - return; - } - - // Ajouter le paramètre fusion dans l'URL const fusionParam = mergeDocuments ? 'true' : 'false'; const data = { - status: 7, - sepa_file: selectedFile, // Utilise le fichier sélectionné depuis l'état + status: 5, fusionParam: fusionParam, }; - // Appeler la fonction passée par le parent pour mettre à jour le RF onAccept(data); }; @@ -95,150 +67,114 @@ export default function ValidateSubscription({ setMergeDocuments((prevState) => !prevState); }; - const isValidateButtonDisabled = isSepa && !uploadedFileName; - - const goToNextPage = () => { - const totalPages = - 1 + - schoolFileTemplates.length + - parentFileTemplates.length + - (isSepa ? 1 : 0); - if (currentPage < totalPages) { - setCurrentPage(currentPage + 1); - } - }; - - const goToPreviousPage = () => { - if (currentPage > 1) { - setCurrentPage(currentPage - 1); - } - }; - - const totalPages = - 1 + - schoolFileTemplates.length + - parentFileTemplates.length + - (isSepa ? 1 : 0); - - const renderContent = () => { - if (currentPage === 1) { - // Page 1 : Afficher le PDF principal - return ( -