diff --git a/Back-End/Auth/serializers.py b/Back-End/Auth/serializers.py index 98686d2..bee22ac 100644 --- a/Back-End/Auth/serializers.py +++ b/Back-End/Auth/serializers.py @@ -16,7 +16,24 @@ class ProfileSerializer(serializers.ModelSerializer): def get_roles(self, obj): roles = ProfileRole.objects.filter(profile=obj) - return [{'role_type': role.role_type, 'establishment': role.establishment.id, 'establishment_name': role.establishment.name, 'is_active': role.is_active} for role in roles] + roles_data = [] + for role in roles: + # Récupérer l'ID de l'associated_person en fonction du type de rôle + if role.role_type == ProfileRole.RoleType.PROFIL_PARENT: + guardian = Guardian.objects.filter(profile_role=role).first() + id_associated_person = guardian.id if guardian else None + else: + teacher = Teacher.objects.filter(profile_role=role).first() + id_associated_person = teacher.id if teacher else None + + roles_data.append({ + 'id_associated_person': id_associated_person, + 'role_type': role.role_type, + 'establishment': role.establishment.id, + 'establishment_name': role.establishment.name, + 'is_active': role.is_active, + }) + return roles_data def create(self, validated_data): user = Profile( @@ -107,6 +124,7 @@ class ProfileRoleSerializer(serializers.ModelSerializer): "registration_status": registration_status }) return { + "id": guardian.id, "guardian_name": f"{guardian.last_name} {guardian.first_name}", "students": students_list } @@ -118,6 +136,7 @@ class ProfileRoleSerializer(serializers.ModelSerializer): specialities = teacher.specialities.all() specialities_list = [{"name": speciality.name, "color_code": speciality.color_code} for speciality in specialities] return { + "id": teacher.id, "teacher_name": f"{teacher.last_name} {teacher.first_name}", "classes": classes_list, "specialities": specialities_list diff --git a/Back-End/Auth/views.py b/Back-End/Auth/views.py index bffc8d5..a1eb820 100644 --- a/Back-End/Auth/views.py +++ b/Back-End/Auth/views.py @@ -4,7 +4,6 @@ from django.http.response import JsonResponse from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt, csrf_protect from django.utils.decorators import method_decorator from django.core.exceptions import ValidationError -from django.core.cache import cache from django.middleware.csrf import get_token from rest_framework.views import APIView from rest_framework.parsers import JSONParser @@ -24,7 +23,6 @@ from rest_framework.decorators import action, api_view from Auth.serializers import ProfileSerializer, ProfileRoleSerializer from Subscriptions.models import RegistrationForm -from Subscriptions.signals import clear_cache import Subscriptions.mailManager as mailer import Subscriptions.util as util import logging @@ -204,7 +202,6 @@ class LoginView(APIView): login(request, user) user.save() - clear_cache() retour = '' # Récupérer tous les rôles de l'utilisateur avec le type spécifié @@ -423,7 +420,6 @@ class SubscribeView(APIView): else: return JsonResponse(role_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST) - clear_cache() retour = error.returnMessage[error.MESSAGE_ACTIVATION_PROFILE] retourErreur = '' return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": profil.id}, safe=False) @@ -473,7 +469,6 @@ class NewPasswordView(APIView): profil.code = util.genereRandomCode(12) profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS) profil.save() - clear_cache() retourErreur = '' retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD] % (newProfilConnection.get('email')) mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code) @@ -527,7 +522,6 @@ class ResetPasswordView(APIView): profil.code = '' profil.datePeremption = '' profil.save() - clear_cache() retourErreur = '' return JsonResponse({'message': retour, "errorMessage": retourErreur, "errorFields": errorFields}, safe=False) diff --git a/Back-End/Subscriptions/apps.py b/Back-End/Subscriptions/apps.py index 503b621..3ab723f 100644 --- a/Back-End/Subscriptions/apps.py +++ b/Back-End/Subscriptions/apps.py @@ -5,6 +5,3 @@ class GestioninscriptionsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'Subscriptions' - def ready(self): - from Subscriptions.signals import clear_cache - clear_cache() diff --git a/Back-End/Subscriptions/automate.py b/Back-End/Subscriptions/automate.py index 3c457cb..6f98e79 100644 --- a/Back-End/Subscriptions/automate.py +++ b/Back-End/Subscriptions/automate.py @@ -1,7 +1,6 @@ # state_machine.py import json from Subscriptions.models import RegistrationForm -from Subscriptions.signals import clear_cache state_mapping = { "ABSENT": RegistrationForm.RegistrationFormStatus.RF_ABSENT, @@ -31,7 +30,6 @@ def updateStateMachine(rf, transition) : if state_machine.trigger(transition, automateModel): rf.status = state_machine.state rf.save() - clear_cache() class Automate_RF_Register: def __init__(self, initial_state): diff --git a/Back-End/Subscriptions/serializers.py b/Back-End/Subscriptions/serializers.py index 86d2870..1ed09df 100644 --- a/Back-End/Subscriptions/serializers.py +++ b/Back-End/Subscriptions/serializers.py @@ -110,6 +110,9 @@ class StudentSerializer(serializers.ModelSerializer): profile_role = guardian_data.pop('profile_role', None) if profile_role_data: + # Vérifiez si 'profile' est un objet ou une clé primaire + if isinstance(profile_role_data.get('profile'), Profile): + profile_role_data['profile'] = profile_role_data['profile'].id establishment_id = profile_role_data.pop('establishment').id profile_role_data['establishment'] = establishment_id diff --git a/Back-End/Subscriptions/signals.py b/Back-End/Subscriptions/signals.py index 0ce2f11..1da56dc 100644 --- a/Back-End/Subscriptions/signals.py +++ b/Back-End/Subscriptions/signals.py @@ -1,6 +1,5 @@ from django.db.models.signals import post_save, post_delete, m2m_changed from django.dispatch import receiver -from django.core.cache import cache from .models import RegistrationForm, Student, Guardian from Auth.models import Profile from N3wtSchool import settings @@ -8,23 +7,6 @@ from N3wtSchool.redis_client import redis_client import logging logger = logging.getLogger(__name__) -def clear_cache(): - # Préfixes des clés à supprimer - prefixes = ['N3WT_'] - - for prefix in prefixes: - # Utiliser le motif pour obtenir les clés correspondant au préfixe - pattern = f'*{prefix}*' - logger.debug(f'pattern : {pattern}') - for key in redis_client.scan_iter(pattern): - redis_client.delete(key) - logger.debug(f'deleting : {key}') - -@receiver(post_save, sender=RegistrationForm) -@receiver(post_delete, sender=RegistrationForm) -def clear_cache_after_change(sender, instance, **kwargs): - clear_cache() - @receiver(m2m_changed, sender=Student.guardians.through) def check_orphan_reponsables(sender, **kwargs): action = kwargs.pop('action', None) diff --git a/Back-End/Subscriptions/views/register_form_views.py b/Back-End/Subscriptions/views/register_form_views.py index ce72e85..1cecb01 100644 --- a/Back-End/Subscriptions/views/register_form_views.py +++ b/Back-End/Subscriptions/views/register_form_views.py @@ -1,7 +1,6 @@ from django.http.response import JsonResponse from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect from django.utils.decorators import method_decorator -from django.core.cache import cache from rest_framework.parsers import JSONParser from rest_framework.views import APIView from rest_framework.decorators import action, api_view @@ -18,7 +17,6 @@ import Subscriptions.util as util from Subscriptions.serializers import RegistrationFormSerializer from Subscriptions.pagination import CustomPagination -from Subscriptions.signals import clear_cache from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationTemplate, RegistrationFileGroup from Subscriptions.automate import updateStateMachine @@ -88,13 +86,6 @@ class RegisterFormView(APIView): except ValueError: page_size = settings.NB_RESULT_PER_PAGE - # Définir le cache_key en fonction du filtre - page_number = request.GET.get('page', 1) - cache_key = f'N3WT_ficheInscriptions_{establishment_id}_{filter}_page_{page_number}_search_{search if filter == "pending" else ""}' - cached_page = cache.get(cache_key) - if cached_page: - return JsonResponse(cached_page, safe=False) - # Récupérer les dossier d'inscriptions en fonction du filtre registerForms_List = None if filter == 'pending': @@ -120,7 +111,6 @@ class RegisterFormView(APIView): if page is not None: registerForms_serializer = RegistrationFormSerializer(page, many=True) response_data = paginator.get_paginated_response(registerForms_serializer.data) - cache.set(cache_key, response_data, timeout=60 * 15) return JsonResponse(response_data, safe=False) return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False) @@ -308,7 +298,6 @@ class RegisterFormWithIdView(APIView): student.profiles.clear() student.registration_files.clear() student.delete() - clear_cache() return JsonResponse("La suppression du dossier a été effectuée avec succès", 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 064dc2a..973e3d8 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -43,7 +43,7 @@ import { fetchRegistrationFees, fetchTuitionFees } from '@/app/actions/schoolAction'; -import { createProfile, deleteProfile } from '@/app/actions/authAction'; +import { createProfile, deleteProfile, fetchProfiles } from '@/app/actions/authAction'; import { BASE_URL, @@ -86,6 +86,7 @@ export default function Page({ params: { locale } }) { const [registrationFees, setRegistrationFees] = useState([]); const [tuitionFees, setTuitionFees] = useState([]); const [groups, setGroups] = useState([]); + const [profiles, setProfiles] = useState([]); const csrfToken = useCsrfToken(); const { selectedEstablishmentId } = useEstablishment(); @@ -235,7 +236,14 @@ useEffect(() => { }) .catch(error => { logger.error('Error fetching file groups:', error); + }), + fetchProfiles() + .then(data => { + setProfiles(data); }) + .catch(error => { + logger.error('Error fetching profileRoles:', error); + }), ]) .then(() => { setIsLoading(false); @@ -373,24 +381,46 @@ useEffect(()=>{ student: { last_name: updatedData.studentLastName, first_name: updatedData.studentFirstName, - guardians: updatedData.selectedGuardians.length !== 0 ? updatedData.selectedGuardians.map(guardianId => ({ id: guardianId })) : [{ - profile_role_data: { - establishment: selectedEstablishmentId, - role_type: 2, - is_active: false, - profile_data: { - email: updatedData.guardianEmail, - password: 'Provisoire01!', - username: updatedData.guardianEmail, - } - }, - last_name: updatedData.guardianLastName, - first_name: updatedData.guardianFirstName, - birth_date: updatedData.guardianBirthDate, - address: updatedData.guardianAddress, - phone: updatedData.guardianPhone, - profession: updatedData.guardianProfession - }], + guardians: updatedData.selectedGuardians.length !== 0 + ? updatedData.selectedGuardians.map(guardianId => ({ id: guardianId })) + : (() => { + if (updatedData.isExistingParentProfile) { + return [{ + profile_role_data: { + establishment: selectedEstablishmentId, + role_type: 2, + is_active: false, + profile: updatedData.existingProfileId, // Associer au profil existant + }, + last_name: updatedData.guardianLastName, + first_name: updatedData.guardianFirstName, + birth_date: updatedData.guardianBirthDate, + address: updatedData.guardianAddress, + phone: updatedData.guardianPhone, + profession: updatedData.guardianProfession + }]; + } + + // Si aucun profil existant n'est trouvé, créer un nouveau profil + return [{ + profile_role_data: { + establishment: selectedEstablishmentId, + role_type: 2, + is_active: false, + profile_data: { + email: updatedData.guardianEmail, + password: 'Provisoire01!', + username: updatedData.guardianEmail, + } + }, + last_name: updatedData.guardianLastName, + first_name: updatedData.guardianFirstName, + birth_date: updatedData.guardianBirthDate, + address: updatedData.guardianAddress, + phone: updatedData.guardianPhone, + profession: updatedData.guardianProfession + }]; + })(), sibling: [] }, fees: allFeesIds, @@ -699,6 +729,7 @@ const columnsSubscribed = [ registrationFees={registrationFees.filter(fee => fee.is_active)} tuitionFees={tuitionFees.filter(fee => fee.is_active)} groups={groups} + profiles={profiles} onSubmit={createRF} /> )} diff --git a/Front-End/src/app/actions/authAction.js b/Front-End/src/app/actions/authAction.js index 80a3a6a..377917f 100644 --- a/Front-End/src/app/actions/authAction.js +++ b/Front-End/src/app/actions/authAction.js @@ -113,6 +113,11 @@ export const deleteProfileRoles = (id, csrfToken) => { return fetch(request).then(requestResponseHandler); }; +export const fetchProfiles = () => { + return fetch(`${BE_AUTH_PROFILES_URL}`) + .then(requestResponseHandler) +}; + export const createProfile = (data, csrfToken) => { const request = new Request( `${BE_AUTH_PROFILES_URL}`, diff --git a/Front-End/src/components/Inscription/InscriptionForm.js b/Front-End/src/components/Inscription/InscriptionForm.js index df1e151..846f983 100644 --- a/Front-End/src/components/Inscription/InscriptionForm.js +++ b/Front-End/src/components/Inscription/InscriptionForm.js @@ -9,11 +9,14 @@ import DiscountsSection from '@/components/Structure/Tarification/DiscountsSecti import SectionTitle from '@/components/SectionTitle'; import ProgressStep from '@/components/ProgressStep'; import logger from '@/utils/logger'; +import Popup from '@/components/Popup'; -const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit, currentStep, groups }) => { +const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, profiles, onSubmit, currentStep, groups }) => { const [formData, setFormData] = useState({ studentLastName: '', studentFirstName: '', + guardianLastName: '', + guardianFirstName: '', guardianEmail: '', guardianPhone: '', selectedGuardians: [], @@ -31,6 +34,10 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r const [existingGuardians, setExistingGuardians] = useState([]); const [totalRegistrationAmount, setTotalRegistrationAmount] = useState(0); const [totalTuitionAmount, setTotalTuitionAmount] = useState(0); + const [filteredStudents, setFilteredStudents] = useState(students); + + const [popupVisible, setPopupVisible] = useState(false); + const [popupMessage, setPopupMessage] = useState(""); const stepTitles = { 1: 'Nouvel élève', @@ -45,8 +52,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r const isStep1Valid = formData.studentLastName && formData.studentFirstName; const isStep2Valid = ( - (formData.responsableType === "new" && formData.guardianEmail.length > 0) || - (formData.responsableType === "existing" && formData.selectedGuardians.length > 0) + formData.selectedGuardians.length > 0 || + (!formData.emailError && formData.guardianEmail.length > 0 && filteredStudents.length === 0) ); const isStep3Valid = formData.selectedRegistrationFees.length > 0; const isStep4Valid = formData.selectedTuitionFees.length > 0; @@ -99,9 +106,35 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r }; const nextStep = () => { - if (step < steps.length) { - setStep(step + 1); - } + if (step === 2) { + // Vérifier si l'adresse email saisie est rattachée à un profil existant + const existingProfile = profiles.find(profile => profile.email === formData.guardianEmail); + + if (existingProfile) { + // Vérifier si le profil a un rôle de type PARENT + const parentRole = existingProfile.roles.find(role => role.role_type === 2); + + if (parentRole) { + console.log('Profil parent associé trouvé !', existingProfile); + setFormData((prevData) => ({ + ...prevData, + guardianEmail: existingProfile.email, // Mettre à jour le champ guardianEmail avec l'email du profil + isExistingParentProfile: true, // Indiquer que le profil est un parent existant + existingProfileId: existingProfile.id + })); + } else { + console.log('Le profil existe mais n\'est pas de type PARENT.'); + setFormData((prevData) => ({ + ...prevData, + isExistingParentProfile: false, // Réinitialiser si le profil n'est pas de type PARENT + })); + } + } + } + + if (step < steps.length) { + setStep(step + 1); + } }; const prevStep = () => { @@ -120,13 +153,27 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r }; const handleResponsableSelection = (guardianId, guardianEmail) => { - setFormData((prevData) => { - const isSelected = prevData.selectedGuardians.includes(guardianId); - const selectedGuardians = isSelected - ? prevData.selectedGuardians.filter(id => id !== guardianId) - : [...prevData.selectedGuardians, guardianId]; - return { ...prevData, selectedGuardians, guardianEmail }; - }); + setFormData((prevData) => { + const isSelected = prevData.selectedGuardians.includes(guardianId); + const selectedGuardians = isSelected + ? prevData.selectedGuardians.filter(id => id !== guardianId) // Retirer le guardian si déjà sélectionné + : [...prevData.selectedGuardians, guardianId]; // Ajouter le guardian s'il n'est pas sélectionné + + // Mettre à jour l'email uniquement si un guardian est sélectionné + const updatedGuardianEmail = isSelected ? '' : guardianEmail; + + // Si aucun guardian n'est sélectionné, réinitialiser le tableau des élèves + if (selectedGuardians.length === 0) { + setFilteredStudents(students); // Réafficher tous les élèves + setSelectedEleve(null); + } + + return { + ...prevData, + selectedGuardians, + guardianEmail: updatedGuardianEmail, + }; + }); }; const submit = () => { @@ -265,117 +312,141 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r {step === 2 && (
-
- - -
- {formData.responsableType === 'new' && ( - <> - - - - + + - - )} - {formData.responsableType === 'existing' && ( -
-
- - - - - + {/* Email du responsable */} + { + const email = e.target.value; + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + + setFormData((prevData) => ({ + ...prevData, + guardianEmail: email, + emailError: + email.length > 0 && // Le champ de mail est non null + (!emailRegex.test(email) && filteredStudents.length === 0) // Format invalide ou aucun résultat + ? "Format d'email invalide ou aucun élève trouvé" + : "", + })); + + // Filtrer les responsables affichés dans le tableau + const filteredGuardians = students + .flatMap((student) => student.guardians) // Récupérer tous les guardians + .filter((guardian) => + guardian.associated_profile_email.toLowerCase().includes(email.toLowerCase()) + ); + + // Filtrer les élèves en fonction des responsables + const filteredStudents = students.filter((student) => + student.guardians.some((guardian) => + filteredGuardians.some((filteredGuardian) => filteredGuardian.id === guardian.id) + ) + ); + + setFilteredStudents(filteredStudents); // Mettre à jour l'état des élèves filtrés + + // Réinitialiser existingGuardians et selectedStudent si l'élève sélectionné n'est plus dans le tableau + if (!filteredStudents.some((student) => student.id === selectedStudent?.id)) { + setSelectedEleve(null); // Réinitialiser l'élève sélectionné + setExistingGuardians([]); // Réinitialiser les responsables associés + setFormData((prevData) => ({ + ...prevData, + selectedGuardians: [] // Réinitialiser les responsables sélectionnés + })); + } + }} + errorMsg={formData.emailError} + className="w-full mt-4" + /> + + {/* Tableau des élèves */} +
+
+
NomPrénom
+ + + + + + + + {filteredStudents.map((student, index) => ( // Utiliser les élèves filtrés + handleEleveSelection(student)} + > + + - - - {students.map((student, index) => ( - handleEleveSelection(student)} - > - - - - ))} - -
NomPrénom
{student.last_name}{student.first_name}
{student.last_name}{student.first_name}
-
- {selectedStudent && ( -
-

Responsables associés à {selectedStudent.last_name} {selectedStudent.first_name} :

- {existingGuardians.map((guardian) => ( -
- -
))} -
- )} + +
- )} + {selectedStudent && ( +
+

+ Responsables associés à {selectedStudent.last_name} {selectedStudent.first_name} : +

+ {existingGuardians.map((guardian) => ( +
+ +
+ ))} +
+ )} +
)} @@ -641,6 +712,13 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r name="Create" /> )} + + setPopupVisible(false)} + onCancel={() => setPopupVisible(false)} + /> ); } diff --git a/Front-End/src/components/Popup.js b/Front-End/src/components/Popup.js index ac7e04f..261d63a 100644 --- a/Front-End/src/components/Popup.js +++ b/Front-End/src/components/Popup.js @@ -28,14 +28,14 @@ const Popup = ({ visible, message, onConfirm, onCancel, uniqueConfirmButton = fa
{!uniqueConfirmButton && ( )}