diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py index c3de2b3..a9d51d2 100644 --- a/Back-End/Subscriptions/models.py +++ b/Back-End/Subscriptions/models.py @@ -3,7 +3,7 @@ from django.utils.timezone import now from django.conf import settings from django.utils.translation import gettext_lazy as _ -from School.models import SchoolClass, Fee, Discount, PaymentModeType +from School.models import SchoolClass, Fee, Discount, PaymentModeType, PaymentPlanType from Auth.models import ProfileRole from Establishment.models import Establishment @@ -229,6 +229,8 @@ class RegistrationForm(models.Model): establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='register_forms') registration_payment = models.IntegerField(choices=PaymentModeType.choices, null=True, blank=True) tuition_payment = models.IntegerField(choices=PaymentModeType.choices, null=True, blank=True) + registration_payment_plan = models.IntegerField(choices=PaymentPlanType.choices, null=True, blank=True) + tuition_payment_plan = models.IntegerField(choices=PaymentPlanType.choices, null=True, blank=True) def __str__(self): return "RF_" + self.student.last_name + "_" + self.student.first_name diff --git a/Back-End/Subscriptions/views/register_form_views.py b/Back-End/Subscriptions/views/register_form_views.py index 83c5bd9..ae61ae0 100644 --- a/Back-End/Subscriptions/views/register_form_views.py +++ b/Back-End/Subscriptions/views/register_form_views.py @@ -231,6 +231,7 @@ class RegisterFormWithIdView(APIView): """ studentForm_data = request.data.get('data', '{}') + print(f'studentForm_data : {studentForm_data}') try: data = json.loads(studentForm_data) except json.JSONDecodeError: @@ -239,10 +240,16 @@ class RegisterFormWithIdView(APIView): # Extraire le fichier photo photo_file = request.FILES.get('photo') + # Extraire le fichier photo + sepa_file = request.FILES.get('sepa_file') + # Ajouter la photo aux données de l'étudiant if photo_file: data['student']['photo'] = photo_file + if sepa_file: + data['sepa_file'] = sepa_file + # Gérer le champ `_status` _status = data.pop('status', 0) _status = int(_status) @@ -307,7 +314,7 @@ class RegisterFormWithIdView(APIView): elif _status == RegistrationForm.RegistrationFormStatus.RF_VALIDATED: # Vérifier si le paramètre fusion est activé via l'URL - fusion = studentForm_data.get('fusion', False) + fusion = data.get('fusion', False) if fusion: # Fusion des documents # Récupération des fichiers schoolFileTemplates diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index 903a497..606f684 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -375,8 +375,15 @@ export default function Page({ params: { locale } }) { return; } + // Préparer les données JSON + const jsonData = { + status: 7, + }; + const formData = new FormData(); - formData.append('status', 7); + + // Ajouter les données JSON sous forme de chaîne + formData.append('data', JSON.stringify(jsonData)); formData.append('sepa_file', file); // Appeler l'API pour uploader le fichier SEPA @@ -868,7 +875,7 @@ export default function Page({ params: { locale } }) { {`${row.student.first_name} ) : ( 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 b9e14a6..e58b4d6 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js @@ -23,10 +23,8 @@ export default function Page() { const csrfToken = useCsrfToken(); const handleAcceptRF = (data) => { - const { status, fusionParam } = data; const formData = new FormData(); - formData.append('status', status); // Ajoute le statut - formData.append('fusion', fusionParam); + formData.append('data', JSON.stringify(data)); setIsLoading(true); // Appeler l'API pour mettre à jour le RF diff --git a/Front-End/src/app/[locale]/parents/editInscription/page.js b/Front-End/src/app/[locale]/parents/editInscription/page.js index 80dca19..1a90772 100644 --- a/Front-End/src/app/[locale]/parents/editInscription/page.js +++ b/Front-End/src/app/[locale]/parents/editInscription/page.js @@ -5,7 +5,7 @@ import { useSearchParams, useRouter } from 'next/navigation'; import { useCsrfToken } from '@/context/CsrfContext'; import { useEstablishment } from '@/context/EstablishmentContext'; import { FE_PARENTS_HOME_URL } from '@/utils/Url'; -import { editRegisterForm } from '@/app/actions/subscriptionAction'; +import { editRegisterFormWithBinaryFile } from '@/app/actions/subscriptionAction'; import logger from '@/utils/logger'; export default function Page() { @@ -18,7 +18,11 @@ export default function Page() { const handleSubmit = async (data) => { try { - const result = await editRegisterForm(studentId, data, csrfToken); + const result = await editRegisterFormWithBinaryFile( + studentId, + data, + csrfToken + ); logger.debug('Success:', result); router.push(FE_PARENTS_HOME_URL); } catch (error) { diff --git a/Front-End/src/app/[locale]/parents/page.js b/Front-End/src/app/[locale]/parents/page.js index 86841a2..363edf1 100644 --- a/Front-End/src/app/[locale]/parents/page.js +++ b/Front-End/src/app/[locale]/parents/page.js @@ -66,9 +66,12 @@ export default function ParentHomePage() { return; } + const jsonData = { + status: 3, + }; const formData = new FormData(); + formData.append('data', JSON.stringify(jsonData)); formData.append('sepa_file', uploadedFile); // Ajoute le fichier SEPA - formData.append('status', 3); // Statut à envoyer editRegisterFormWithBinaryFile(uploadingStudentId, formData, csrfToken) .then((response) => { diff --git a/Front-End/src/components/FileUpload.js b/Front-End/src/components/FileUpload.js index 5cc49bb..a461a9b 100644 --- a/Front-End/src/components/FileUpload.js +++ b/Front-End/src/components/FileUpload.js @@ -6,6 +6,9 @@ export default function FileUpload({ selectionMessage, onFileSelect, uploadedFileName, + existingFile, + required, + errorMsg, }) { const [localFileName, setLocalFileName] = useState(uploadedFileName || ''); const fileInputRef = useRef(null); // Utilisation de useRef pour cibler l'input @@ -31,7 +34,10 @@ export default function FileUpload({ return (
-

{`${selectionMessage}`}

+

+ {`${selectionMessage}`} + {required && *} +

fileInputRef.current.click()} // Utilisation de la référence pour ouvrir l'explorateur @@ -52,10 +58,24 @@ export default function FileUpload({ Déposez votre fichier ici

- ou cliquez pour sélectionner un fichier PDF + ou cliquez pour sélectionner un fichier

+ + {/* Affichage du fichier existant */} + {existingFile && !localFileName && ( +
+ +

+ + {existingFile.split('/').pop()} + +

+
+ )} + + {/* Affichage du fichier sélectionné */} {localFileName && (
@@ -64,6 +84,9 @@ export default function FileUpload({

)} + + {/* Message d'erreur */} + {errorMsg &&

{errorMsg}

}
); } diff --git a/Front-End/src/components/Inscription/InscriptionFormShared.js b/Front-End/src/components/Inscription/InscriptionFormShared.js index afacb30..914b626 100644 --- a/Front-End/src/components/Inscription/InscriptionFormShared.js +++ b/Front-End/src/components/Inscription/InscriptionFormShared.js @@ -14,6 +14,8 @@ import { import { fetchRegistrationPaymentModes, fetchTuitionPaymentModes, + fetchRegistrationPaymentPlans, + fetchTuitionPaymentPlans, } from '@/app/actions/schoolAction'; import { BASE_URL } from '@/utils/Url'; import logger from '@/utils/logger'; @@ -53,13 +55,14 @@ export default function InscriptionFormShared({ nationality: '', attending_physician: '', level: '', - registration_payment: '', - tuition_payment: '', + photo: '', }); const [guardians, setGuardians] = useState([]); const [registrationPaymentModes, setRegistrationPaymentModes] = useState([]); const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]); + const [registrationPaymentPlans, setRegistrationPaymentPlans] = useState([]); + const [tuitionPaymentPlans, setTuitionPaymentPlans] = useState([]); // États pour la gestion des fichiers const [uploadedFiles, setUploadedFiles] = useState([]); @@ -194,6 +197,12 @@ export default function InscriptionFormShared({ // Fetch data for tuition payment modes handleTuitionPaymentModes(); + + // Fetch data for registration payment plans + handleRegistrationPaymentPlans(); + + // Fetch data for tuition payment plans + handleTuitionnPaymentPlans(); } }, [selectedEstablishmentId]); @@ -223,6 +232,32 @@ export default function InscriptionFormShared({ ); }; + const handleRegistrationPaymentPlans = () => { + fetchRegistrationPaymentPlans(selectedEstablishmentId) + .then((data) => { + const activePaymentPlans = data.filter( + (mode) => mode.is_active === true + ); + setRegistrationPaymentPlans(activePaymentPlans); + }) + .catch((error) => + logger.error('Error fetching registration payment plans:', error) + ); + }; + + const handleTuitionnPaymentPlans = () => { + fetchTuitionPaymentPlans(selectedEstablishmentId) + .then((data) => { + const activePaymentPlans = data.filter( + (mode) => mode.is_active === true + ); + setTuitionPaymentPlans(activePaymentPlans); + }) + .catch((error) => + logger.error('Error fetching registration tuition plans:', error) + ); + }; + const handleFileUpload = (file, selectedFile) => { if (!file || !selectedFile) { logger.error('Données manquantes pour le téléversement.'); @@ -344,8 +379,12 @@ export default function InscriptionFormShared({ status: isSepaPayment ? 8 : 3, tuition_payment: formData.tuition_payment, registration_payment: formData.registration_payment, + tuition_payment_plan: formData.tuition_payment_plan, + registration_payment_plan: formData.registration_payment_plan, }; + console.log('jsonData : ', jsonData); + // Créer un objet FormData const formDataToSend = new FormData(); @@ -450,6 +489,8 @@ export default function InscriptionFormShared({ setFormData={setFormData} registrationPaymentModes={registrationPaymentModes} tuitionPaymentModes={tuitionPaymentModes} + registrationPaymentPlans={registrationPaymentPlans} + tuitionPaymentPlans={tuitionPaymentPlans} errors={errors} setIsPageValid={setIsPage3Valid} /> diff --git a/Front-End/src/components/Inscription/PaymentMethodSelector.js b/Front-End/src/components/Inscription/PaymentMethodSelector.js index 719adfe..796465d 100644 --- a/Front-End/src/components/Inscription/PaymentMethodSelector.js +++ b/Front-End/src/components/Inscription/PaymentMethodSelector.js @@ -1,11 +1,14 @@ import React, { useEffect } from 'react'; import SelectChoice from '@/components/SelectChoice'; +import RadioList from '@/components/RadioList'; export default function PaymentMethodSelector({ formData, setFormData, registrationPaymentModes, tuitionPaymentModes, + registrationPaymentPlans, + tuitionPaymentPlans, errors, setIsPageValid, }) { @@ -14,6 +17,7 @@ export default function PaymentMethodSelector({ (field) => getLocalError(field) !== '' ); setIsPageValid(isValid); + console.log('formdata : ', formData); }, [formData, setIsPageValid]); const paymentModesOptions = [ @@ -23,19 +27,31 @@ export default function PaymentMethodSelector({ { id: 4, name: 'Espèce' }, ]; + const paymentPlansOptions = [ + { id: 1, name: '1 fois' }, + { id: 3, name: '3 fois' }, + { id: 10, name: '10 fois' }, + { id: 12, name: '12 fois' }, + ]; + const getError = (field) => { return errors?.student?.[field]?.[0]; }; const getLocalError = (field) => { if ( - // Student Form (field === 'registration_payment' && (!formData.registration_payment || String(formData.registration_payment).trim() === '')) || (field === 'tuition_payment' && (!formData.tuition_payment || - String(formData.tuition_payment).trim() === '')) + String(formData.tuition_payment).trim() === '')) || + (field === 'registration_payment_plan' && + (!formData.registration_payment_plan || + String(formData.registration_payment_plan).trim() === '')) || + (field === 'tuition_payment_plan' && + (!formData.tuition_payment_plan || + String(formData.tuition_payment_plan).trim() === '')) ) { return 'Champs requis'; } @@ -48,13 +64,12 @@ export default function PaymentMethodSelector({ return ( <> + {/* Frais d'inscription */}
- {/* Titre */}

Frais d'inscription

- {/* Section d'information */}

Montant :{' '} @@ -80,15 +95,42 @@ export default function PaymentMethodSelector({ getLocalError('registration_payment') } /> + + + registrationPaymentPlans.some( + (plan) => plan.frequency === option.id + ) + ) + .map((option) => ({ + id: option.id, + label: option.name, + }))} + formData={{ + ...formData, + registration_payment_plan: parseInt( + formData.registration_payment_plan, + 10 + ), // S'assurer que la valeur est un entier + }} + handleChange={(e) => { + const value = parseInt(e.target.value, 10); + onChange('registration_payment_plan', value); // Convertir la valeur en entier + }} + fieldName="registration_payment_plan" + className="mt-4" + />

+ {/* Frais de scolarité */}
- {/* Titre */}

Frais de scolarité

- {/* Section d'information */}

Montant :{' '} @@ -113,6 +155,26 @@ export default function PaymentMethodSelector({ getError('tuition_payment') || getLocalError('tuition_payment') } /> + + + tuitionPaymentPlans.some((plan) => plan.frequency === option.id) + ) + .map((option) => ({ + id: option.id, + label: option.name, + }))} + formData={formData} + handleChange={(e) => onChange('tuition_payment_plan', e.target.value)} + fieldName="tuition_payment_plan" + className="mt-4" + errorMsg={ + getError('tuition_payment_plan') || + getLocalError('tuition_payment_plan') + } + />

); diff --git a/Front-End/src/components/Inscription/StudentInfoForm.js b/Front-End/src/components/Inscription/StudentInfoForm.js index cc78cdc..8f5694e 100644 --- a/Front-End/src/components/Inscription/StudentInfoForm.js +++ b/Front-End/src/components/Inscription/StudentInfoForm.js @@ -51,6 +51,8 @@ export default function StudentInfoForm({ level: data?.student?.level || '', registration_payment: data?.registration_payment || '', tuition_payment: data?.tuition_payment || '', + registration_payment_plan: data?.registration_payment_plan || '', + tuition_payment_plan: data?.tuition_payment_plan || '', totalRegistrationFees: data?.totalRegistrationFees, totalTuitionFees: data?.totalTuitionFees, }); @@ -96,7 +98,8 @@ export default function StudentInfoForm({ (!formData.attending_physician || formData.attending_physician.trim() === '')) || (field === 'level' && - (!formData.level || String(formData.level).trim() === '')) + (!formData.level || String(formData.level).trim() === '')) || + (field === 'photo' && !formData.photo) ) { return 'Champs requis'; } @@ -230,6 +233,9 @@ export default function StudentInfoForm({ handlePhotoUpload(file)} + existingFile={formData.photo} + required + errorMsg={getError('photo') || getLocalError('photo')} />
diff --git a/Front-End/src/components/RadioList.js b/Front-End/src/components/RadioList.js index fb8b72f..90df256 100644 --- a/Front-End/src/components/RadioList.js +++ b/Front-End/src/components/RadioList.js @@ -7,9 +7,17 @@ const RadioList = ({ fieldName, icon: Icon, className, + sectionLabel, + required, }) => { return (
+ {sectionLabel && ( +

+ {sectionLabel} + {required && *} +

+ )}
{items.map((item) => (