From 8fd1b62ec09a663f40262b406c2f17f64a8d5a2f Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Thu, 19 Feb 2026 18:53:33 +0100 Subject: [PATCH] feat: Validation document par document [N3WTS-2] --- Back-End/Subscriptions/models.py | 2 + .../app/[locale]/admin/subscriptions/page.js | 2 +- .../validateSubscription/page.js | 43 +++- .../Inscription/RefuseSubscription.js | 69 ----- .../Inscription/ValidateSubscription.js | 241 +++++++++++++++--- 5 files changed, 240 insertions(+), 117 deletions(-) delete mode 100644 Front-End/src/components/Inscription/RefuseSubscription.js diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py index 3e3caba..1cf7897 100644 --- a/Back-End/Subscriptions/models.py +++ b/Back-End/Subscriptions/models.py @@ -498,6 +498,7 @@ class RegistrationSchoolFileTemplate(models.Model): 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) formTemplateData = models.JSONField(default=list, blank=True, null=True) + isValidated = models.BooleanField(default=False) def __str__(self): return self.name @@ -540,6 +541,7 @@ class RegistrationParentFileTemplate(models.Model): 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_parent_file_upload_to) + isValidated = models.BooleanField(default=False) def __str__(self): return self.name diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index f73089e..4254a21 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -520,7 +520,7 @@ export default function Page({ params: { locale } }) { ), onClick: () => { - const url = `${FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL}?studentId=${row.student.id}&firstName=${row.student.first_name}&lastName=${row.student.last_name}&level=${row.student.level}&sepa_file=${row.sepa_file}&student_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}&email=${row.student.guardians[0].associated_profile_email}&level=${row.student.level}&sepa_file=${row.sepa_file}&student_file=${row.registration_file}`; router.push(`${url}`); }, }, 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 4d16716..886b6a6 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js @@ -10,6 +10,7 @@ import Loader from '@/components/Loader'; import { useEstablishment } from '@/context/EstablishmentContext'; import { FE_ADMIN_SUBSCRIPTIONS_URL } from '@/utils/Url'; import { useNotification } from '@/context/NotificationContext'; +import { editRegistrationSchoolFileTemplates, editRegistrationParentFileTemplates } from '@/app/actions/registerFileGroupAction'; export default function Page() { const [isLoadingRefuse, setIsLoadingRefuse] = useState(false); @@ -21,6 +22,7 @@ export default function Page() { const studentId = searchParams.get('studentId'); const firstName = searchParams.get('firstName'); const lastName = searchParams.get('lastName'); + const email = searchParams.get('email'); const level = searchParams.get('level'); const sepa_file = searchParams.get('sepa_file') === 'null' @@ -86,12 +88,7 @@ export default function Page() { }; - const handleRefuseRF = (reason) => { - setIsLoadingRefuse(true); - const data = { - status: 6, // STATUS_ARCHIVED - notes: reason, - }; + const handleRefuseRF = (data) => { const formData = new FormData(); formData.append('data', JSON.stringify(data)); editRegisterForm(studentId, formData, csrfToken) @@ -108,6 +105,37 @@ export default function Page() { }); }; + // Validation/refus d'un document individuel (hors fiche élève) + const handleValidateOrRefuseDoc = ({ templateId, type, validated, csrfToken }) => { + if (!templateId) return; + let editFn = null; + if (type === 'school') { + editFn = editRegistrationSchoolFileTemplates; + } else if (type === 'parent') { + editFn = editRegistrationParentFileTemplates; + } + if (!editFn) return; + const updateData = new FormData(); + updateData.append('data', JSON.stringify({ isValidated: validated })); + editFn(templateId, updateData, csrfToken) + .then((response) => { + logger.debug(`Document ${validated ? 'validé' : 'refusé'} (type: ${type}, id: ${templateId})`, response); + showNotification( + `Le document a bien été ${validated ? 'validé' : 'refusé'}.`, + 'success', + 'Succès' + ); + }) + .catch((error) => { + logger.error('Erreur lors de la validation/refus du document:', error); + showNotification( + `Erreur lors de la ${validated ? 'validation' : 'refus'} du document.`, + 'error', + 'Erreur' + ); + }); + }; + if (isLoading) { return ; } @@ -117,12 +145,15 @@ export default function Page() { studentId={studentId} firstName={firstName} lastName={lastName} + email={email} sepa_file={sepa_file} student_file={student_file} onAccept={handleAcceptRF} classes={classes} onRefuse={handleRefuseRF} isLoadingRefuse={isLoadingRefuse} + handleValidateOrRefuseDoc={handleValidateOrRefuseDoc} + csrfToken={csrfToken} /> ); } diff --git a/Front-End/src/components/Inscription/RefuseSubscription.js b/Front-End/src/components/Inscription/RefuseSubscription.js deleted file mode 100644 index eb2fdd9..0000000 --- a/Front-End/src/components/Inscription/RefuseSubscription.js +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useState } from 'react'; -import Textarea from '@/components/Textarea'; -import Button from '@/components/Form/Button'; - -/** - * Composant pour afficher le textarea de refus et le bouton d'action - * @param {function} onRefuse - callback appelée avec la raison du refus - * @param {boolean} isLoading - état de chargement - */ -const RefuseSubscription = ({ onRefuse, isLoading }) => { - const [reason, setReason] = useState(''); - const [showTextarea, setShowTextarea] = useState(false); - - const handleRefuseClick = () => { - setShowTextarea((prev) => !prev); - }; - - const handleSubmit = (e) => { - e.preventDefault(); - if (reason.trim()) { - onRefuse(reason); - setShowTextarea(false); - setReason(''); - } - }; - - return ( -
- {!showTextarea ? ( -