feat: Gestion multi-profil multi-école

This commit is contained in:
N3WT DE COMPET
2025-03-09 16:22:28 +01:00
parent 95c154a4a2
commit 16178296ec
51 changed files with 1621 additions and 802 deletions

View File

@ -2,25 +2,31 @@ from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.validators import EmailValidator from django.core.validators import EmailValidator
from Establishment.models import Establishment
class Profile(AbstractUser): class Profile(AbstractUser):
class Droits(models.IntegerChoices):
PROFIL_UNDEFINED = -1, _('NON DEFINI')
PROFIL_ECOLE = 0, _('ECOLE')
PROFIL_ADMIN = 1, _('ADMIN')
PROFIL_PARENT = 2, _('PARENT')
email = models.EmailField(max_length=255, unique=True, default="", validators=[EmailValidator()]) email = models.EmailField(max_length=255, unique=True, default="", validators=[EmailValidator()])
USERNAME_FIELD = 'email' USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ('password', ) REQUIRED_FIELDS = ('password', )
code = models.CharField(max_length=200, default="", blank=True) code = models.CharField(max_length=200, default="", blank=True)
datePeremption = models.CharField(max_length=200, default="", blank=True) datePeremption = models.CharField(max_length=200, default="", blank=True)
droit = models.IntegerField(choices=Droits, default=Droits.PROFIL_UNDEFINED)
estConnecte = models.BooleanField(default=False, blank=True)
establishment = models.ForeignKey('School.Establishment', on_delete=models.PROTECT, related_name='profile', null=True, blank=True)
def __str__(self): def __str__(self):
return self.email + " - " + str(self.droit) return self.email
class ProfileRole(models.Model):
class RoleType(models.IntegerChoices):
PROFIL_UNDEFINED = -1, _('NON DEFINI')
PROFIL_ECOLE = 0, _('ECOLE')
PROFIL_ADMIN = 1, _('ADMIN')
PROFIL_PARENT = 2, _('PARENT')
profile = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='roles')
role_type = models.IntegerField(choices=RoleType.choices, default=RoleType.PROFIL_UNDEFINED)
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='profile_roles')
is_active = models.BooleanField(default=False)
def __str__(self):
return f"{self.profile.email} - {self.get_role_type_display()} - {self.establishment.name}"

View File

@ -1,48 +1,97 @@
from rest_framework import serializers from rest_framework import serializers
from Auth.models import Profile from Auth.models import Profile, ProfileRole
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
class ProfileRoleSerializer(serializers.ModelSerializer):
class Meta:
model = ProfileRole
fields = ['role_type', 'establishment', 'is_active', 'profile']
class ProfileSerializer(serializers.ModelSerializer): class ProfileSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False) id = serializers.IntegerField(required=False)
password = serializers.CharField(write_only=True) password = serializers.CharField(write_only=True)
roles = ProfileRoleSerializer(many=True, required=False)
class Meta: class Meta:
model = Profile model = Profile
fields = ['id', 'password', 'email', 'code', 'datePeremption', 'estConnecte', 'droit', 'username', 'is_active', 'establishment'] fields = ['id', 'password', 'email', 'code', 'datePeremption', 'username', 'roles']
extra_kwargs = {'password': {'write_only': True}} extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data): def create(self, validated_data):
roles_data = validated_data.pop('roles', [])
user = Profile( user = Profile(
username=validated_data['username'], username=validated_data['username'],
email=validated_data['email'], email=validated_data['email'],
is_active=validated_data['is_active'], code=validated_data.get('code', ''),
droit=validated_data['droit'], datePeremption=validated_data.get('datePeremption', '')
establishment=validated_data.get('establishment')
) )
user.set_password(validated_data['password']) user.set_password(validated_data['password'])
user.full_clean()
user.save() user.save()
for role_data in roles_data:
ProfileRole.objects.create(profile=user, **role_data)
return user return user
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['password'] = '********'
return ret
class ProfilUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['id', 'password', 'email', 'code', 'datePeremption', 'estConnecte', 'droit', 'username', 'is_active']
extra_kwargs = {
'password': {'write_only': True, 'required': False}
}
def update(self, instance, validated_data): def update(self, instance, validated_data):
roles_data = validated_data.pop('roles', [])
password = validated_data.pop('password', None) password = validated_data.pop('password', None)
instance = super().update(instance, validated_data) instance = super().update(instance, validated_data)
if password: if password:
instance.set_password(password) instance.set_password(password)
instance.save()
instance.full_clean()
instance.save()
for role_data in roles_data:
ProfileRole.objects.update_or_create(
profile=instance,
establishment_id=role_data.get('establishment_id'),
defaults={
'role_type': role_data.get('role_type'),
'is_active': role_data.get('is_active', True)
}
)
return instance
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['password'] = '********'
return ret
class ProfilUpdateSerializer(serializers.ModelSerializer):
roles = ProfileRoleSerializer(many=True, required=False)
class Meta:
model = Profile
fields = ['id', 'password', 'email', 'code', 'datePeremption', 'username', 'roles']
extra_kwargs = {
'password': {'write_only': True, 'required': False}
}
def update(self, instance, validated_data):
roles_data = validated_data.pop('roles', [])
password = validated_data.pop('password', None)
instance = super().update(instance, validated_data)
if password:
instance.set_password(password)
instance.full_clean()
instance.save()
for role_data in roles_data:
ProfileRole.objects.update_or_create(
profile=instance,
establishment_id=role_data.get('establishment_id'),
defaults={
'role_type': role_data.get('role_type'),
'is_active': role_data.get('is_active', True)
}
)
return instance return instance

View File

@ -19,7 +19,7 @@ from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
import json import json
from . import validator from . import validator
from .models import Profile from .models import Profile, ProfileRole
from rest_framework.decorators import action, api_view from rest_framework.decorators import action, api_view
from Auth.serializers import ProfileSerializer, ProfilUpdateSerializer from Auth.serializers import ProfileSerializer, ProfilUpdateSerializer
@ -56,7 +56,10 @@ class SessionView(APIView):
'user': openapi.Schema(type=openapi.TYPE_OBJECT, properties={ 'user': openapi.Schema(type=openapi.TYPE_OBJECT, properties={
'id': openapi.Schema(type=openapi.TYPE_INTEGER), 'id': openapi.Schema(type=openapi.TYPE_INTEGER),
'email': openapi.Schema(type=openapi.TYPE_STRING), 'email': openapi.Schema(type=openapi.TYPE_STRING),
'role': openapi.Schema(type=openapi.TYPE_STRING) 'roles': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Items(type=openapi.TYPE_OBJECT, properties={
'role_type': openapi.Schema(type=openapi.TYPE_STRING),
'establishment': openapi.Schema(type=openapi.TYPE_STRING)
}))
}) })
})), })),
401: openapi.Response('Session invalide') 401: openapi.Response('Session invalide')
@ -67,15 +70,16 @@ class SessionView(APIView):
try: try:
decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
print(f'decode : {decoded_token}') userid = decoded_token.get('user_id')
userid = decoded_token.get('id')
user = Profile.objects.get(id=userid) user = Profile.objects.get(id=userid)
roles = ProfileRole.objects.filter(profile=user).values('role_type', 'establishment__name')
response_data = { response_data = {
'user': { 'user': {
'id': user.id, 'id': user.id,
'email': user.email, 'email': user.email,
'role': user.droit, # Assure-toi que le champ 'droit' existe et contient le rôle 'roles': list(roles)
} }
} }
return JsonResponse(response_data, status=status.HTTP_200_OK) return JsonResponse(response_data, status=status.HTTP_200_OK)
@ -103,13 +107,11 @@ class ProfileView(APIView):
} }
) )
def post(self, request): def post(self, request):
profil_data=JSONParser().parse(request) profil_data = JSONParser().parse(request)
print(f'{profil_data}')
profil_serializer = ProfileSerializer(data=profil_data) profil_serializer = ProfileSerializer(data=profil_data)
if profil_serializer.is_valid(): if profil_serializer.is_valid():
profil_serializer.save() profil = profil_serializer.save()
return JsonResponse(profil_serializer.data, safe=False) return JsonResponse(profil_serializer.data, safe=False)
return JsonResponse(profil_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST) return JsonResponse(profil_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@ -122,8 +124,8 @@ class ProfileSimpleView(APIView):
responses={200: ProfileSerializer} responses={200: ProfileSerializer}
) )
def get(self, request, id): def get(self, request, id):
profil=bdd.getObject(Profile, "id", id) profil = bdd.getObject(Profile, "id", id)
profil_serializer=ProfileSerializer(profil) profil_serializer = ProfileSerializer(profil)
return JsonResponse(profil_serializer.data, safe=False) return JsonResponse(profil_serializer.data, safe=False)
@swagger_auto_schema( @swagger_auto_schema(
@ -135,12 +137,12 @@ class ProfileSimpleView(APIView):
} }
) )
def put(self, request, id): def put(self, request, id):
data=JSONParser().parse(request) data = JSONParser().parse(request)
profil = Profile.objects.get(id=id) profil = Profile.objects.get(id=id)
profil_serializer = ProfilUpdateSerializer(profil, data=data) profil_serializer = ProfilUpdateSerializer(profil, data=data)
if profil_serializer.is_valid(): if profil_serializer.is_valid():
profil_serializer.save() profil_serializer.save()
return JsonResponse("Updated Successfully", safe=False) return JsonResponse(profil_serializer.data, safe=False)
return JsonResponse(profil_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST) return JsonResponse(profil_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@ -157,10 +159,11 @@ class LoginView(APIView):
operation_description="Connexion utilisateur", operation_description="Connexion utilisateur",
request_body=openapi.Schema( request_body=openapi.Schema(
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['email', 'password'], required=['email', 'password', 'role_type'],
properties={ properties={
'email': openapi.Schema(type=openapi.TYPE_STRING), 'email': openapi.Schema(type=openapi.TYPE_STRING),
'password': openapi.Schema(type=openapi.TYPE_STRING) 'password': openapi.Schema(type=openapi.TYPE_STRING),
'role_type': openapi.Schema(type=openapi.TYPE_STRING)
} }
), ),
responses={ responses={
@ -193,40 +196,45 @@ class LoginView(APIView):
password=data.get('password'), password=data.get('password'),
) )
if user is not None: if user is not None:
if user.is_active: role_type = data.get('role_type')
login(request, user) primary_role = ProfileRole.objects.filter(profile=user, role_type=role_type, is_active=True).first()
user.estConnecte = True
user.save()
clear_cache()
retour = ''
# Générer le JWT avec la bonne syntaxe datetime
access_payload = {
'user_id': user.id,
'email': user.email,
'droit': user.droit,
'establishment': user.establishment.id,
'type': 'access',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
}
access_token = jwt.encode(access_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM']) if not primary_role:
# Générer le Refresh Token (exp: 7 jours) return JsonResponse({"errorMessage": "Role not assigned to the user"}, status=status.HTTP_401_UNAUTHORIZED)
refresh_payload = {
'user_id': user.id,
'type': 'refresh',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
}
refresh_token = jwt.encode(refresh_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
return JsonResponse({ login(request, user)
'token': access_token, user.save()
'refresh': refresh_token clear_cache()
}, safe=False) retour = ''
# Récupérer tous les rôles de l'utilisateur avec le type spécifié
roles = ProfileRole.objects.filter(profile=user, role_type=role_type).values('role_type', 'establishment__id', 'establishment__name')
# Générer le JWT avec la bonne syntaxe datetime
access_payload = {
'user_id': user.id,
'email': user.email,
'roles': list(roles),
'type': 'access',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
}
access_token = jwt.encode(access_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
# Générer le Refresh Token (exp: 7 jours)
refresh_payload = {
'user_id': user.id,
'type': 'refresh',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
}
refresh_token = jwt.encode(refresh_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
return JsonResponse({
'token': access_token,
'refresh': refresh_token
}, safe=False)
else:
retour = error.returnMessage[error.PROFIL_INACTIVE]
else: else:
retour = error.returnMessage[error.WRONG_ID] retour = error.returnMessage[error.WRONG_ID]
@ -235,7 +243,6 @@ class LoginView(APIView):
'errorMessage': retour, 'errorMessage': retour,
}, safe=False, status=status.HTTP_400_BAD_REQUEST) }, safe=False, status=status.HTTP_400_BAD_REQUEST)
class RefreshJWTView(APIView): class RefreshJWTView(APIView):
@swagger_auto_schema( @swagger_auto_schema(
operation_description="Rafraîchir le token d'accès", operation_description="Rafraîchir le token d'accès",
@ -295,13 +302,20 @@ class RefreshJWTView(APIView):
# Récupérer les informations utilisateur # Récupérer les informations utilisateur
user = Profile.objects.get(id=payload['user_id']) user = Profile.objects.get(id=payload['user_id'])
role_type = payload.get('role_type')
# Récupérer le rôle principal de l'utilisateur
primary_role = ProfileRole.objects.filter(profile=user, role_type=role_type).first()
if not primary_role:
return JsonResponse({'errorMessage': 'No role assigned to the user'}, status=400)
# Générer un nouveau Access Token avec les informations complètes # Générer un nouveau Access Token avec les informations complètes
new_access_payload = { new_access_payload = {
'user_id': user.id, 'user_id': user.id,
'email': user.email, 'email': user.email,
'droit': user.droit, 'role_type': primary_role.get_role_type_display(),
'establishment': user.establishment.id, 'establishment': primary_role.establishment.id,
'type': 'access', 'type': 'access',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'], 'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
'iat': datetime.utcnow(), 'iat': datetime.utcnow(),
@ -311,6 +325,7 @@ class RefreshJWTView(APIView):
new_refresh_payload = { new_refresh_payload = {
'user_id': user.id, 'user_id': user.id,
'role_type': role_type,
'type': 'refresh', 'type': 'refresh',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'], 'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
'iat': datetime.utcnow(), 'iat': datetime.utcnow(),
@ -335,6 +350,14 @@ class RefreshJWTView(APIView):
class SubscribeView(APIView): class SubscribeView(APIView):
@swagger_auto_schema( @swagger_auto_schema(
operation_description="Inscription utilisateur", operation_description="Inscription utilisateur",
manual_parameters=[
openapi.Parameter(
'establishment_id', openapi.IN_QUERY,
description="ID de l'établissement",
type=openapi.TYPE_INTEGER,
required=True
)
],
request_body=openapi.Schema( request_body=openapi.Schema(
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['email', 'password1', 'password2'], required=['email', 'password1', 'password2'],
@ -359,37 +382,54 @@ class SubscribeView(APIView):
def post(self, request): def post(self, request):
retourErreur = error.returnMessage[error.BAD_URL] retourErreur = error.returnMessage[error.BAD_URL]
retour = '' retour = ''
newProfilConnection=JSONParser().parse(request) newProfilConnection = JSONParser().parse(request)
establishment_id = request.GET.get('establishment_id')
if not establishment_id:
return JsonResponse({'message': retour, 'errorMessage': 'establishment_id manquant', "errorFields": {}, "id": -1}, safe=False, status=status.HTTP_400_BAD_REQUEST)
validatorSubscription = validator.ValidatorSubscription(data=newProfilConnection) validatorSubscription = validator.ValidatorSubscription(data=newProfilConnection)
validationOk, errorFields = validatorSubscription.validate() validationOk, errorFields = validatorSubscription.validate()
if validationOk: if validationOk:
# On vérifie que l'email existe : si ce n'est pas le cas, on retourne une erreur # On vérifie que l'email existe : si ce n'est pas le cas, on retourne une erreur
profil = bdd.getProfile(Profile.objects.all(), newProfilConnection.get('email')) profil = bdd.getProfile(Profile.objects.all(), newProfilConnection.get('email'))
if profil == None: if profil is None:
retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS] retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS]
else: else:
if profil.is_active: # Vérifier si le profil a déjà un rôle actif pour l'établissement donné
retourErreur=error.returnMessage[error.PROFIL_ACTIVE] active_roles = ProfileRole.objects.filter(profile=profil, establishment_id=establishment_id, is_active=True)
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields, "id":profil.id}, safe=False) if active_roles.exists():
retourErreur = error.returnMessage[error.PROFIL_ACTIVE]
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": profil.id}, safe=False)
else: else:
try: try:
profil.set_password(newProfilConnection.get('password1')) profil.set_password(newProfilConnection.get('password1'))
profil.is_active = True
profil.full_clean() profil.full_clean()
profil.save() profil.save()
# Utiliser le sérialiseur ProfileRoleSerializer pour créer ou mettre à jour le rôle
role_data = {
'profile': profil.id,
'establishment_id': establishment_id,
'role_type': ProfileRole.RoleType.PROFIL_PARENT,
'is_active': True
}
role_serializer = ProfileRoleSerializer(data=role_data)
if role_serializer.is_valid():
role_serializer.save()
else:
return JsonResponse(role_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
clear_cache() clear_cache()
retour = error.returnMessage[error.MESSAGE_ACTIVATION_PROFILE] retour = error.returnMessage[error.MESSAGE_ACTIVATION_PROFILE]
retourErreur='' retourErreur = ''
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields, "id":profil.id}, safe=False) return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": profil.id}, safe=False)
except ValidationError as e: except ValidationError as e:
retourErreur = error.returnMessage[error.WRONG_MAIL_FORMAT] retourErreur = error.returnMessage[error.WRONG_MAIL_FORMAT]
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields}, safe=False) return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields}, safe=False)
return JsonResponse({'message':retour, 'errorMessage':retourErreur, "errorFields":errorFields, "id":-1}, safe=False) return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": -1}, safe=False)
@method_decorator(csrf_protect, name='dispatch') @method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
@ -417,26 +457,29 @@ class NewPasswordView(APIView):
def post(self, request): def post(self, request):
retourErreur = error.returnMessage[error.BAD_URL] retourErreur = error.returnMessage[error.BAD_URL]
retour = '' retour = ''
newProfilConnection=JSONParser().parse(request) newProfilConnection = JSONParser().parse(request)
validatorNewPassword = validator.ValidatorNewPassword(data=newProfilConnection) validatorNewPassword = validator.ValidatorNewPassword(data=newProfilConnection)
validationOk, errorFields = validatorNewPassword.validate() validationOk, errorFields = validatorNewPassword.validate()
if validationOk: if validationOk:
profil = bdd.getProfile(Profile.objects.all(), newProfilConnection.get('email')) profil = bdd.getProfile(Profile.objects.all(), newProfilConnection.get('email'))
if profil == None: if profil is None:
retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS] retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS]
else: else:
# Génération d'une URL provisoire pour modifier le mot de passe try:
profil.code = util.genereRandomCode(12) # Génération d'une URL provisoire pour modifier le mot de passe
profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS) profil.code = util.genereRandomCode(12)
profil.save() profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS)
clear_cache() profil.save()
retourErreur = '' clear_cache()
retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD]%(newProfilConnection.get('email')) retourErreur = ''
mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code) retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD] % (newProfilConnection.get('email'))
mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code)
except ValidationError as e:
retourErreur = error.returnMessage[error.WRONG_MAIL_FORMAT]
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields}, safe=False)
return JsonResponse({'message':retour, 'errorMessage':retourErreur, "errorFields":errorFields}, safe=False) return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields}, safe=False)
@method_decorator(csrf_protect, name='dispatch') @method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
@ -465,7 +508,7 @@ class ResetPasswordView(APIView):
def post(self, request, code): def post(self, request, code):
retourErreur = error.returnMessage[error.BAD_URL] retourErreur = error.returnMessage[error.BAD_URL]
retour = '' retour = ''
newProfilConnection=JSONParser().parse(request) newProfilConnection = JSONParser().parse(request)
validatorResetPassword = validator.ValidatorResetPassword(data=newProfilConnection) validatorResetPassword = validator.ValidatorResetPassword(data=newProfilConnection)
validationOk, errorFields = validatorResetPassword.validate() validationOk, errorFields = validatorResetPassword.validate()
@ -474,16 +517,15 @@ class ResetPasswordView(APIView):
if profil: if profil:
if datetime.strptime(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'), '%d-%m-%Y %H:%M') > datetime.strptime(profil.datePeremption, '%d-%m-%Y %H:%M'): if datetime.strptime(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'), '%d-%m-%Y %H:%M') > datetime.strptime(profil.datePeremption, '%d-%m-%Y %H:%M'):
retourErreur = error.returnMessage[error.EXPIRED_URL]%(_uuid) retourErreur = error.returnMessage[error.EXPIRED_URL] % (_uuid)
elif validationOk: elif validationOk:
retour = error.returnMessage[error.PASSWORD_CHANGED] retour = error.returnMessage[error.PASSWORD_CHANGED]
profil.set_password(newProfilConnection.get('password1')) profil.set_password(newProfilConnection.get('password1'))
profil.code = '' profil.code = ''
profil.datePeremption = '' profil.datePeremption = ''
profil.is_active = True
profil.save() profil.save()
clear_cache() clear_cache()
retourErreur='' retourErreur = ''
return JsonResponse({'message':retour, "errorMessage":retourErreur, "errorFields":errorFields}, safe=False) return JsonResponse({'message': retour, "errorMessage": retourErreur, "errorFields": errorFields}, safe=False)

View File

@ -0,0 +1 @@
default_app_config = 'Establishment.apps.EstablishmentConfig'

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class EstablishmentConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'Establishment'

View File

@ -0,0 +1,20 @@
from django.db import models
from django.contrib.postgres.fields import ArrayField
from django.utils.translation import gettext_lazy as _
class StructureType(models.IntegerChoices):
MATERNELLE = 1, _('Maternelle')
PRIMAIRE = 2, _('Primaire')
SECONDAIRE = 3, _('Secondaire')
class Establishment(models.Model):
name = models.CharField(max_length=255, unique=True)
address = models.CharField(max_length=255)
total_capacity = models.IntegerField()
establishment_type = ArrayField(models.IntegerField(choices=StructureType.choices))
licence_code = models.CharField(max_length=100, blank=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name

View File

@ -0,0 +1,91 @@
from rest_framework import serializers
from .models import Establishment
from School.models import SchoolClass, Teacher, Speciality, Fee, Discount, PaymentMode, PaymentPlan
from Subscriptions.models import RegistrationForm, RegistrationFileGroup
from Auth.models import Profile
class EstablishmentSerializer(serializers.ModelSerializer):
profile_count = serializers.SerializerMethodField()
profiles = serializers.SerializerMethodField()
school_class_count = serializers.SerializerMethodField()
school_classes = serializers.SerializerMethodField()
teacher_count = serializers.SerializerMethodField()
teachers = serializers.SerializerMethodField()
speciality_count = serializers.SerializerMethodField()
specialities = serializers.SerializerMethodField()
fee_count = serializers.SerializerMethodField()
fees = serializers.SerializerMethodField()
discount_count = serializers.SerializerMethodField()
discounts = serializers.SerializerMethodField()
active_payment_mode_count = serializers.SerializerMethodField()
active_payment_modes = serializers.SerializerMethodField()
active_payment_plan_count = serializers.SerializerMethodField()
active_payment_plans = serializers.SerializerMethodField()
file_group_count = serializers.SerializerMethodField()
file_groups = serializers.SerializerMethodField()
registration_form_count = serializers.SerializerMethodField()
registration_forms = serializers.SerializerMethodField()
class Meta:
model = Establishment
fields = '__all__'
def get_profile_count(self, obj):
return Profile.objects.filter(roles__establishment=obj).distinct().count()
def get_profiles(self, obj):
return list(Profile.objects.filter(roles__establishment=obj).distinct().values_list('email', flat=True))
def get_school_class_count(self, obj):
return SchoolClass.objects.filter(establishment=obj).distinct().count()
def get_school_classes(self, obj):
return list(SchoolClass.objects.filter(establishment=obj).distinct().values_list('atmosphere_name', flat=True))
def get_teacher_count(self, obj):
return Teacher.objects.filter(profile_role__establishment=obj).distinct().count()
def get_teachers(self, obj):
return list(Teacher.objects.filter(profile_role__establishment=obj).distinct().values_list('last_name', 'first_name'))
def get_speciality_count(self, obj):
return Speciality.objects.filter(establishment=obj).distinct().count()
def get_specialities(self, obj):
return list(Speciality.objects.filter(establishment=obj).distinct().values_list('name', flat=True))
def get_fee_count(self, obj):
return Fee.objects.filter(establishment=obj).distinct().count()
def get_fees(self, obj):
return list(Fee.objects.filter(establishment=obj).distinct().values_list('name', flat=True))
def get_discount_count(self, obj):
return Discount.objects.filter(establishment=obj).distinct().count()
def get_discounts(self, obj):
return list(Discount.objects.filter(establishment=obj).distinct().values_list('name', flat=True))
def get_active_payment_mode_count(self, obj):
return PaymentMode.objects.filter(establishment=obj, is_active=True).distinct().count()
def get_active_payment_modes(self, obj):
return list(PaymentMode.objects.filter(establishment=obj, is_active=True).distinct().values_list('mode', flat=True))
def get_active_payment_plan_count(self, obj):
return PaymentPlan.objects.filter(establishment=obj, is_active=True).distinct().count()
def get_active_payment_plans(self, obj):
return list(PaymentPlan.objects.filter(establishment=obj, is_active=True).distinct().values_list('frequency', flat=True))
def get_file_group_count(self, obj):
return RegistrationFileGroup.objects.filter(establishment=obj).distinct().count()
def get_file_groups(self, obj):
return list(RegistrationFileGroup.objects.filter(establishment=obj).distinct().values_list('name', flat=True))
def get_registration_form_count(self, obj):
return RegistrationForm.objects.filter(establishment=obj).distinct().count()
def get_registration_forms(self, obj):
return list(RegistrationForm.objects.filter(establishment=obj).distinct().values_list('student__last_name', 'student__first_name'))

View File

@ -0,0 +1,7 @@
from django.urls import path, re_path
from .views import EstablishmentListCreateView, EstablishmentDetailView
urlpatterns = [
re_path(r'^establishments$', EstablishmentListCreateView.as_view(), name='establishment_list_create'),
re_path(r'^establishments/(?P<id>[0-9]+)$', EstablishmentDetailView.as_view(), name="establishment_detail"),
]

View File

@ -0,0 +1,51 @@
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 rest_framework.parsers import JSONParser
from rest_framework.views import APIView
from rest_framework import status
from .models import Establishment
from .serializers import EstablishmentSerializer
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class EstablishmentListCreateView(APIView):
def get(self, request):
establishments = getAllObjects(Establishment)
establishments_serializer = EstablishmentSerializer(establishments, many=True)
return JsonResponse(establishments_serializer.data, safe=False, status=status.HTTP_200_OK)
def post(self, request):
establishment_data = JSONParser().parse(request)
establishment_serializer = EstablishmentSerializer(data=establishment_data)
if establishment_serializer.is_valid():
establishment_serializer.save()
return JsonResponse(establishment_serializer.data, safe=False, status=status.HTTP_201_CREATED)
return JsonResponse(establishment_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class EstablishmentDetailView(APIView):
def get(self, request, id=None):
try:
establishment = Establishment.objects.get(id=id)
establishment_serializer = EstablishmentSerializer(establishment)
return JsonResponse(establishment_serializer.data, safe=False)
except Establishment.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
def put(self, request, id):
establishment_data = JSONParser().parse(request)
try:
establishment = Establishment.objects.get(id=id)
except Establishment.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
establishment_serializer = EstablishmentSerializer(establishment, data=establishment_data, partial=True)
if establishment_serializer.is_valid():
establishment_serializer.save()
return JsonResponse(establishment_serializer.data, safe=False)
return JsonResponse(establishment_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
return delete_object(Establishment, id)

View File

@ -4,20 +4,20 @@ from .models import Notification, TypeNotif
from GestionMessagerie.models import Messagerie from GestionMessagerie.models import Messagerie
from Subscriptions.models import RegistrationForm from Subscriptions.models import RegistrationForm
@receiver(post_save, sender=Messagerie) # @receiver(post_save, sender=Messagerie)
def notification_MESSAGE(sender, instance, created, **kwargs): # def notification_MESSAGE(sender, instance, created, **kwargs):
if created: # if created:
Notification.objects.create( # Notification.objects.create(
user=instance.destinataire, # user=instance.destinataire,
message=(TypeNotif.NOTIF_MESSAGE).label, # message=(TypeNotif.NOTIF_MESSAGE).label,
typeNotification=TypeNotif.NOTIF_MESSAGE # typeNotification=TypeNotif.NOTIF_MESSAGE
) # )
@receiver(post_save, sender=RegistrationForm) # @receiver(post_save, sender=RegistrationForm)
def notification_DI(sender, instance, created, **kwargs): # def notification_DI(sender, instance, created, **kwargs):
for responsable in instance.student.guardians.all(): # for responsable in instance.student.guardians.all():
Notification.objects.create( # Notification.objects.create(
user=responsable.associated_profile, # user=responsable.associated_profile,
message=(TypeNotif.NOTIF_DI).label, # message=(TypeNotif.NOTIF_DI).label,
typeNotification=TypeNotif.NOTIF_DI # typeNotification=TypeNotif.NOTIF_DI
) # )

View File

@ -44,6 +44,7 @@ INSTALLED_APPS = [
'GestionNotification.apps.GestionNotificationConfig', 'GestionNotification.apps.GestionNotificationConfig',
'School.apps.SchoolConfig', 'School.apps.SchoolConfig',
'Planning.apps.PlanningConfig', 'Planning.apps.PlanningConfig',
'Establishment.apps.EstablishmentConfig',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',

View File

@ -46,6 +46,7 @@ urlpatterns = [
path("School/", include(("School.urls", 'School'), namespace='School')), path("School/", include(("School.urls", 'School'), namespace='School')),
path("DocuSeal/", include(("DocuSeal.urls", 'DocuSeal'), namespace='DocuSeal')), path("DocuSeal/", include(("DocuSeal.urls", 'DocuSeal'), namespace='DocuSeal')),
path("Planning/", include(("Planning.urls", 'Planning'), namespace='Planning')), path("Planning/", include(("Planning.urls", 'Planning'), namespace='Planning')),
path("Establishment/", include(("Establishment.urls", 'Establishment'), namespace='Establishment')),
# Documentation Api # Documentation Api
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),

View File

@ -3,7 +3,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.conf import settings from django.conf import settings
from School.models import Establishment from Establishment.models import Establishment
class RecursionType(models.IntegerChoices): class RecursionType(models.IntegerChoices):
RECURSION_NONE = 0, _('Aucune') RECURSION_NONE = 0, _('Aucune')

View File

@ -1,14 +1,5 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.db.models.signals import post_migrate
def create_speciality(sender, **kwargs):
from .models import Speciality
if not Speciality.objects.filter(name='GROUPE').exists():
Speciality.objects.create(name='GROUPE', color_code='#FF0000')
class SchoolConfig(AppConfig): class SchoolConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'School' name = 'School'
def ready(self):
post_migrate.connect(create_speciality, sender=self)

View File

@ -1,5 +1,4 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from Subscriptions.models import ( from Subscriptions.models import (
RegistrationForm, RegistrationForm,
Student, Student,
@ -10,9 +9,8 @@ from Subscriptions.models import (
RegistrationTemplateMaster, RegistrationTemplateMaster,
RegistrationTemplate RegistrationTemplate
) )
from Auth.models import Profile from Auth.models import Profile, ProfileRole
from School.models import ( from School.models import (
Establishment,
FeeType, FeeType,
Speciality, Speciality,
Teacher, Teacher,
@ -20,8 +18,7 @@ from School.models import (
PaymentMode, PaymentMode,
PaymentModeType, PaymentModeType,
PaymentPlan, PaymentPlan,
PaymentPlanType, PaymentPlanType,
StructureType,
DiscountType DiscountType
) )
from django.utils import timezone from django.utils import timezone
@ -34,6 +31,19 @@ from faker import Faker
import random import random
import json import json
from School.serializers import (
FeeSerializer,
DiscountSerializer,
PaymentModeSerializer,
PaymentPlanSerializer,
SpecialitySerializer,
TeacherSerializer,
SchoolClassSerializer
)
from Auth.serializers import ProfileSerializer, ProfileRoleSerializer
from Establishment.serializers import EstablishmentSerializer
from Subscriptions.serializers import RegistrationFormSerializer, GuardianSerializer
# Définir le chemin vers le dossier mock_datas # Définir le chemin vers le dossier mock_datas
MOCK_DATAS_PATH = os.path.join(settings.BASE_DIR, 'School', 'management', 'mock_datas') MOCK_DATAS_PATH = os.path.join(settings.BASE_DIR, 'School', 'management', 'mock_datas')
@ -41,176 +51,306 @@ class Command(BaseCommand):
help = 'Initialise toutes les données mock' help = 'Initialise toutes les données mock'
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
self.create_or_update_establishments() self.init_establishments()
self.create_or_update_fees() self.init_profiles()
self.create_or_update_discounts() self.init_fees()
self.create_or_update_payment_modes() self.init_discounts()
self.create_or_update_payment_plans() self.init_payment_modes()
self.create_or_update_specialities() self.init_payment_plans()
self.create_or_update_teachers() self.init_specialities()
self.create_or_update_school_classes() self.init_teachers()
self.create_or_update_registration_file_group() self.init_guardians()
self.create_register_form() self.init_school_classes()
self.init_file_group()
self.init_register_form()
def load_data(self, filename): def load_data(self, filename):
with open(os.path.join(MOCK_DATAS_PATH, filename), 'r') as file: with open(os.path.join(MOCK_DATAS_PATH, filename), 'r') as file:
return json.load(file) return json.load(file)
def create_or_update_establishments(self): def init_establishments(self):
establishments_data = self.load_data('establishments.json') establishments_data = self.load_data('establishments.json')
self.establishments = [] self.establishments = []
for establishment_data in establishments_data: for establishment_data in establishments_data:
establishment, created = Establishment.objects.update_or_create( serializer = EstablishmentSerializer(data=establishment_data)
name=establishment_data["name"], if serializer.is_valid():
defaults=establishment_data establishment = serializer.save()
) self.establishments.append(establishment)
self.establishments.append(establishment) self.stdout.write(self.style.SUCCESS(f'Establishment {establishment.name} created or updated successfully'))
if created:
self.stdout.write(self.style.SUCCESS(f'Establishment {establishment.name} created successfully'))
else: else:
self.stdout.write(self.style.SUCCESS(f'Establishment {establishment.name} updated successfully')) self.stdout.write(self.style.ERROR(f'Error in data for establishment: {serializer.errors}'))
def create_or_update_fees(self): def init_profiles(self):
profiles_data = self.load_data('profiles.json')
for profile_data in profiles_data:
# Randomize the number of roles to create (between 1 et 3)
num_roles = random.randint(1, 3)
selected_roles = []
for _ in range(num_roles):
establishment = random.choice(self.establishments)
role_type = random.choice([ProfileRole.RoleType.PROFIL_ECOLE, ProfileRole.RoleType.PROFIL_ADMIN, ProfileRole.RoleType.PROFIL_PARENT])
# Ensure no duplicate ADMIN role for the same establishment
if role_type == ProfileRole.RoleType.PROFIL_ADMIN:
if any(role['role_type'] == ProfileRole.RoleType.PROFIL_ADMIN and role['establishment'] == establishment.id for role in selected_roles):
continue
selected_roles.append({
"role_type": role_type,
"establishment": establishment.id,
"establishment_name": establishment.name
})
# Generate email based on the selected roles and establishment
role_types = '-'.join([f"{ProfileRole.RoleType(role['role_type']).name.replace('PROFIL_', '')}_{role['establishment_name'].replace(' ', '')}" for role in selected_roles])
email = f"{profile_data['username']}-{role_types}@exemple.com"
# Add email to profile data
profile_data['email'] = email
serializer = ProfileSerializer(data=profile_data)
if serializer.is_valid():
profile = serializer.save()
profile.set_password(profile_data["password"])
profile.save()
self.stdout.write(self.style.SUCCESS(f'Profile {profile.email} created successfully'))
# Create or update the profile role for each selected role using ProfileRoleSerializer
for role in selected_roles:
role_data = {
"profile": profile.id,
"establishment": role["establishment"],
"role_type": role["role_type"],
"is_active": True
}
role_serializer = ProfileRoleSerializer(data=role_data)
if role_serializer.is_valid():
role_serializer.save()
else:
self.stdout.write(self.style.ERROR(f'Error in data for profile role: {role_serializer.errors}'))
else:
self.stdout.write(self.style.ERROR(f'Error in data for profile: {serializer.errors}'))
def init_fees(self):
fees_data = self.load_data('fees.json') fees_data = self.load_data('fees.json')
for fee_data in fees_data: for fee_data in fees_data:
establishment = random.choice(self.establishments) establishment = random.choice(self.establishments)
print(f'establishment : {establishment}')
fee_data["name"] = f"{fee_data['name']} - {establishment.name}" fee_data["name"] = f"{fee_data['name']} - {establishment.name}"
fee_data["establishment"] = establishment fee_data["establishment"] = establishment.id
Fee.objects.update_or_create( fee_data["type"] = random.choice([FeeType.REGISTRATION_FEE, FeeType.TUITION_FEE])
name=fee_data["name"],
type=fee_data["type"],
defaults=fee_data
)
self.stdout.write(self.style.SUCCESS('Fees initialized or updated successfully')) serializer = FeeSerializer(data=fee_data)
if serializer.is_valid():
fee = serializer.save()
self.stdout.write(self.style.SUCCESS(f'Fee {fee.name} created successfully'))
else:
self.stdout.write(self.style.ERROR(f'Error in data for fee: {serializer.errors}'))
def create_or_update_discounts(self): def init_discounts(self):
discounts_data = self.load_data('discounts.json') discounts_data = self.load_data('discounts.json')
for discount_data in discounts_data: for discount_data in discounts_data:
establishment = random.choice(self.establishments) establishment = random.choice(self.establishments)
discount_data["name"] = f"{discount_data['name']} - {establishment.name}" discount_data["name"] = f"{discount_data['name']} - {establishment.name}"
discount_data["establishment"] = establishment discount_data["establishment"] = establishment.id
Discount.objects.update_or_create( discount_data["type"] = random.choice([FeeType.REGISTRATION_FEE, FeeType.TUITION_FEE])
name=discount_data["name"], discount_data["discount_type"] = random.choice([DiscountType.CURRENCY, DiscountType.PERCENT])
type=discount_data["type"],
discount_type=discount_data["discount_type"],
defaults=discount_data
)
self.stdout.write(self.style.SUCCESS('Discounts initialized or updated successfully')) serializer = DiscountSerializer(data=discount_data)
if serializer.is_valid():
discount = serializer.save()
self.stdout.write(self.style.SUCCESS(f'Discount {discount.name} created successfully'))
else:
self.stdout.write(self.style.ERROR(f'Error in data for discount: {serializer.errors}'))
def create_or_update_payment_modes(self): def init_payment_modes(self):
payment_modes_data = self.load_data('payment_modes.json') modes = [PaymentModeType.SEPA, PaymentModeType.TRANSFER, PaymentModeType.CHECK, PaymentModeType.CASH]
types = [FeeType.REGISTRATION_FEE, FeeType.TUITION_FEE]
for payment_mode_data in payment_modes_data: for establishment in self.establishments:
establishment = random.choice(self.establishments) for mode in modes:
payment_mode_data["establishment"] = establishment for type in types:
PaymentMode.objects.update_or_create( payment_mode_data = {
mode=payment_mode_data["mode"], "mode": mode,
type=payment_mode_data["type"], "type": type,
defaults=payment_mode_data "is_active": random.choice([True, False]),
) "establishment": establishment.id
}
self.stdout.write(self.style.SUCCESS('Payment Modes initialized or updated successfully')) serializer = PaymentModeSerializer(data=payment_mode_data)
if serializer.is_valid():
payment_mode = serializer.save()
self.stdout.write(self.style.SUCCESS(f'Payment Mode {payment_mode} created successfully'))
else:
self.stdout.write(self.style.ERROR(f'Error in data for payment mode: {serializer.errors}'))
def create_or_update_payment_plans(self): def init_payment_plans(self):
payment_plans_data = self.load_data('payment_plans.json') frequencies = [PaymentPlanType.ONE_TIME, PaymentPlanType.THREE_TIMES, PaymentPlanType.TEN_TIMES, PaymentPlanType.TWELVE_TIMES]
types = [FeeType.REGISTRATION_FEE, FeeType.TUITION_FEE]
current_date = timezone.now().date() current_date = timezone.now().date()
for payment_plan_data in payment_plans_data: for establishment in self.establishments:
establishment = random.choice(self.establishments) for frequency in frequencies:
payment_plan_data["establishment"] = establishment for type in types:
payment_plan_data["due_dates"] = [current_date + relativedelta(months=1)] payment_plan_data = {
if payment_plan_data["frequency"] == PaymentPlanType.THREE_TIMES: "frequency": frequency,
payment_plan_data["due_dates"] = [current_date + relativedelta(months=1+4*i) for i in range(3)] "type": type,
elif payment_plan_data["frequency"] == PaymentPlanType.TEN_TIMES: "is_active": random.choice([True, False]),
payment_plan_data["due_dates"] = [current_date + relativedelta(months=1+i) for i in range(10)] "establishment": establishment.id,
elif payment_plan_data["frequency"] == PaymentPlanType.TWELVE_TIMES: "due_dates": self.generate_due_dates(frequency, current_date)
payment_plan_data["due_dates"] = [current_date + relativedelta(months=1+i) for i in range(12)] }
PaymentPlan.objects.update_or_create( serializer = PaymentPlanSerializer(data=payment_plan_data)
frequency=payment_plan_data["frequency"], if serializer.is_valid():
type=payment_plan_data["type"], payment_plan = serializer.save()
defaults=payment_plan_data self.stdout.write(self.style.SUCCESS(f'Payment Plan {payment_plan} created successfully'))
) else:
self.stdout.write(self.style.ERROR(f'Error in data for payment plan: {serializer.errors}'))
self.stdout.write(self.style.SUCCESS('Payment Plans initialized or updated successfully')) def generate_due_dates(self, frequency, start_date):
if frequency == PaymentPlanType.ONE_TIME:
return [start_date + relativedelta(months=1)]
elif frequency == PaymentPlanType.THREE_TIMES:
return [start_date + relativedelta(months=1+4*i) for i in range(3)]
elif frequency == PaymentPlanType.TEN_TIMES:
return [start_date + relativedelta(months=1+i) for i in range(10)]
elif frequency == PaymentPlanType.TWELVE_TIMES:
return [start_date + relativedelta(months=1+i) for i in range(12)]
def create_or_update_specialities(self): def init_specialities(self):
specialities_data = self.load_data('specialities.json') specialities_data = self.load_data('specialities.json')
for speciality_data in specialities_data: for speciality_data in specialities_data:
Speciality.objects.update_or_create(
name=speciality_data["name"],
defaults=speciality_data
)
self.stdout.write(self.style.SUCCESS('Specialities initialized or updated successfully'))
def create_or_update_teachers(self):
teachers_data = self.load_data('teachers.json')
for teacher_data in teachers_data:
specialities = teacher_data.pop("specialities")
email = teacher_data["email"]
droit = teacher_data.pop("droit")
establishment = random.choice(self.establishments) establishment = random.choice(self.establishments)
speciality_data["name"] = f"{speciality_data['name']} - {establishment.name}"
speciality_data["establishment"] = establishment.id
# Create or update the user profile serializer = SpecialitySerializer(data=speciality_data)
user, created = Profile.objects.update_or_create( if serializer.is_valid():
email=email, speciality = serializer.save()
defaults={ self.stdout.write(self.style.SUCCESS(f'Speciality {speciality.name} created successfully'))
"username": email, else:
"email": email, self.stdout.write(self.style.ERROR(f'Error in data for speciality: {serializer.errors}'))
"is_active": True,
"password": "Provisoire01!", def init_teachers(self):
"droit": droit, fake = Faker()
"establishment": establishment
# Récupérer tous les profils dont le role_type est soit ECOLE soit ADMIN
profiles = Profile.objects.filter(roles__role_type__in=[ProfileRole.RoleType.PROFIL_ECOLE, ProfileRole.RoleType.PROFIL_ADMIN]).distinct()
for profile in profiles:
# Récupérer les rôles associés au profil
profile_roles = ProfileRole.objects.filter(profile=profile, role_type__in=[ProfileRole.RoleType.PROFIL_ECOLE, ProfileRole.RoleType.PROFIL_ADMIN])
for profile_role in profile_roles:
establishment = profile_role.establishment
teacher_data = {
"last_name": fake.last_name(),
"first_name": f"{fake.first_name()} - {establishment.name}",
"profile_role": profile_role.id
} }
)
if created:
user.set_password("Provisoire01!")
user.save()
# Create or update the teacher establishment_specialities = list(Speciality.objects.filter(establishment=establishment))
teacher, created = Teacher.objects.update_or_create( num_specialities = min(random.randint(1, 3), len(establishment_specialities))
email=email, selected_specialities = random.sample(establishment_specialities, num_specialities)
defaults={**teacher_data, "associated_profile_id": user.id}
) # Créer l'enseignant si il n'existe pas
teacher.specialities.set(Speciality.objects.filter(name__in=specialities)) teacher_serializer = TeacherSerializer(data=teacher_data)
teacher.save() if teacher_serializer.is_valid():
teacher = teacher_serializer.save()
# Associer les spécialités
teacher.specialities.set(selected_specialities)
teacher.save()
self.stdout.write(self.style.SUCCESS(f'Teacher {teacher.last_name} created successfully for establishment {establishment.name}'))
else:
self.stdout.write(self.style.ERROR(f'Error in data for teacher: {teacher_serializer.errors}'))
self.stdout.write(self.style.SUCCESS('Teachers initialized or updated successfully')) self.stdout.write(self.style.SUCCESS('Teachers initialized or updated successfully'))
def create_or_update_school_classes(self): def init_guardians(self):
fake = Faker()
# Récupérer tous les profils dont le role_type est PROFIL_PARENT
profiles = Profile.objects.filter(roles__role_type=ProfileRole.RoleType.PROFIL_PARENT).distinct()
for profile in profiles:
# Récupérer les rôles associés au profil
profile_roles = ProfileRole.objects.filter(profile=profile, role_type=ProfileRole.RoleType.PROFIL_PARENT)
for profile_role in profile_roles:
establishment = profile_role.establishment
guardian_data = {
"last_name": fake.last_name(),
"first_name": f"{fake.first_name()} - {establishment.name}",
"profile_role": profile_role.id,
"birth_date": fake.date_of_birth().strftime('%Y-%m-%d'), # Convertir en chaîne de caractères valide
"address": fake.address(),
"phone": fake.phone_number(),
"profession": fake.job()
}
# Créer le guardian si il n'existe pas
guardian_serializer = GuardianSerializer(data=guardian_data)
if guardian_serializer.is_valid():
guardian = guardian_serializer.save()
self.stdout.write(self.style.SUCCESS(f'Guardian {guardian.last_name} created successfully for establishment {establishment.name}'))
else:
self.stdout.write(self.style.ERROR(f'Error in data for guardian: {guardian_serializer.errors}'))
self.stdout.write(self.style.SUCCESS('Guardians initialized or updated successfully'))
def init_school_classes(self):
school_classes_data = self.load_data('school_classes.json') school_classes_data = self.load_data('school_classes.json')
for index, class_data in enumerate(school_classes_data, start=1): for index, class_data in enumerate(school_classes_data, start=1):
teachers_ids = class_data.pop("teachers") # Randomize establishment
establishment = random.choice(self.establishments) establishment = random.choice(self.establishments)
class_data["atmosphere_name"] = f"Classe {index} - {establishment.name}" class_data["atmosphere_name"] = f"Classe {index} - {establishment.name}"
class_data["establishment"] = establishment class_data["establishment"] = establishment.id
school_class, created = SchoolClass.objects.update_or_create(
atmosphere_name=class_data["atmosphere_name"], # Randomize levels
school_year=class_data["school_year"], class_data["levels"] = random.sample(range(1, 10), random.randint(1, 5))
defaults=class_data
) # Randomize teachers
school_class.teachers.set(teachers_ids) establishment_teachers = list(Teacher.objects.filter(profile_role__establishment=establishment))
school_class.save() num_teachers = min(random.randint(1, 10), len(establishment_teachers))
selected_teachers = random.sample(establishment_teachers, num_teachers)
teachers_ids = [teacher.id for teacher in selected_teachers]
# Use the serializer to create or update the school class
class_data["teachers"] = teachers_ids
serializer = SchoolClassSerializer(data=class_data)
if serializer.is_valid():
school_class = serializer.save()
self.stdout.write(self.style.SUCCESS(f'SchoolClass {school_class.atmosphere_name} created or updated successfully'))
else:
self.stdout.write(self.style.ERROR(f'Error in data for school class: {serializer.errors}'))
self.stdout.write(self.style.SUCCESS('SchoolClasses initialized or updated successfully')) self.stdout.write(self.style.SUCCESS('SchoolClasses initialized or updated successfully'))
def create_or_update_registration_file_group(self): def init_file_group(self):
file_groups_data = self.load_data('file_groups.json') fake = Faker()
for group_data in file_groups_data: for establishment in self.establishments:
RegistrationFileGroup.objects.update_or_create(name=group_data["name"], defaults=group_data) for i in range(1, 4): # Créer 3 groupes de fichiers par établissement
self.stdout.write(self.style.SUCCESS(f'RegistrationFileGroup {group_data["name"]} initialized or updated successfully')) name = f"Fichiers d'inscription - {fake.word()} - {establishment.name}"
description = fake.sentence()
group_data = {
"name": name,
"description": description,
"establishment": establishment
}
RegistrationFileGroup.objects.update_or_create(name=name, defaults=group_data)
self.stdout.write(self.style.SUCCESS(f'RegistrationFileGroup {name} initialized or updated successfully'))
def create_register_form(self): self.stdout.write(self.style.SUCCESS('All RegistrationFileGroups initialized or updated successfully'))
def init_register_form(self):
fake = Faker('fr_FR') # Utiliser le locale français pour Faker fake = Faker('fr_FR') # Utiliser le locale français pour Faker
file_group_count = RegistrationFileGroup.objects.count() file_group_count = RegistrationFileGroup.objects.count()
@ -218,40 +358,13 @@ class Command(BaseCommand):
for _ in range(50): for _ in range(50):
establishment = random.choice(self.establishments) establishment = random.choice(self.establishments)
# Générer des données fictives pour le profil # Récupérer un guardian aléatoire déjà créé
profile_data = { guardian = Guardian.objects.order_by('?').first()
"email": fake.email(),
"droit": 2,
"username": fake.user_name(),
"is_active": True,
"password": "Provisoire01!",
"establishment": establishment
}
user, created = Profile.objects.update_or_create(
email=profile_data["email"],
defaults={
"username": profile_data["username"],
"email": profile_data["email"],
"is_active": profile_data["is_active"],
"droit": profile_data["droit"],
"establishment": profile_data["establishment"]
}
)
if created:
user.set_password(profile_data["password"])
user.save()
# Générer des données fictives pour le guardian
guardian_data = {
"associated_profile_id": user.id,
"email": profile_data["email"],
}
# Générer des données fictives pour l'étudiant # Générer des données fictives pour l'étudiant
student_data = { student_data = {
"last_name": f"{fake.last_name()} - {establishment.name}", "last_name": fake.last_name(),
"first_name": fake.first_name(), "first_name": f"{fake.first_name()} - {establishment.name}",
"address": fake.address(), "address": fake.address(),
"birth_date": fake.date_of_birth(), "birth_date": fake.date_of_birth(),
"birth_place": fake.city(), "birth_place": fake.city(),
@ -261,16 +374,12 @@ class Command(BaseCommand):
"level": fake.random_int(min=1, max=6) "level": fake.random_int(min=1, max=6)
} }
# Créer ou mettre à jour l'étudiant et le guardian # Créer ou mettre à jour l'étudiant
student, created = Student.objects.get_or_create( student, created = Student.objects.get_or_create(
last_name=student_data["last_name"], last_name=student_data["last_name"],
first_name=student_data["first_name"], first_name=student_data["first_name"],
defaults=student_data defaults=student_data
) )
guardian, created = Guardian.objects.get_or_create(
last_name=guardian_data["email"],
defaults=guardian_data
)
student.guardians.add(guardian) student.guardians.add(guardian)
# Récupérer les frais et les réductions # Récupérer les frais et les réductions
@ -279,18 +388,23 @@ class Command(BaseCommand):
# Créer les données du formulaire d'inscription # Créer les données du formulaire d'inscription
register_form_data = { register_form_data = {
"student": student,
"fileGroup": RegistrationFileGroup.objects.get(id=fake.random_int(min=1, max=file_group_count)), "fileGroup": RegistrationFileGroup.objects.get(id=fake.random_int(min=1, max=file_group_count)),
"establishment": establishment, "establishment": establishment,
"status": fake.random_int(min=1, max=3) "status": fake.random_int(min=1, max=3)
} }
# Créer ou mettre à jour le formulaire d'inscription # Créer ou mettre à jour le formulaire d'inscription
register_form, created = RegistrationForm.objects.get_or_create(student=student, defaults=register_form_data) register_form, created = RegistrationForm.objects.get_or_create(
register_form.fees.set(fees) student=student,
register_form.discounts.set(discounts) establishment=establishment,
if not created: defaults=register_form_data
register_form.fileGroup = file_group )
register_form.save()
if created:
register_form.fees.set(fees)
register_form.discounts.set(discounts)
self.stdout.write(self.style.SUCCESS(f'RegistrationForm for student {student.last_name} created successfully'))
else:
self.stdout.write(self.style.SUCCESS(f'RegistrationForm for student {student.last_name} already exists'))
self.stdout.write(self.style.SUCCESS('50 RegistrationForms initialized or updated successfully')) self.stdout.write(self.style.SUCCESS('50 RegistrationForms initialized or updated successfully'))

View File

@ -2,15 +2,41 @@
{ {
"name": "Parrainage", "name": "Parrainage",
"amount": "10.00", "amount": "10.00",
"description": "Réduction pour parrainage", "description": "Réduction pour parrainage"
"discount_type": 1,
"type": 1
}, },
{ {
"name": "Réinscription", "name": "Réinscription",
"amount": "100.00", "amount": "100.00",
"description": "Réduction pour Réinscription", "description": "Réduction pour Réinscription"
"discount_type": 1, },
"type": 0 {
"name": "Famille nombreuse",
"amount": "50.00",
"description": "Réduction pour les familles nombreuses"
},
{
"name": "Excellence académique",
"amount": "200.00",
"description": "Réduction pour les élèves ayant des résultats académiques exceptionnels"
},
{
"name": "Sportif de haut niveau",
"amount": "150.00",
"description": "Réduction pour les élèves pratiquant un sport de haut niveau"
},
{
"name": "Artiste talentueux",
"amount": "100.00",
"description": "Réduction pour les élèves ayant des talents artistiques"
},
{
"name": "Bourse d'études",
"amount": "300.00",
"description": "Réduction pour les élèves bénéficiant d'une bourse d'études"
},
{
"name": "Réduction spéciale",
"amount": "75.00",
"description": "Réduction spéciale pour des occasions particulières"
} }
] ]

View File

@ -3,35 +3,30 @@
"name": "Frais d'inscription", "name": "Frais d'inscription",
"base_amount": "150.00", "base_amount": "150.00",
"description": "Montant de base", "description": "Montant de base",
"is_active": true, "is_active": true
"type": 0
}, },
{ {
"name": "Matériel", "name": "Matériel",
"base_amount": "85.00", "base_amount": "85.00",
"description": "Livres / jouets", "description": "Livres / jouets",
"is_active": true, "is_active": true
"type": 0
}, },
{ {
"name": "Sorties périscolaires", "name": "Sorties périscolaires",
"base_amount": "120.00", "base_amount": "120.00",
"description": "Sorties", "description": "Sorties",
"is_active": true, "is_active": true
"type": 0
}, },
{ {
"name": "Les colibris", "name": "Les colibris",
"base_amount": "4500.00", "base_amount": "4500.00",
"description": "TPS / PS / MS / GS", "description": "TPS / PS / MS / GS",
"is_active": true, "is_active": true
"type": 1
}, },
{ {
"name": "Les butterflies", "name": "Les butterflies",
"base_amount": "5000.00", "base_amount": "5000.00",
"description": "CP / CE1 / CE2 / CM1 / CM2", "description": "CP / CE1 / CE2 / CM1 / CM2",
"is_active": true, "is_active": true
"type": 1
} }
] ]

View File

@ -1,22 +0,0 @@
[
{
"name": "Fichiers d'inscription - Classe 1 - Ecole A",
"description": "Fichiers d'inscription pour la Classe 1 de l'école Ecole A"
},
{
"name": "Fichiers d'inscription - Classe 2 - Ecole B",
"description": "Fichiers d'inscription pour la Classe 2 de l'école Ecole B"
},
{
"name": "Fichiers d'inscription - Classe 3 - Ecole C",
"description": "Fichiers d'inscription pour la Classe 3 de l'école Ecole C"
},
{
"name": "Fichiers d'inscription - Classe 4 - Ecole A",
"description": "Fichiers d'inscription pour la Classe 4 de l'école Ecole A"
},
{
"name": "Fichiers d'inscription - Classe 5 - Ecole B",
"description": "Fichiers d'inscription pour la Classe 5 de l'école Ecole B"
}
]

View File

@ -1,12 +0,0 @@
[
{
"mode": 4,
"type": 0,
"is_active": true
},
{
"mode": 2,
"type": 1,
"is_active": true
}
]

View File

@ -1,22 +0,0 @@
[
{
"frequency": 1,
"type": 0,
"is_active": true
},
{
"frequency": 3,
"type": 1,
"is_active": true
},
{
"frequency": 10,
"type": 1,
"is_active": true
},
{
"frequency": 12,
"type": 1,
"is_active": true
}
]

View File

@ -0,0 +1,34 @@
[
{
"username": "albus.dumbledore",
"password": "Provisoire01!"
},
{
"username": "severus.rogue",
"password": "Provisoire01!"
},
{
"username": "minerva.mcgonagall",
"password": "Provisoire01!"
},
{
"username": "pomona.chourave",
"password": "Provisoire01!"
},
{
"username": "rubeus.hagrid",
"password": "Provisoire01!"
},
{
"username": "filius.flitwick",
"password": "Provisoire01!"
},
{
"username": "pomona.sprout",
"password": "Provisoire01!"
},
{
"username": "aurora.sinistra",
"password": "Provisoire01!"
}
]

View File

@ -7,8 +7,7 @@
"levels": [2, 3, 4], "levels": [2, 3, 4],
"type": 1, "type": 1,
"time_range": ["08:30", "17:30"], "time_range": ["08:30", "17:30"],
"opening_days": [1, 2, 4, 5], "opening_days": [1, 2, 4, 5]
"teachers": [2]
}, },
{ {
"age_range": "2-3", "age_range": "2-3",
@ -18,8 +17,7 @@
"levels": [1], "levels": [1],
"type": 1, "type": 1,
"time_range": ["08:30", "17:30"], "time_range": ["08:30", "17:30"],
"opening_days": [1, 2, 4, 5], "opening_days": [1, 2, 4, 5]
"teachers": [3]
}, },
{ {
"age_range": "6-12", "age_range": "6-12",
@ -29,8 +27,7 @@
"levels": [5, 6, 7, 8, 9], "levels": [5, 6, 7, 8, 9],
"type": 1, "type": 1,
"time_range": ["08:30", "17:30"], "time_range": ["08:30", "17:30"],
"opening_days": [1, 2, 4, 5], "opening_days": [1, 2, 4, 5]
"teachers": [4]
}, },
{ {
"age_range": "4-6", "age_range": "4-6",
@ -40,8 +37,7 @@
"levels": [4, 5], "levels": [4, 5],
"type": 1, "type": 1,
"time_range": ["08:30", "17:30"], "time_range": ["08:30", "17:30"],
"opening_days": [1, 2, 4, 5], "opening_days": [1, 2, 4, 5]
"teachers": [1]
}, },
{ {
"age_range": "7-9", "age_range": "7-9",
@ -51,7 +47,6 @@
"levels": [6, 7], "levels": [6, 7],
"type": 1, "type": 1,
"time_range": ["08:30", "17:30"], "time_range": ["08:30", "17:30"],
"opening_days": [1, 2, 4, 5], "opening_days": [1, 2, 4, 5]
"teachers": [2]
} }
] ]

View File

@ -1,58 +0,0 @@
[
{
"last_name": "DUMBLEDORE",
"first_name": "Albus",
"email": "albus.dumbledore@gmail.com",
"specialities": ["GROUPE"],
"droit": 1
},
{
"last_name": "ROGUE",
"first_name": "Severus",
"email": "severus.rogue@gmail.com",
"specialities": ["ANGLAIS"],
"droit": 2
},
{
"last_name": "MC GONAGALL",
"first_name": "Minerva",
"email": "minerva.mcgonagall@gmail.com",
"specialities": ["MATHS", "HISTOIRE"],
"droit": 2
},
{
"last_name": "CHOURAVE",
"first_name": "Pomona",
"email": "pomona.chourave@gmail.com",
"specialities": ["MATHS", "FRANCAIS", "SPORT"],
"droit": 1
},
{
"last_name": "HAGRID",
"first_name": "Rubeus",
"email": "rubeus.hagrid@gmail.com",
"specialities": ["SCIENCES"],
"droit": 2
},
{
"last_name": "FLITWICK",
"first_name": "Filius",
"email": "filius.flitwick@gmail.com",
"specialities": ["MUSIQUE"],
"droit": 1
},
{
"last_name": "SPROUT",
"first_name": "Pomona",
"email": "pomona.sprout@gmail.com",
"specialities": ["ART"],
"droit": 2
},
{
"last_name": "SINISTRA",
"first_name": "Aurora",
"email": "aurora.sinistra@gmail.com",
"specialities": ["INFORMATIQUE"],
"droit": 2
}
]

View File

@ -1,5 +1,6 @@
from django.db import models from django.db import models
from Auth.models import Profile from Auth.models import ProfileRole
from Establishment.models import Establishment
from django.db.models import JSONField from django.db.models import JSONField
from django.dispatch import receiver from django.dispatch import receiver
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
@ -18,27 +19,11 @@ LEVEL_CHOICES = [
(9, 'Cours Moyen 2 (CM2)') (9, 'Cours Moyen 2 (CM2)')
] ]
class StructureType(models.IntegerChoices):
MATERNELLE = 1, _('Maternelle')
PRIMAIRE = 2, _('Primaire')
SECONDAIRE = 3, _('Secondaire')
class Establishment(models.Model):
name = models.CharField(max_length=255, unique=True)
address = models.CharField(max_length=255)
total_capacity = models.IntegerField()
establishment_type = ArrayField(models.IntegerField(choices=StructureType.choices))
licence_code = models.CharField(max_length=100, blank=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Speciality(models.Model): class Speciality(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
updated_date = models.DateTimeField(auto_now=True) updated_date = models.DateTimeField(auto_now=True)
color_code = models.CharField(max_length=7, default='#FFFFFF') color_code = models.CharField(max_length=7, default='#FFFFFF')
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='specialities')
def __str__(self): def __str__(self):
return self.name return self.name
@ -46,9 +31,8 @@ class Speciality(models.Model):
class Teacher(models.Model): class Teacher(models.Model):
last_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100)
first_name = models.CharField(max_length=100) first_name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
specialities = models.ManyToManyField(Speciality, blank=True) specialities = models.ManyToManyField(Speciality, blank=True)
associated_profile = models.ForeignKey(Profile, on_delete=models.CASCADE, null=True, blank=True) profile_role = models.OneToOneField(ProfileRole, on_delete=models.CASCADE, related_name='teacher_profile', null=True, blank=True)
updated_date = models.DateTimeField(auto_now=True) updated_date = models.DateTimeField(auto_now=True)
def __str__(self): def __str__(self):

View File

@ -1,7 +1,8 @@
from rest_framework import serializers from rest_framework import serializers
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan, PaymentMode, Establishment from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan, PaymentMode
from Auth.models import Profile from Auth.models import Profile, ProfileRole
from Subscriptions.models import Student from Subscriptions.models import Student
from Establishment.models import Establishment
from N3wtSchool import settings, bdd from N3wtSchool import settings, bdd
from django.utils import timezone from django.utils import timezone
import pytz import pytz
@ -30,9 +31,10 @@ class TeacherDetailSerializer(serializers.ModelSerializer):
class TeacherSerializer(serializers.ModelSerializer): class TeacherSerializer(serializers.ModelSerializer):
specialities = serializers.PrimaryKeyRelatedField(queryset=Speciality.objects.all(), many=True, required=False) specialities = serializers.PrimaryKeyRelatedField(queryset=Speciality.objects.all(), many=True, required=False)
specialities_details = serializers.SerializerMethodField() specialities_details = serializers.SerializerMethodField()
associated_profile = serializers.PrimaryKeyRelatedField(queryset=Profile.objects.all(), required=True) profile_role = serializers.PrimaryKeyRelatedField(queryset=ProfileRole.objects.all(), required=True)
updated_date_formatted = serializers.SerializerMethodField() updated_date_formatted = serializers.SerializerMethodField()
droit = serializers.SerializerMethodField() role_type = serializers.SerializerMethodField()
associated_profile_email = serializers.SerializerMethodField()
class Meta: class Meta:
model = Teacher model = Teacher
@ -40,12 +42,12 @@ class TeacherSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
specialities_data = validated_data.pop('specialities', None) specialities_data = validated_data.pop('specialities', None)
associated_profile = validated_data.pop('associated_profile', None) profile_role = validated_data.pop('profile_role', None)
teacher = Teacher.objects.create(**validated_data) teacher = Teacher.objects.create(**validated_data)
if specialities_data: if specialities_data:
teacher.specialities.set(specialities_data) teacher.specialities.set(specialities_data)
if associated_profile: if profile_role:
teacher.associated_profile = associated_profile teacher.profile_role = profile_role
teacher.save() teacher.save()
return teacher return teacher
@ -54,7 +56,7 @@ class TeacherSerializer(serializers.ModelSerializer):
instance.last_name = validated_data.get('last_name', instance.last_name) instance.last_name = validated_data.get('last_name', instance.last_name)
instance.first_name = validated_data.get('first_name', instance.first_name) instance.first_name = validated_data.get('first_name', instance.first_name)
instance.email = validated_data.get('email', instance.email) instance.email = validated_data.get('email', instance.email)
instance.associated_profile = validated_data.get('associated_profile', instance.associated_profile) instance.profile_role = validated_data.get('profile_role', instance.profile_role)
instance.save() instance.save()
if specialities_data: if specialities_data:
instance.specialities.set(specialities_data) instance.specialities.set(specialities_data)
@ -64,17 +66,18 @@ class TeacherSerializer(serializers.ModelSerializer):
utc_time = timezone.localtime(obj.updated_date) # Convert to local time utc_time = timezone.localtime(obj.updated_date) # Convert to local time
local_tz = pytz.timezone(settings.TZ_APPLI) local_tz = pytz.timezone(settings.TZ_APPLI)
local_time = utc_time.astimezone(local_tz) local_time = utc_time.astimezone(local_tz)
return local_time.strftime("%d-%m-%Y %H:%M") return local_time.strftime("%d-%m-%Y %H:%M")
def get_droit(self, obj): def get_role_type(self, obj):
if obj.associated_profile: profile_role = obj.profile_role
return obj.associated_profile.droit return {'role_type': profile_role.role_type, 'establishment': profile_role.establishment.name}
return None
def get_specialities_details(self, obj): def get_specialities_details(self, obj):
return [{'id': speciality.id, 'name': speciality.name, 'color_code': speciality.color_code} for speciality in obj.specialities.all()] return [{'id': speciality.id, 'name': speciality.name, 'color_code': speciality.color_code} for speciality in obj.specialities.all()]
def get_associated_profile_email(self, obj):
return obj.profile_role.profile.email
class PlanningSerializer(serializers.ModelSerializer): class PlanningSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Planning model = Planning
@ -207,9 +210,4 @@ class PaymentPlanSerializer(serializers.ModelSerializer):
class PaymentModeSerializer(serializers.ModelSerializer): class PaymentModeSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = PaymentMode model = PaymentMode
fields = '__all__'
class EstablishmentSerializer(serializers.ModelSerializer):
class Meta:
model = Establishment
fields = '__all__' fields = '__all__'

View File

@ -16,9 +16,7 @@ from .views import (
PaymentPlanListCreateView, PaymentPlanListCreateView,
PaymentPlanDetailView, PaymentPlanDetailView,
PaymentModeListCreateView, PaymentModeListCreateView,
PaymentModeDetailView, PaymentModeDetailView
EstablishmentListCreateView,
EstablishmentDetailView
) )
urlpatterns = [ urlpatterns = [
@ -44,8 +42,5 @@ urlpatterns = [
re_path(r'^paymentPlans/(?P<id>[0-9]+)$', PaymentPlanDetailView.as_view(), name="payment_plan_detail"), re_path(r'^paymentPlans/(?P<id>[0-9]+)$', PaymentPlanDetailView.as_view(), name="payment_plan_detail"),
re_path(r'^paymentModes$', PaymentModeListCreateView.as_view(), name="payment_mode_list_create"), re_path(r'^paymentModes$', PaymentModeListCreateView.as_view(), name="payment_mode_list_create"),
re_path(r'^paymentModes/(?P<id>[0-9]+)$', PaymentModeDetailView.as_view(), name="payment_mode_detail"), re_path(r'^paymentModes/(?P<id>[0-9]+)$', PaymentModeDetailView.as_view(), name="payment_mode_detail")
re_path(r'^establishments$', EstablishmentListCreateView.as_view(), name='establishment_list_create'),
re_path(r'^establishments/(?P<id>[0-9]+)$', EstablishmentDetailView.as_view(), name="establishment_detail"),
] ]

View File

@ -12,8 +12,7 @@ from .models import (
Discount, Discount,
Fee, Fee,
PaymentPlan, PaymentPlan,
PaymentMode, PaymentMode
Establishment
) )
from .serializers import ( from .serializers import (
TeacherSerializer, TeacherSerializer,
@ -23,8 +22,7 @@ from .serializers import (
DiscountSerializer, DiscountSerializer,
FeeSerializer, FeeSerializer,
PaymentPlanSerializer, PaymentPlanSerializer,
PaymentModeSerializer, PaymentModeSerializer
EstablishmentSerializer
) )
from N3wtSchool.bdd import delete_object, getAllObjects, getObject from N3wtSchool.bdd import delete_object, getAllObjects, getObject
@ -32,8 +30,14 @@ from N3wtSchool.bdd import delete_object, getAllObjects, getObject
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
class SpecialityListCreateView(APIView): class SpecialityListCreateView(APIView):
def get(self, request): def get(self, request):
specialitiesList = getAllObjects(Speciality) establishment_id = request.GET.get('establishment_id', None)
specialities_serializer = SpecialitySerializer(specialitiesList, many=True) if establishment_id is None:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
specialities_list = getAllObjects(Speciality)
if establishment_id:
specialities_list = specialities_list.filter(establishment__id=establishment_id).distinct()
specialities_serializer = SpecialitySerializer(specialities_list, many=True)
return JsonResponse(specialities_serializer.data, safe=False) return JsonResponse(specialities_serializer.data, safe=False)
def post(self, request): def post(self, request):
@ -51,7 +55,7 @@ class SpecialityListCreateView(APIView):
class SpecialityDetailView(APIView): class SpecialityDetailView(APIView):
def get(self, request, id): def get(self, request, id):
speciality = getObject(_objectName=Speciality, _columnName='id', _value=id) speciality = getObject(_objectName=Speciality, _columnName='id', _value=id)
speciality_serializer = SpecialitySerializer(speciality) speciality_serializer=SpecialitySerializer(speciality)
return JsonResponse(speciality_serializer.data, safe=False) return JsonResponse(speciality_serializer.data, safe=False)
def put(self, request, id): def put(self, request, id):
@ -71,9 +75,14 @@ class SpecialityDetailView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
class TeacherListCreateView(APIView): class TeacherListCreateView(APIView):
def get(self, request): def get(self, request):
teachersList=getAllObjects(Teacher) establishment_id = request.GET.get('establishment_id', None)
teachers_serializer=TeacherSerializer(teachersList, many=True) if establishment_id is None:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
teachers_list = getAllObjects(Teacher)
if teachers_list:
teachers_list = teachers_list.filter(profile_role__establishment_id=establishment_id)
teachers_serializer = TeacherSerializer(teachers_list, many=True)
return JsonResponse(teachers_serializer.data, safe=False) return JsonResponse(teachers_serializer.data, safe=False)
def post(self, request): def post(self, request):
@ -107,14 +116,20 @@ class TeacherDetailView(APIView):
return JsonResponse(teacher_serializer.errors, safe=False) return JsonResponse(teacher_serializer.errors, safe=False)
def delete(self, request, id): def delete(self, request, id):
return delete_object(Teacher, id, related_field='associated_profile') return delete_object(Teacher, id, related_field='profile')
@method_decorator(csrf_protect, name='dispatch') @method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
class SchoolClassListCreateView(APIView): class SchoolClassListCreateView(APIView):
def get(self, request): def get(self, request):
classesList=getAllObjects(SchoolClass) establishment_id = request.GET.get('establishment_id', None)
classes_serializer=SchoolClassSerializer(classesList, many=True) if establishment_id is None:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
school_classes_list = getAllObjects(SchoolClass)
if school_classes_list:
school_classes_list = school_classes_list.filter(establishment=establishment_id).distinct()
classes_serializer = SchoolClassSerializer(school_classes_list, many=True)
return JsonResponse(classes_serializer.data, safe=False) return JsonResponse(classes_serializer.data, safe=False)
def post(self, request): def post(self, request):
@ -201,9 +216,14 @@ class PlanningDetailView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
class FeeListCreateView(APIView): class FeeListCreateView(APIView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
establishment_id = request.GET.get('establishment_id', None)
if establishment_id is None:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
filter = request.GET.get('filter', '').strip() filter = request.GET.get('filter', '').strip()
fee_type_value = 0 if filter == 'registration' else 1 fee_type_value = 0 if filter == 'registration' else 1
fees = Fee.objects.filter(type=fee_type_value)
fees = Fee.objects.filter(type=fee_type_value, establishment_id=establishment_id).distinct()
fee_serializer = FeeSerializer(fees, many=True) fee_serializer = FeeSerializer(fees, many=True)
return JsonResponse(fee_serializer.data, safe=False, status=status.HTTP_200_OK) return JsonResponse(fee_serializer.data, safe=False, status=status.HTTP_200_OK)
@ -244,11 +264,16 @@ class FeeDetailView(APIView):
@method_decorator(csrf_protect, name='dispatch') @method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
class DiscountListCreateView(APIView): class DiscountListCreateView(APIView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
establishment_id = request.GET.get('establishment_id', None)
if establishment_id is None:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
filter = request.GET.get('filter', '').strip() filter = request.GET.get('filter', '').strip()
discount_type_value = 0 if filter == 'registration' else 1 discount_type_value = 0 if filter == 'registration' else 1
discounts = Discount.objects.filter(type=discount_type_value)
discounts = Discount.objects.filter(type=discount_type_value, establishment_id=establishment_id).distinct()
discounts_serializer = DiscountSerializer(discounts, many=True) discounts_serializer = DiscountSerializer(discounts, many=True)
return JsonResponse(discounts_serializer.data, safe=False, status=status.HTTP_200_OK) return JsonResponse(discounts_serializer.data, safe=False, status=status.HTTP_200_OK)
@ -291,10 +316,15 @@ class DiscountDetailView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
class PaymentPlanListCreateView(APIView): class PaymentPlanListCreateView(APIView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
establishment_id = request.GET.get('establishment_id', None)
if establishment_id is None:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
filter = request.GET.get('filter', '').strip() filter = request.GET.get('filter', '').strip()
type_value = 0 if filter == 'registration' else 1 type_value = 0 if filter == 'registration' else 1
paymentPlans = PaymentPlan.objects.filter(type=type_value)
payment_plans_serializer = PaymentPlanSerializer(paymentPlans, many=True) payment_plans = PaymentPlan.objects.filter(type=type_value, establishment_id=establishment_id).distinct()
payment_plans_serializer = PaymentPlanSerializer(payment_plans, many=True)
return JsonResponse(payment_plans_serializer.data, safe=False, status=status.HTTP_200_OK) return JsonResponse(payment_plans_serializer.data, safe=False, status=status.HTTP_200_OK)
@ -336,10 +366,15 @@ class PaymentPlanDetailView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch')
class PaymentModeListCreateView(APIView): class PaymentModeListCreateView(APIView):
def get(self, request): def get(self, request):
establishment_id = request.GET.get('establishment_id', None)
if establishment_id is None:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
filter = request.GET.get('filter', '').strip() filter = request.GET.get('filter', '').strip()
type_value = 0 if filter == 'registration' else 1 type_value = 0 if filter == 'registration' else 1
paymentModes = PaymentMode.objects.filter(type=type_value)
payment_modes_serializer = PaymentModeSerializer(paymentModes, many=True) payment_modes = PaymentMode.objects.filter(type=type_value, establishment_id=establishment_id).distinct()
payment_modes_serializer = PaymentModeSerializer(payment_modes, many=True)
return JsonResponse(payment_modes_serializer.data, safe=False, status=status.HTTP_200_OK) return JsonResponse(payment_modes_serializer.data, safe=False, status=status.HTTP_200_OK)
@ -376,45 +411,3 @@ class PaymentModeDetailView(APIView):
def delete(self, request, id): def delete(self, request, id):
return delete_object(PaymentMode, id) return delete_object(PaymentMode, id)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class EstablishmentListCreateView(APIView):
def get(self, request):
establishments=getAllObjects(Establishment)
establishments_serializer=EstablishmentSerializer(establishments, many=True)
return JsonResponse(establishments_serializer.data, safe=False, status=status.HTTP_200_OK)
def post(self, request):
establishment_data = JSONParser().parse(request)
establishment_serializer = EstablishmentSerializer(data=establishment_data)
if establishment_serializer.is_valid():
establishment_serializer.save()
return JsonResponse(establishment_serializer.data, safe=False, status=status.HTTP_201_CREATED)
return JsonResponse(establishment_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class EstablishmentDetailView(APIView):
def get(self, request, id=None):
try:
establishment = Establishment.objects.get(id=id)
establishment_serializer = EstablishmentSerializer(establishment)
return JsonResponse(establishment_serializer.data, safe=False)
except Establishment.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
def put(self, request, id):
establishment_data = JSONParser().parse(request)
try:
establishment = Establishment.objects.get(id=id)
except Establishment.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
establishment_serializer = EstablishmentSerializer(establishment, data=establishment_data, partial=True)
if establishment_serializer.is_valid():
establishment_serializer.save()
return JsonResponse(establishment_serializer.data, safe=False)
return JsonResponse(establishment_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
return delete_object(Establishment, id)

View File

@ -4,6 +4,8 @@ from django.conf import settings
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from School.models import SchoolClass, Fee, Discount from School.models import SchoolClass, Fee, Discount
from Auth.models import ProfileRole
from Establishment.models import Establishment
from datetime import datetime from datetime import datetime
@ -25,10 +27,9 @@ class Guardian(models.Model):
first_name = models.CharField(max_length=200, default="") first_name = models.CharField(max_length=200, default="")
birth_date = models.CharField(max_length=200, default="", blank=True) birth_date = models.CharField(max_length=200, default="", blank=True)
address = models.CharField(max_length=200, default="", blank=True) address = models.CharField(max_length=200, default="", blank=True)
email = models.CharField(max_length=200, default="", blank=True)
phone = models.CharField(max_length=200, default="", blank=True) phone = models.CharField(max_length=200, default="", blank=True)
profession = models.CharField(max_length=200, default="", blank=True) profession = models.CharField(max_length=200, default="", blank=True)
associated_profile = models.ForeignKey('Auth.Profile', on_delete=models.CASCADE) profile_role = models.OneToOneField(ProfileRole, on_delete=models.CASCADE, related_name='guardian_profile', null=True, blank=True)
def __str__(self): def __str__(self):
return self.last_name + "_" + self.first_name return self.last_name + "_" + self.first_name
@ -163,6 +164,7 @@ class Student(models.Model):
class RegistrationFileGroup(models.Model): class RegistrationFileGroup(models.Model):
name = models.CharField(max_length=255, default="") name = models.CharField(max_length=255, default="")
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='file_group', null=True, blank=True)
def __str__(self): def __str__(self):
return self.name return self.name
@ -214,7 +216,7 @@ class RegistrationForm(models.Model):
null=True, null=True,
blank=True) blank=True)
establishment = models.ForeignKey('School.Establishment', on_delete=models.CASCADE, related_name='register_forms') establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='register_forms')
def __str__(self): def __str__(self):
return "RF_" + self.student.last_name + "_" + self.student.first_name return "RF_" + self.student.last_name + "_" + self.student.first_name

View File

@ -2,7 +2,7 @@ from rest_framework import serializers
from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationTemplateMaster, RegistrationTemplate from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationTemplateMaster, RegistrationTemplate
from School.models import SchoolClass, Fee, Discount, FeeType from School.models import SchoolClass, Fee, Discount, FeeType
from School.serializers import FeeSerializer, DiscountSerializer from School.serializers import FeeSerializer, DiscountSerializer
from Auth.models import Profile from Auth.models import ProfileRole
from Auth.serializers import ProfileSerializer from Auth.serializers import ProfileSerializer
from GestionMessagerie.models import Messagerie from GestionMessagerie.models import Messagerie
from GestionNotification.models import Notification from GestionNotification.models import Notification
@ -68,7 +68,7 @@ class SiblingSerializer(serializers.ModelSerializer):
class GuardianSerializer(serializers.ModelSerializer): class GuardianSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False) id = serializers.IntegerField(required=False)
associated_profile = serializers.PrimaryKeyRelatedField(queryset=Profile.objects.all(), required=True) profile_role = serializers.PrimaryKeyRelatedField(queryset=ProfileRole.objects.all(), required=True)
associated_profile_email = serializers.SerializerMethodField() associated_profile_email = serializers.SerializerMethodField()
class Meta: class Meta:
@ -76,7 +76,7 @@ class GuardianSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
def get_associated_profile_email(self, obj): def get_associated_profile_email(self, obj):
return obj.associated_profile.email return obj.profile_role.profile.email
class StudentSerializer(serializers.ModelSerializer): class StudentSerializer(serializers.ModelSerializer):
@ -248,7 +248,7 @@ class GuardianByDICreationSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Guardian model = Guardian
fields = ['id', 'last_name', 'first_name', 'email', 'associated_profile'] fields = ['id', 'last_name', 'first_name', 'email', 'profile']
class StudentByRFCreationSerializer(serializers.ModelSerializer): class StudentByRFCreationSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False) id = serializers.IntegerField(required=False)

View File

@ -90,6 +90,12 @@ class ChildrenListView(APIView):
# Récupération des élèves d'un parent # Récupération des élèves d'un parent
# idProfile : identifiant du profil connecté rattaché aux fiches d'élèves # idProfile : identifiant du profil connecté rattaché aux fiches d'élèves
def get(self, request, id): def get(self, request, id):
students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__associated_profile__id', _value=id) establishment_id = request.GET.get('establishment_id', None)
if establishment_id is None:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__profile_role__profile__id', _value=id)
if students:
students = students.filter(establishment=establishment_id).distinct()
students_serializer = RegistrationFormByParentSerializer(students, many=True) students_serializer = RegistrationFormByParentSerializer(students, many=True)
return JsonResponse(students_serializer.data, safe=False) return JsonResponse(students_serializer.data, safe=False)

View File

@ -15,6 +15,7 @@ test_mode = os.getenv('TEST_MODE', 'False') == 'True'
commands = [ commands = [
["python", "manage.py", "collectstatic", "--noinput"], ["python", "manage.py", "collectstatic", "--noinput"],
["python", "manage.py", "flush", "--noinput"], ["python", "manage.py", "flush", "--noinput"],
["python", "manage.py", "makemigrations", "Establishment", "--noinput"],
["python", "manage.py", "makemigrations", "Subscriptions", "--noinput"], ["python", "manage.py", "makemigrations", "Subscriptions", "--noinput"],
["python", "manage.py", "makemigrations", "Planning", "--noinput"], ["python", "manage.py", "makemigrations", "Planning", "--noinput"],
["python", "manage.py", "makemigrations", "GestionNotification", "--noinput"], ["python", "manage.py", "makemigrations", "GestionNotification", "--noinput"],

View File

@ -3,7 +3,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import Sidebar from '@/components/Sidebar'; import Sidebar from '@/components/Sidebar';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import {useTranslations} from 'next-intl'; import { useTranslations } from 'next-intl';
import Image from 'next/image'; import Image from 'next/image';
import { import {
Users, Users,
@ -35,6 +35,9 @@ import ProtectedRoute from '@/components/ProtectedRoute';
import { getGravatarUrl } from '@/utils/gravatar'; import { getGravatarUrl } from '@/utils/gravatar';
import Footer from '@/components/Footer'; import Footer from '@/components/Footer';
import { getRightStr, RIGHTS } from '@/utils/rights'; import { getRightStr, RIGHTS } from '@/utils/rights';
import { getSession } from 'next-auth/react';
import logger from '@/utils/logger';
import { useEstablishment } from '@/context/EstablishmentContext';
export default function Layout({ export default function Layout({
children, children,
@ -51,11 +54,12 @@ export default function Layout({
"settings": { "id": "settings", "name": t('settings'), "url": FE_ADMIN_SETTINGS_URL, "icon": Settings } "settings": { "id": "settings", "name": t('settings'), "url": FE_ADMIN_SETTINGS_URL, "icon": Settings }
}; };
const [establishment, setEstablishment] = useState(null); const [establishments, setEstablishments] = useState([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isPopupVisible, setIsPopupVisible] = useState(false); const [isPopupVisible, setIsPopupVisible] = useState(false);
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
const { data: session } = useSession(); const { data: session } = useSession();
const { selectedEstablishmentId, setSelectedEstablishmentId, profileRole, setProfileRole } = useEstablishment();
const pathname = usePathname(); const pathname = usePathname();
const currentPage = pathname.split('/').pop(); const currentPage = pathname.split('/').pop();
@ -80,7 +84,7 @@ export default function Layout({
content: ( content: (
<div className="px-4 py-2"> <div className="px-4 py-2">
<div className="font-medium">{user?.email || 'Utilisateur'}</div> <div className="font-medium">{user?.email || 'Utilisateur'}</div>
<div className="text-xs text-gray-400">{getRightStr(user?.role) || ''}</div> <div className="text-xs text-gray-400">{getRightStr(profileRole) || ''}</div>
</div> </div>
) )
}, },
@ -106,17 +110,34 @@ export default function Layout({
}, [pathname]); }, [pathname]);
useEffect(() => { useEffect(() => {
setIsLoading(true); getSession()
fetchEstablishment() .then(session => {
.then(data => { if (session && session.user) {
setEstablishment(data); setUser(session.user);
setEstablishments(session.user.roles.map(role => ({
id: role.establishment__id,
name: role.establishment__name,
role_type: role.role_type
})));
// Sélectionner l'établissement depuis le localStorage ou le premier établissement par défaut
const storedEstablishmentId = localStorage.getItem('selectedEstablishmentId');
if (storedEstablishmentId) {
setSelectedEstablishmentId(storedEstablishmentId);
const storedProfileRole = session.user.roles.find(role => role.establishment__id === parseInt(storedEstablishmentId))?.role_type;
setProfileRole(storedProfileRole);
} else if (session.user.roles.length > 0) {
setSelectedEstablishmentId(session.user.roles[0].establishment__id);
setProfileRole(session.user.roles[0].role_type);
}
}
}) })
.catch(error => console.error('Error fetching establishment : ', error)) .catch(err => {
.finally(() => setIsLoading(false)); logger.error('Error fetching session:', err);
});
}, []); }, []);
useEffect(() => { useEffect(() => {
const fetchUser = async () => { const fetchUser = async () => {
if (session) { // Vérifier que la session existe if (session) { // Vérifier que la session existe
const userData = await getUser(); const userData = await getUser();
setUser(userData); setUser(userData);
@ -126,8 +147,6 @@ export default function Layout({
fetchUser(); fetchUser();
}, [session]); }, [session]);
return ( return (
<ProtectedRoute requiredRight={RIGHTS.ADMIN}> <ProtectedRoute requiredRight={RIGHTS.ADMIN}>
{!isLoading && ( {!isLoading && (
@ -138,12 +157,16 @@ export default function Layout({
style={{ height: '100vh' }} // Force la hauteur à 100% de la hauteur de la vue style={{ height: '100vh' }} // Force la hauteur à 100% de la hauteur de la vue
> >
<Sidebar <Sidebar
establishment={establishment} establishments={establishments}
currentPage={currentPage} currentPage={currentPage}
items={Object.values(sidebarItems)} items={Object.values(sidebarItems)}
onCloseMobile={toggleSidebar} onCloseMobile={toggleSidebar}
onEstablishmentChange={(establishmentId) => {
const parsedEstablishmentId = parseInt(establishmentId, 10);
setSelectedEstablishmentId(parsedEstablishmentId);
const role = session.user.roles.find(role => role.establishment__id === parsedEstablishmentId)?.role_type;
setProfileRole(role);
}}
/> />
</div> </div>

View File

@ -39,47 +39,61 @@ export default function DashboardPage() {
const [classes, setClasses] = useState([]); const [classes, setClasses] = useState([]);
const [establishmentId, setEstablishmentId] = useState(null);
useEffect(() => { useEffect(() => {
// Fetch data for classes getSession()
fetchClasses().then(data => { .then(session => {
setClasses(data); if (session && session.user) {
logger.info('Classes fetched:', data); setEstablishmentId(session.user.establishment);
const nbMaxStudents = data.reduce((acc, classe) => acc + classe.number_of_students, 0); }
const nbStudents = data.reduce((acc, classe) => acc + classe.students.length, 0);
setStructureCapacity(nbMaxStudents);
setTotalStudents(nbStudents);
}) })
.catch(error => { .catch(err => {
logger.error('Error fetching classes:', error); logger.error('Error fetching session:', err);
}); });
}, []);
fetchRegisterForms().then(data => { useEffect(() => {
logger.info('Pending registrations fetched:', data); if (establishmentId) {
setPendingRegistration(data.count); // Fetch data for classes
}) fetchClasses(establishmentId).then(data => {
.catch(error => { setClasses(data);
logger.error('Error fetching pending registrations:', error); logger.info('Classes fetched:', data);
}); const nbMaxStudents = data.reduce((acc, classe) => acc + classe.number_of_students, 0);
const nbStudents = data.reduce((acc, classe) => acc + classe.students.length, 0);
setStructureCapacity(nbMaxStudents);
setTotalStudents(nbStudents);
fetchUpcomingEvents().then(data => { })
setUpcomingEvents(data); .catch(error => {
}).catch(error => { logger.error('Error fetching classes:', error);
logger.error('Error fetching upcoming events:', error); });
});
// Simulation de chargement des données fetchRegisterForms().then(data => {
setTimeout(() => { logger.info('Pending registrations fetched:', data);
setMonthlyStats({ setPendingRegistration(data.count);
inscriptions: [150, 180, 210, 245], })
completionRate: 78 .catch(error => {
}); logger.error('Error fetching pending registrations:', error);
setIsLoading(false); });
}, 1000);
} fetchUpcomingEvents().then(data => {
, []); setUpcomingEvents(data);
}).catch(error => {
logger.error('Error fetching upcoming events:', error);
});
// Simulation de chargement des données
setTimeout(() => {
setMonthlyStats({
inscriptions: [150, 180, 210, 245],
completionRate: 78
});
setIsLoading(false);
}, 1000);
}
}
, [establishmentId]);
if (isLoading) return <Loader />; if (isLoading) return <Loader />;

View File

@ -27,13 +27,13 @@ import {
fetchRegistrationTemplateMaster fetchRegistrationTemplateMaster
} from "@/app/actions/registerFileGroupAction"; } from "@/app/actions/registerFileGroupAction";
import logger from '@/utils/logger'; import logger from '@/utils/logger';
import { useEstablishment } from '@/context/EstablishmentContext';
export default function Page() { export default function Page() {
const [specialities, setSpecialities] = useState([]); const [specialities, setSpecialities] = useState([]);
const [classes, setClasses] = useState([]); const [classes, setClasses] = useState([]);
const [teachers, setTeachers] = useState([]); const [teachers, setTeachers] = useState([]);
const [schedules, setSchedules] = useState([]); // Add this line const [schedules, setSchedules] = useState([]);
const [registrationDiscounts, setRegistrationDiscounts] = useState([]); const [registrationDiscounts, setRegistrationDiscounts] = useState([]);
const [tuitionDiscounts, setTuitionDiscounts] = useState([]); const [tuitionDiscounts, setTuitionDiscounts] = useState([]);
const [registrationFees, setRegistrationFees] = useState([]); const [registrationFees, setRegistrationFees] = useState([]);
@ -45,54 +45,57 @@ export default function Page() {
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]); const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
const csrfToken = useCsrfToken(); const csrfToken = useCsrfToken();
const { selectedEstablishmentId } = useEstablishment();
useEffect(() => { useEffect(() => {
// Fetch data for specialities if (selectedEstablishmentId) {
handleSpecialities(); // Fetch data for specialities
handleSpecialities();
// Fetch data for teachers // Fetch data for teachers
handleTeachers(); handleTeachers();
// Fetch data for classes // Fetch data for classes
handleClasses(); handleClasses();
// Fetch data for schedules // Fetch data for schedules
handleSchedules(); handleSchedules();
// Fetch data for registration discounts // Fetch data for registration discounts
handleRegistrationDiscounts(); handleRegistrationDiscounts();
// Fetch data for tuition discounts // Fetch data for tuition discounts
handleTuitionDiscounts(); handleTuitionDiscounts();
// Fetch data for registration fees // Fetch data for registration fees
handleRegistrationFees(); handleRegistrationFees();
// Fetch data for tuition fees // Fetch data for tuition fees
handleTuitionFees(); handleTuitionFees();
// Fetch data for registration file templates // Fetch data for registration file templates
fetchRegistrationTemplateMaster() fetchRegistrationTemplateMaster()
.then((data)=> { .then((data)=> {
setFichiers(data) setFichiers(data)
}) })
.catch(error => logger.error('Error fetching files:', error)); .catch(error => logger.error('Error fetching files:', error));
// Fetch data for registration payment plans // Fetch data for registration payment plans
handleRegistrationPaymentPlans(); handleRegistrationPaymentPlans();
// Fetch data for tuition payment plans // Fetch data for tuition payment plans
handleTuitionPaymentPlans(); handleTuitionPaymentPlans();
// Fetch data for registration payment modes // Fetch data for registration payment modes
handleRegistrationPaymentModes(); handleRegistrationPaymentModes();
// Fetch data for tuition payment modes // Fetch data for tuition payment modes
handleTuitionPaymentModes(); handleTuitionPaymentModes();
}, []); }
}, [selectedEstablishmentId]);
const handleSpecialities = () => { const handleSpecialities = () => {
fetchSpecialities() fetchSpecialities(selectedEstablishmentId)
.then(data => { .then(data => {
setSpecialities(data); setSpecialities(data);
}) })
@ -100,7 +103,7 @@ export default function Page() {
}; };
const handleTeachers = () => { const handleTeachers = () => {
fetchTeachers() fetchTeachers(selectedEstablishmentId)
.then(data => { .then(data => {
setTeachers(data); setTeachers(data);
}) })
@ -108,7 +111,7 @@ export default function Page() {
}; };
const handleClasses = () => { const handleClasses = () => {
fetchClasses() fetchClasses(selectedEstablishmentId)
.then(data => { .then(data => {
setClasses(data); setClasses(data);
}) })
@ -124,7 +127,7 @@ export default function Page() {
}; };
const handleRegistrationDiscounts = () => { const handleRegistrationDiscounts = () => {
fetchRegistrationDiscounts() fetchRegistrationDiscounts(selectedEstablishmentId)
.then(data => { .then(data => {
setRegistrationDiscounts(data); setRegistrationDiscounts(data);
}) })
@ -132,7 +135,7 @@ export default function Page() {
}; };
const handleTuitionDiscounts = () => { const handleTuitionDiscounts = () => {
fetchTuitionDiscounts() fetchTuitionDiscounts(selectedEstablishmentId)
.then(data => { .then(data => {
setTuitionDiscounts(data); setTuitionDiscounts(data);
}) })
@ -140,7 +143,7 @@ export default function Page() {
}; };
const handleRegistrationFees = () => { const handleRegistrationFees = () => {
fetchRegistrationFees() fetchRegistrationFees(selectedEstablishmentId)
.then(data => { .then(data => {
setRegistrationFees(data); setRegistrationFees(data);
}) })
@ -148,7 +151,7 @@ export default function Page() {
}; };
const handleTuitionFees = () => { const handleTuitionFees = () => {
fetchTuitionFees() fetchTuitionFees(selectedEstablishmentId)
.then(data => { .then(data => {
setTuitionFees(data); setTuitionFees(data);
}) })
@ -156,7 +159,7 @@ export default function Page() {
}; };
const handleRegistrationPaymentPlans = () => { const handleRegistrationPaymentPlans = () => {
fetchRegistrationPaymentPlans() fetchRegistrationPaymentPlans(selectedEstablishmentId)
.then(data => { .then(data => {
setRegistrationPaymentPlans(data); setRegistrationPaymentPlans(data);
}) })
@ -164,7 +167,7 @@ export default function Page() {
}; };
const handleTuitionPaymentPlans = () => { const handleTuitionPaymentPlans = () => {
fetchTuitionPaymentPlans() fetchTuitionPaymentPlans(selectedEstablishmentId)
.then(data => { .then(data => {
setTuitionPaymentPlans(data); setTuitionPaymentPlans(data);
}) })
@ -172,7 +175,7 @@ export default function Page() {
}; };
const handleRegistrationPaymentModes = () => { const handleRegistrationPaymentModes = () => {
fetchRegistrationPaymentModes() fetchRegistrationPaymentModes(selectedEstablishmentId)
.then(data => { .then(data => {
setRegistrationPaymentModes(data); setRegistrationPaymentModes(data);
}) })
@ -180,7 +183,7 @@ export default function Page() {
}; };
const handleTuitionPaymentModes = () => { const handleTuitionPaymentModes = () => {
fetchTuitionPaymentModes() fetchTuitionPaymentModes(selectedEstablishmentId)
.then(data => { .then(data => {
setTuitionPaymentModes(data); setTuitionPaymentModes(data);
}) })

View File

@ -16,6 +16,7 @@ import Modal from '@/components/Modal';
import InscriptionForm from '@/components/Inscription/InscriptionForm' import InscriptionForm from '@/components/Inscription/InscriptionForm'
import AffectationClasseForm from '@/components/AffectationClasseForm' import AffectationClasseForm from '@/components/AffectationClasseForm'
import { getSession } from 'next-auth/react'; import { getSession } from 'next-auth/react';
import { useEstablishment } from '@/context/EstablishmentContext';
import { import {
PENDING, PENDING,
@ -50,7 +51,6 @@ import {
import DjangoCSRFToken from '@/components/DjangoCSRFToken' import DjangoCSRFToken from '@/components/DjangoCSRFToken'
import { useCsrfToken } from '@/context/CsrfContext'; import { useCsrfToken } from '@/context/CsrfContext';
import { ESTABLISHMENT_ID } from '@/utils/Url';
import logger from '@/utils/logger'; import logger from '@/utils/logger';
export default function Page({ params: { locale } }) { export default function Page({ params: { locale } }) {
@ -87,9 +87,8 @@ export default function Page({ params: { locale } }) {
const [tuitionFees, setTuitionFees] = useState([]); const [tuitionFees, setTuitionFees] = useState([]);
const [groups, setGroups] = useState([]); const [groups, setGroups] = useState([]);
const [establishmentId, setEstablishmentId] = useState(null);
const csrfToken = useCsrfToken(); const csrfToken = useCsrfToken();
const { selectedEstablishmentId } = useEstablishment();
const openModal = () => { const openModal = () => {
setIsOpen(true); setIsOpen(true);
@ -167,23 +166,11 @@ const registerFormArchivedDataHandler = (data) => {
} }
useEffect(() => { useEffect(() => {
getSession() if (selectedEstablishmentId) {
.then(session => {
if (session && session.user) {
setEstablishmentId(session.user.establishment);
}
})
.catch(err => {
logger.error('Error fetching session:', err);
});
}, []);
useEffect(() => {
if (establishmentId) {
const fetchInitialData = () => { const fetchInitialData = () => {
Promise.all([ Promise.all([
fetchClasses(), fetchClasses(selectedEstablishmentId),
fetchStudents(establishmentId) // Utiliser l'ID de l'établissement ici fetchStudents(selectedEstablishmentId) // Utiliser l'ID de l'établissement ici
]) ])
.then(([classesData, studentsData]) => { .then(([classesData, studentsData]) => {
setClasses(classesData); setClasses(classesData);
@ -198,21 +185,21 @@ useEffect(() => {
fetchInitialData(); fetchInitialData();
} }
}, [establishmentId]); }, [selectedEstablishmentId]);
useEffect(() => { useEffect(() => {
if (establishmentId) { if (selectedEstablishmentId) {
const fetchDataAndSetState = () => { const fetchDataAndSetState = () => {
setIsLoading(true); setIsLoading(true);
Promise.all([ Promise.all([
fetchRegisterForms(establishmentId, PENDING, currentPage, itemsPerPage, searchTerm) fetchRegisterForms(selectedEstablishmentId, PENDING, currentPage, itemsPerPage, searchTerm)
.then(registerFormPendingDataHandler) .then(registerFormPendingDataHandler)
.catch(requestErrorHandler), .catch(requestErrorHandler),
fetchRegisterForms(establishmentId, SUBSCRIBED) fetchRegisterForms(selectedEstablishmentId, SUBSCRIBED)
.then(registerFormSubscribedDataHandler) .then(registerFormSubscribedDataHandler)
.catch(requestErrorHandler), .catch(requestErrorHandler),
fetchRegisterForms(establishmentId, ARCHIVED) fetchRegisterForms(selectedEstablishmentId, ARCHIVED)
.then(registerFormArchivedDataHandler) .then(registerFormArchivedDataHandler)
.catch(requestErrorHandler), .catch(requestErrorHandler),
fetchRegistrationTemplateMaster() fetchRegistrationTemplateMaster()
@ -222,27 +209,27 @@ useEffect(() => {
.catch(err => { .catch(err => {
logger.debug(err.message); logger.debug(err.message);
}), }),
fetchRegistrationDiscounts() fetchRegistrationDiscounts(selectedEstablishmentId)
.then(data => { .then(data => {
setRegistrationDiscounts(data); setRegistrationDiscounts(data);
}) })
.catch(requestErrorHandler), .catch(requestErrorHandler),
fetchTuitionDiscounts() fetchTuitionDiscounts(selectedEstablishmentId)
.then(data => { .then(data => {
setTuitionDiscounts(data); setTuitionDiscounts(data);
}) })
.catch(requestErrorHandler), .catch(requestErrorHandler),
fetchRegistrationFees() fetchRegistrationFees(selectedEstablishmentId)
.then(data => { .then(data => {
setRegistrationFees(data); setRegistrationFees(data);
}) })
.catch(requestErrorHandler), .catch(requestErrorHandler),
fetchTuitionFees() fetchTuitionFees(selectedEstablishmentId)
.then(data => { .then(data => {
setTuitionFees(data); setTuitionFees(data);
}) })
.catch(requestErrorHandler), .catch(requestErrorHandler),
fetchRegistrationFileGroups() fetchRegistrationFileGroups(selectedEstablishmentId)
.then(data => { .then(data => {
setGroups(data); setGroups(data);
}) })
@ -263,20 +250,20 @@ useEffect(() => {
fetchDataAndSetState(); fetchDataAndSetState();
} }
}, [establishmentId, reloadFetch, currentPage, searchTerm]); }, [selectedEstablishmentId, reloadFetch, currentPage, searchTerm]);
useEffect(() => { useEffect(() => {
if (establishmentId) { if (selectedEstablishmentId) {
const fetchDataAndSetState = () => { const fetchDataAndSetState = () => {
setIsLoading(true); setIsLoading(true);
fetchRegisterForms(establishmentId, PENDING, currentPage, itemsPerPage, searchTerm) fetchRegisterForms(selectedEstablishmentId, PENDING, currentPage, itemsPerPage, searchTerm)
.then(registerFormPendingDataHandler) .then(registerFormPendingDataHandler)
.catch(requestErrorHandler) .catch(requestErrorHandler)
fetchRegisterForms(establishmentId, SUBSCRIBED) fetchRegisterForms(selectedEstablishmentId, SUBSCRIBED)
.then(registerFormSubscribedDataHandler) .then(registerFormSubscribedDataHandler)
.catch(requestErrorHandler) .catch(requestErrorHandler)
fetchRegisterForms(establishmentId, ARCHIVED) fetchRegisterForms(selectedEstablishmentId, ARCHIVED)
.then(registerFormArchivedDataHandler) .then(registerFormArchivedDataHandler)
.catch(requestErrorHandler) .catch(requestErrorHandler)
fetchRegistrationTemplateMaster() fetchRegistrationTemplateMaster()
@ -545,7 +532,7 @@ useEffect(()=>{
const columns = [ const columns = [
{ name: t('studentName'), transform: (row) => row.student.last_name }, { name: t('studentName'), transform: (row) => row.student.last_name },
{ name: t('studentFistName'), transform: (row) => row.student.first_name }, { name: t('studentFistName'), transform: (row) => row.student.first_name },
{ name: t('mainContactMail'), transform: (row) => row.student.guardians[0].email }, { name: t('mainContactMail'), transform: (row) => row.student.guardians[0].associated_profile_email },
{ name: t('phone'), transform: (row) => formatPhoneNumber(row.student.guardians[0].phone) }, { name: t('phone'), transform: (row) => formatPhoneNumber(row.student.guardians[0].phone) },
{ name: t('lastUpdateDate'), transform: (row) => row.formatted_last_update}, { name: t('lastUpdateDate'), transform: (row) => row.formatted_last_update},
{ name: t('registrationFileStatus'), transform: (row) => ( { name: t('registrationFileStatus'), transform: (row) => (

View File

@ -37,8 +37,9 @@ export default function Layout({
setIsPopupVisible(false); setIsPopupVisible(false);
disconnect(); disconnect();
}; };
useEffect(() => {
const fetchUser = async () => { useEffect(() => {
const fetchUser = async () => {
if (session) { // Vérifier que la session existe if (session) { // Vérifier que la session existe
const userData = await getUser(); const userData = await getUser();
setUser(userData); setUser(userData);
@ -47,6 +48,7 @@ export default function Layout({
fetchUser(); fetchUser();
}, [session]); }, [session]);
// useEffect(() => { // useEffect(() => {
// if (status === 'loading') return; // if (status === 'loading') return;
@ -81,7 +83,7 @@ const dropdownItems = [
content: ( content: (
<div className="px-4 py-2"> <div className="px-4 py-2">
<div className="font-medium">{user?.email || 'Utilisateur'}</div> <div className="font-medium">{user?.email || 'Utilisateur'}</div>
<div className="text-xs text-gray-400">{getRightStr(user?.role) || ''}</div> <div className="text-xs text-gray-400">{getRightStr(user?.roles[0]?.role_type) || ''}</div>
</div> </div>
) )
}, },

View File

@ -9,13 +9,15 @@ import { fetchChildren } from '@/app/actions/subscriptionAction';
import logger from '@/utils/logger'; import logger from '@/utils/logger';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { FE_USERS_LOGIN_URL } from '@/utils/Url'; import { FE_USERS_LOGIN_URL } from '@/utils/Url';
import { useEstablishment } from '@/context/EstablishmentContext';
export default function ParentHomePage() { export default function ParentHomePage() {
const [children, setChildren] = useState([]); const [children, setChildren] = useState([]);
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const [userId, setUserId] = useState(null); const [userId, setUserId] = useState(null);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [establishments, setEstablishments] = useState([]);
const { selectedEstablishmentId, setSelectedEstablishmentId } = useEstablishment();
const router = useRouter(); const router = useRouter();
@ -24,21 +26,42 @@ export default function ParentHomePage() {
if (!session) { if (!session) {
router.push(`${FE_USERS_LOGIN_URL}`); router.push(`${FE_USERS_LOGIN_URL}`);
} } else {
console.log(session); const userIdFromSession = session.user.user_id;
const userIdFromSession = session.user.user_id; setUserId(userIdFromSession);
setUserId(userIdFromSession);
fetchChildren(userIdFromSession).then(data => { const userEstablishments = session.user.roles.map(role => ({
setChildren(data); id: role.establishment__id,
}); name: role.establishment__name,
}, [userId]); role_type: role.role_type
}));
setEstablishments(userEstablishments);
const storedEstablishmentId = localStorage.getItem('selectedEstablishmentId');
if (storedEstablishmentId) {
setSelectedEstablishmentId(storedEstablishmentId);
} else if (userEstablishments.length > 0) {
setSelectedEstablishmentId(userEstablishments[0].id);
}
fetchChildren(userIdFromSession, storedEstablishmentId).then(data => {
setChildren(data);
});
}
}, [status, session, selectedEstablishmentId]);
const handleEstablishmentChange = (e) => {
const establishmentId = parseInt(e.target.value, 10);
setSelectedEstablishmentId(establishmentId);
localStorage.setItem('selectedEstablishmentId', establishmentId);
};
function handleEdit(eleveId) { function handleEdit(eleveId) {
// Logique pour éditer le dossier de l'élève // Logique pour éditer le dossier de l'élève
logger.debug(`Edit dossier for student id: ${eleveId}`); logger.debug(`Edit dossier for student id: ${eleveId}`);
router.push(`${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}`); router.push(`${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}`);
} }
const actionColumns = [ const actionColumns = [
{ name: 'Action', transform: (row) => row.action }, { name: 'Action', transform: (row) => row.action },
]; ];
@ -106,6 +129,22 @@ export default function ParentHomePage() {
<Users className="h-6 w-6 text-emerald-600" /> <Users className="h-6 w-6 text-emerald-600" />
Enfants Enfants
</h2> </h2>
{establishments.length > 1 && (
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">Sélectionnez un établissement :</label>
<select
value={selectedEstablishmentId}
onChange={handleEstablishmentChange}
className="block w-full mt-1 p-2 border border-gray-300 rounded-md"
>
{establishments.map(establishment => (
<option key={establishment.id} value={establishment.id}>
{establishment.name}
</option>
))}
</select>
</div>
)}
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<Table <Table
data={children} data={children}

View File

@ -13,18 +13,20 @@ import {
FE_PARENTS_HOME_URL FE_PARENTS_HOME_URL
} from '@/utils/Url'; } from '@/utils/Url';
import { login } from '@/app/actions/authAction'; import { login } from '@/app/actions/authAction';
import { getSession } from 'next-auth/react'; import { getSession } from 'next-auth/react';
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
import logger from '@/utils/logger'; import logger from '@/utils/logger';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import ProfileSelector from '@/components/ProfileSelector'; // Importez le composant ProfileSelector
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true'; const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
export default function Page() { export default function Page() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [errorMessage, setErrorMessage] = useState(""); const [errorMessage, setErrorMessage] = useState("");
const [userFieldError,setUserFieldError] = useState("") const [userFieldError, setUserFieldError] = useState("")
const [passwordFieldError,setPasswordFieldError] = useState("") const [passwordFieldError, setPasswordFieldError] = useState("")
const [selectedProfile, setSelectedProfile] = useState(1); // Par défaut, sélectionnez ADMIN
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -35,52 +37,54 @@ export default function Page() {
return data.errorMessage === "" return data.errorMessage === ""
} }
function handleFormLogin(formData) { function handleFormLogin(formData) {
setIsLoading(true); setIsLoading(true);
login({ login({
email: formData.get('login'), email: formData.get('login'),
password: formData.get('password'), password: formData.get('password'),
}).then(result => { role_type: selectedProfile // Utilisez le profil sélectionné
logger.debug('Sign In Result', result); }).then(result => {
setIsLoading(false); logger.debug('Sign In Result', result);
if (result.error) { setIsLoading(false);
setErrorMessage(result.error); if (result.error) {
} else { setErrorMessage(result.error);
getSession().then(session => { } else {
if (!session || !session.user) { getSession().then(session => {
throw new Error('Session not found'); if (!session || !session.user) {
} throw new Error('Session not found');
const user = session.user; }
logger.debug('User Session:', user); const user = session.user;
logger.debug('User Session:', user);
if (user.establishment_id) { const roles = user.roles.filter(role => role.role_type === selectedProfile);
localStorage.setItem('establishment_id', user.establishment_id); if (roles.length > 0) {
} // const establishment = roles[0].establishment;
// localStorage.setItem('establishment_id', establishment);
if (user.droit === 0) {
// Vue ECOLE // Redirection en fonction du rôle
} else if (user.droit === 1) { if (roles[0].role_type === 1) {
// Vue ADMIN
router.push(FE_ADMIN_SUBSCRIPTIONS_URL); router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
} else if (user.droit === 2) { } else if (roles[0].role_type === 2) {
// Vue PARENT
router.push(FE_PARENTS_HOME_URL); router.push(FE_PARENTS_HOME_URL);
} else { } else {
// Cas anormal // Cas anormal
} }
}).catch(error => { } else {
logger.error('Error during session retrieval:', error); setErrorMessage('No roles found for the specified role type.');
setIsLoading(false); }
setErrorMessage('An error occurred during session retrieval.'); }).catch(error => {
}); logger.error('Error during session retrieval:', error);
} setIsLoading(false);
}).catch(error => { setErrorMessage('An error occurred during session retrieval.');
logger.error('Error during sign in:', error); });
setIsLoading(false); }
setErrorMessage('An error occurred during sign in.'); }).catch(error => {
}); logger.error('Error during sign in:', error);
} setIsLoading(false);
setErrorMessage('An error occurred during sign in.');
});
}
if (isLoading === true) { if (isLoading === true) {
return <Loader /> // Affichez le composant Loader return <Loader /> // Affichez le composant Loader
@ -94,7 +98,8 @@ export default function Page() {
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); handleFormLogin(new FormData(e.target)); }}> <form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); handleFormLogin(new FormData(e.target)); }}>
<DjangoCSRFToken csrfToken={csrfToken} /> <DjangoCSRFToken csrfToken={csrfToken} />
<InputTextIcon name="login" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full mb-5" /> <InputTextIcon name="login" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full mb-5" />
<InputTextIcon name="password" type="password" IconItem={KeySquare} label="Mot de passe" placeholder="Mot de passe" errorMsg={passwordFieldError} className="w-full" /> <InputTextIcon name="password" type="password" IconItem={KeySquare} label="Mot de passe" placeholder="Mot de passe" errorMsg={passwordFieldError} className="w-full mb-5" />
<ProfileSelector selectedProfile={selectedProfile} setSelectedProfile={setSelectedProfile} />
<div className="input-group mb-4"> <div className="input-group mb-4">
</div> </div>
<label className="text-red-500">{errorMessage}</label> <label className="text-red-500">{errorMessage}</label>

View File

@ -30,6 +30,7 @@ export const login = (data) => {
redirect: false, redirect: false,
email: data.email, email: data.email,
password: data.password, password: data.password,
role_type: data.role_type,
}) })
}; };
@ -194,7 +195,11 @@ export const getUser = async () => {
return { return {
id: session.user.user_id, id: session.user.user_id,
email: session.user.email, email: session.user.email,
role: session.user.droit roles: session.user.roles.map(role => ({
role_type: role.role_type,
establishment_id: role.establishment__id,
establishment_name: role.establishment__name
}))
}; };
} catch (error) { } catch (error) {

View File

@ -7,8 +7,7 @@ import {
BE_SCHOOL_DISCOUNTS_URL, BE_SCHOOL_DISCOUNTS_URL,
BE_SCHOOL_PAYMENT_PLANS_URL, BE_SCHOOL_PAYMENT_PLANS_URL,
BE_SCHOOL_PAYMENT_MODES_URL, BE_SCHOOL_PAYMENT_MODES_URL,
BE_SCHOOL_ESTABLISHMENT_URL, BE_SCHOOL_ESTABLISHMENT_URL
ESTABLISHMENT_ID
} from '@/utils/Url'; } from '@/utils/Url';
const requestResponseHandler = async (response) => { const requestResponseHandler = async (response) => {
@ -24,18 +23,18 @@ const requestResponseHandler = async (response) => {
} }
export const fetchSpecialities = () => { export const fetchSpecialities = (establishment) => {
return fetch(`${BE_SCHOOL_SPECIALITIES_URL}`) return fetch(`${BE_SCHOOL_SPECIALITIES_URL}?establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
}; };
export const fetchTeachers = () => { export const fetchTeachers = (establishment) => {
return fetch(`${BE_SCHOOL_TEACHERS_URL}`) return fetch(`${BE_SCHOOL_TEACHERS_URL}?establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
}; };
export const fetchClasses = () => { export const fetchClasses = (establishment) => {
return fetch(`${BE_SCHOOL_SCHOOLCLASSES_URL}`) return fetch(`${BE_SCHOOL_SCHOOLCLASSES_URL}?establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
}; };
@ -44,48 +43,48 @@ export const fetchSchedules = () => {
.then(requestResponseHandler) .then(requestResponseHandler)
}; };
export const fetchRegistrationDiscounts = () => { export const fetchRegistrationDiscounts = (establishment) => {
return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=registration`) return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=registration&establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
}; };
export const fetchTuitionDiscounts = () => { export const fetchTuitionDiscounts = (establishment) => {
return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=tuition`) return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=tuition&establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
}; };
export const fetchRegistrationFees = () => { export const fetchRegistrationFees = (establishment) => {
return fetch(`${BE_SCHOOL_FEES_URL}?filter=registration`) return fetch(`${BE_SCHOOL_FEES_URL}?filter=registration&establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
}; };
export const fetchTuitionFees = () => { export const fetchTuitionFees = (establishment) => {
return fetch(`${BE_SCHOOL_FEES_URL}?filter=tuition`) return fetch(`${BE_SCHOOL_FEES_URL}?filter=tuition&establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
}; };
export const fetchRegistrationPaymentPlans = () => { export const fetchRegistrationPaymentPlans = (establishment) => {
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=registration`) return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=registration&establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
} }
export const fetchTuitionPaymentPlans = () => { export const fetchTuitionPaymentPlans = (establishment) => {
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=tuition`) return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=tuition&establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
} }
export const fetchRegistrationPaymentModes = () => { export const fetchRegistrationPaymentModes = (establishment) => {
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=registration`) return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=registration&establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
} }
export const fetchTuitionPaymentModes = () => { export const fetchTuitionPaymentModes = (establishment) => {
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=tuition`) return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=tuition&establishment_id=${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
} }
export const fetchEstablishment = () => { export const fetchEstablishment = (establishment) => {
return fetch(`${BE_SCHOOL_ESTABLISHMENT_URL}/${ESTABLISHMENT_ID}`) return fetch(`${BE_SCHOOL_ESTABLISHMENT_URL}/${establishment}`)
.then(requestResponseHandler) .then(requestResponseHandler)
} }

View File

@ -100,7 +100,7 @@ export const archiveRegisterForm = (id) => {
} }
export const fetchStudents = (id=null, establishment) => { export const fetchStudents = (id=null, establishment) => {
const url = (id)?`${BE_SUBSCRIPTION_STUDENTS_URL}/${id}`:`${BE_SUBSCRIPTION_STUDENTS_URL}?establishment_id=${establishment}`; const url = (id)?`${BE_SUBSCRIPTION_STUDENTS_URL}/${id}?establishment_id=${establishment}`:`${BE_SUBSCRIPTION_STUDENTS_URL}?establishment_id=${establishment}`;
const request = new Request( const request = new Request(
url, url,
{ {
@ -114,9 +114,9 @@ export const fetchStudents = (id=null, establishment) => {
}; };
export const fetchChildren = (id) =>{ export const fetchChildren = (id, establishment) =>{
const request = new Request( const request = new Request(
`${BE_SUBSCRIPTION_CHILDRENS_URL}/${id}`, `${BE_SUBSCRIPTION_CHILDRENS_URL}/${id}?establishment_id=${establishment}`,
{ {
method:'GET', method:'GET',
headers: { headers: {

View File

@ -0,0 +1,31 @@
import React from 'react';
const ProfileSelector = ({ selectedProfile, setSelectedProfile }) => {
return (
<div className="flex space-x-4">
<button
type="button"
className={`px-4 py-2 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-blue-300 ${selectedProfile === 1 ? 'bg-blue-500 text-white ring-2 ring-blue-500' : 'bg-gray-200'}`}
onClick={() => setSelectedProfile(1)}
>
ADMINISTRATEUR
</button>
<button
type="button"
className={`px-4 py-2 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-blue-300 ${selectedProfile === 0 ? 'bg-blue-500 text-white ring-2 ring-blue-500' : 'bg-gray-200'}`}
onClick={() => setSelectedProfile(0)}
>
PROFESSEUR
</button>
<button
type="button"
className={`px-4 py-2 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-blue-300 ${selectedProfile === 2 ? 'bg-blue-500 text-white ring-2 ring-blue-500' : 'bg-gray-200'}`}
onClick={() => setSelectedProfile(2)}
>
PARENT
</button>
</div>
);
};
export default ProfileSelector;

View File

@ -18,8 +18,10 @@ const ProtectedRoute = ({ children, requiredRight }) => {
return <Loader />; return <Loader />;
} }
// Vérifier le rôle de l'utilisateur // Vérifier si l'utilisateur a au moins un rôle correspondant au requiredRight
if (session && requiredRight && session.user.droit !== requiredRight) { const hasRequiredRight = session?.user?.roles?.some(role => role.role_type === requiredRight);
if (session && requiredRight && !hasRequiredRight) {
router.push(`${FE_USERS_LOGIN_URL}`); router.push(`${FE_USERS_LOGIN_URL}`);
return null; return null;
} }

View File

@ -3,6 +3,8 @@
import { SessionProvider } from "next-auth/react" import { SessionProvider } from "next-auth/react"
import { CsrfProvider } from '@/context/CsrfContext' import { CsrfProvider } from '@/context/CsrfContext'
import { NextIntlClientProvider } from 'next-intl' import { NextIntlClientProvider } from 'next-intl'
import { EstablishmentProvider } from '@/context/EstablishmentContext';
export default function Providers({ children, messages, locale, session }) { export default function Providers({ children, messages, locale, session }) {
if (!locale) { if (!locale) {
@ -12,9 +14,11 @@ export default function Providers({ children, messages, locale, session }) {
return ( return (
<SessionProvider session={session}> <SessionProvider session={session}>
<CsrfProvider> <CsrfProvider>
<NextIntlClientProvider messages={messages} locale={locale}> <EstablishmentProvider>
{children} <NextIntlClientProvider messages={messages} locale={locale}>
</NextIntlClientProvider> {children}
</NextIntlClientProvider>
</EstablishmentProvider>
</CsrfProvider> </CsrfProvider>
</SessionProvider> </SessionProvider>
) )

View File

@ -1,6 +1,7 @@
'use client' 'use client'
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEstablishment } from '@/context/EstablishmentContext';
const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => ( const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
<div <div
@ -14,8 +15,9 @@ const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
</div> </div>
); );
function Sidebar({ establishment, currentPage, items }) { function Sidebar({ establishments, currentPage, items, onCloseMobile, onEstablishmentChange }) {
const router = useRouter(); const router = useRouter();
const { selectedEstablishmentId, setSelectedEstablishmentId, setProfileRole } = useEstablishment();
const [selectedItem, setSelectedItem] = useState(currentPage); const [selectedItem, setSelectedItem] = useState(currentPage);
useEffect(() => { useEffect(() => {
@ -25,31 +27,49 @@ function Sidebar({ establishment, currentPage, items }) {
const handleItemClick = (url) => { const handleItemClick = (url) => {
setSelectedItem(url); setSelectedItem(url);
router.push(url); router.push(url);
if (onCloseMobile) {
onCloseMobile();
}
}; };
return <> const handleEstablishmentChange = (e) => {
{/* Sidebar */} const establishmentId = parseInt(e.target.value, 10);
setSelectedEstablishmentId(establishmentId);
const role = establishments.find(est => est.id === establishmentId)?.role_type;
setProfileRole(role);
onEstablishmentChange(establishmentId);
};
return (
<div className="w-64 bg-white border-r h-full border-gray-200 py-6 px-4"> <div className="w-64 bg-white border-r h-full border-gray-200 py-6 px-4">
<div className="flex items-center mb-8 px-2"> <div className="flex items-center mb-8 px-2">
<div className="text-xl font-semibold">{establishment?.name}</div> <select
value={selectedEstablishmentId}
onChange={handleEstablishmentChange}
className="form-select block w-full mt-1"
>
{establishments.map(establishment => (
<option key={establishment.id} value={establishment.id}>
{establishment.name}
</option>
))}
</select>
</div> </div>
<nav className="space-y-1"> <nav className="space-y-1">
{ {items.map((item) => (
items.map((item) => ( <SidebarItem
<SidebarItem key={item.id}
key={item.id} icon={item.icon}
icon={item.icon} text={item.name}
text={item.name} active={item.id === selectedItem}
active={item.id === selectedItem} url={item.url}
url={item.url} onClick={() => handleItemClick(item.url)}
onClick={() => handleItemClick(item.url)} />
/> ))}
))
}
</nav> </nav>
</div> </div>
</> );
} }
export default Sidebar; export default Sidebar;

View File

@ -329,7 +329,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
<TeacherItem key={teacher.id} teacher={teacher} /> <TeacherItem key={teacher.id} teacher={teacher} />
); );
case 'EMAIL': case 'EMAIL':
return teacher.email; return teacher.associated_profile_email;
case 'SPECIALITES': case 'SPECIALITES':
return ( return (
<div className="flex justify-center space-x-2 flex-wrap"> <div className="flex justify-center space-x-2 flex-wrap">

View File

@ -0,0 +1,341 @@
import React, { useState, useEffect } from 'react';
import { Plus, Download, Edit, Trash2, FolderPlus, Signature } from 'lucide-react';
import Modal from '@/components/Modal';
import Table from '@/components/Table';
import FileUpload from '@/components/FileUpload';
import { formatDate } from '@/utils/Date';
import { BASE_URL } from '@/utils/Url';
import {
fetchRegisterFormFileTemplate,
createRegistrationFormFileTemplate,
editRegistrationFormFileTemplate,
deleteRegisterFormFileTemplate,
getRegisterFormFileTemplate
} from '@/app/actions/subscriptionAction';
import {
fetchRegistrationFileGroups,
createRegistrationFileGroup,
deleteRegistrationFileGroup,
editRegistrationFileGroup
} from '@/app/actions/registerFileGroupAction';
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
export default function FilesManagement({ csrfToken }) {
const [fichiers, setFichiers] = useState([]);
const [groups, setGroups] = useState([]);
const [selectedGroup, setSelectedGroup] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [fileToEdit, setFileToEdit] = useState(null);
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
const [groupToEdit, setGroupToEdit] = useState(null);
const [token]
// Fonction pour transformer les données des fichiers avec les informations complètes du groupe
const transformFileData = (file, groups) => {
if (!file.group) return file;
const groupInfo = groups.find(g => g.id === file.group);
return {
...file,
group: groupInfo || { id: file.group, name: 'Groupe inconnu' }
};
};
useEffect(() => {
Promise.all([
fetchRegisterFormFileTemplate(),
fetchRegistrationFileGroups()
]).then(([filesData, groupsData]) => {
setGroups(groupsData);
// Sélectionner automatiquement le premier groupe s'il existe
if (groupsData.length > 0) {
setSelectedGroup(groupsData[0].id.toString());
}
// Transformer chaque fichier pour inclure les informations complètes du groupe
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
setFichiers(transformedFiles);
}).catch(err => {
console.log(err.message);
});
}, []);
const handleFileDelete = (fileId) => {
deleteRegisterFormFileTemplate(fileId, csrfToken)
.then(response => {
if (response.ok) {
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
alert('Fichier supprimé avec succès.');
} else {
alert('Erreur lors de la suppression du fichier.');
}
})
.catch(error => {
console.error('Error deleting file:', error);
alert('Erreur lors de la suppression du fichier.');
});
};
const handleFileEdit = (file) => {
setIsEditing(true);
setFileToEdit(file);
setIsModalOpen(true);
};
const handleFileUpload = ({file, name, is_required, order, groupId}) => {
if (!name) {
alert('Veuillez entrer un nom de fichier.');
return;
}
const formData = new FormData();
if(file) {
formData.append('file', file);
}
formData.append('name', name);
formData.append('is_required', is_required);
formData.append('order', order);
// Modification ici : vérifier si groupId existe et n'est pas vide
if (groupId && groupId !== '') {
formData.append('group', groupId); // Notez que le nom du champ est 'group' et non 'group_id'
}
if (isEditing && fileToEdit) {
editRegistrationFormFileTemplate(fileToEdit.id, formData, csrfToken)
.then(data => {
// Transformer le fichier mis à jour avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setFichiers(prevFichiers =>
prevFichiers.map(f => f.id === fileToEdit.id ? transformedFile : f)
);
setIsModalOpen(false);
setFileToEdit(null);
setIsEditing(false);
})
.catch(error => {
console.error('Error editing file:', error);
alert('Erreur lors de la modification du fichier');
});
} else {
createRegistrationFormFileTemplate(formData, csrfToken)
.then(data => {
// Transformer le nouveau fichier avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setFichiers(prevFiles => [...prevFiles, transformedFile]);
setIsModalOpen(false);
})
.catch(error => {
console.error('Error uploading file:', error);
});
}
};
const handleGroupSubmit = (groupData) => {
if (groupToEdit) {
editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken)
.then(updatedGroup => {
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
setGroupToEdit(null);
setIsGroupModalOpen(false);
})
.catch(error => {
console.error('Error handling group:', error);
alert('Erreur lors de l\'opération sur le groupe');
});
} else {
createRegistrationFileGroup(groupData, csrfToken)
.then(newGroup => {
setGroups([...groups, newGroup]);
setIsGroupModalOpen(false);
})
.catch(error => {
console.error('Error handling group:', error);
alert('Erreur lors de l\'opération sur le groupe');
});
}
};
const handleGroupEdit = (group) => {
setGroupToEdit(group);
setIsGroupModalOpen(true);
};
const handleGroupDelete = (groupId) => {
// Vérifier si des fichiers utilisent ce groupe
const filesInGroup = fichiers.filter(file => file.group && file.group.id === groupId);
if (filesInGroup.length > 0) {
alert('Impossible de supprimer ce groupe car il contient des fichiers. Veuillez d\'abord retirer tous les fichiers de ce groupe.');
return;
}
if (window.confirm('Êtes-vous sûr de vouloir supprimer ce groupe ?')) {
deleteRegistrationFileGroup(groupId, csrfToken)
.then((response) => {
if (response.status === 409) {
throw new Error('Ce groupe est lié à des inscriptions existantes.');
}
if (!response.ok) {
throw new Error('Erreur lors de la suppression du groupe.');
}
setGroups(groups.filter(group => group.id !== groupId));
alert('Groupe supprimé avec succès.');
})
.catch(error => {
console.error('Error deleting group:', error);
alert(error.message || 'Erreur lors de la suppression du groupe. Vérifiez qu\'aucune inscription n\'utilise ce groupe.');
});
}
};
// Ajouter cette fonction de filtrage
const filteredFiles = fichiers.filter(file => {
if (!selectedGroup) return true;
return file.group && file.group.id === parseInt(selectedGroup);
});
const columnsFiles = [
{ name: 'Nom du fichier', transform: (row) => row.name },
{ name: 'Groupe', transform: (row) => row.group ? row.group.name : 'Aucun' },
{ name: 'Date de création', transform: (row) => formatDate(new Date (row.date_added),"DD/MM/YYYY hh:mm:ss") },
{ name: 'Fichier Obligatoire', transform: (row) => row.is_required ? 'Oui' : 'Non' },
{ name: 'Ordre de fusion', transform: (row) => row.order },
{ name: 'Actions', transform: (row) => (
<div className="flex items-center justify-center gap-2">
{row.file && (
<a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
<Download size={16} />
</a>
)}
<button onClick={() => handleFileEdit(row)} className="text-blue-500 hover:text-blue-700">
<Edit size={16} />
</button>
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
<Trash2 size={16} />
</button>
<button onClick={() => handleSignatureRequest(row)} className="text-green-500 hover:text-green-700">
<Signature size={16} />
</button>
</div>
)}
];
const columnsGroups = [
{ name: 'Nom du groupe', transform: (row) => row.name },
{ name: 'Description', transform: (row) => row.description },
{ name: 'Actions', transform: (row) => (
<div className="flex items-center justify-center gap-2">
<button onClick={() => handleGroupEdit(row)} className="text-blue-500 hover:text-blue-700">
<Edit size={16} />
</button>
<button onClick={() => handleGroupDelete(row.id)} className="text-red-500 hover:text-red-700">
<Trash2 size={16} />
</button>
</div>
)}
];
// Fonction pour gérer la demande de signature
const handleSignatureRequest = (file) => {
const formData = new FormData();
formData.append('file', file);
console.log('Demande de signature pour le fichier :', file);
fetch('http://localhost:8080:/DocuSeal/generateToken', {
method: 'POST',
headers: {
'Authorization': 'Bearer NFPZy6BBGvYs1BwTuXMQ3XAu5N1kLFiXWftGQhkiz2A',
},
body: formData,
})
.then((response) => {
if (!response.ok) {
throw new Error('Erreur lors du téléversement du document : ' + response.statusText);
}
return response.json();
})
.then((data) => {
const documentId = data.documentId;
console.log('Document téléversé avec succès, ID :', documentId);
onUpload(documentId);
});
.catch((error) => console.error(error));
};
return (
<div>
<Modal
isOpen={isModalOpen}
setIsOpen={setIsModalOpen}
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
ContentComponent={() => (
<FileUpload
onFileUpload={handleFileUpload}
fileToEdit={fileToEdit}
/>
)}
/>
<Modal
isOpen={isGroupModalOpen}
setIsOpen={setIsGroupModalOpen}
title={groupToEdit ? "Modifier le groupe" : "Ajouter un groupe de fichiers"}
ContentComponent={() => (
<RegistrationFileGroupForm
onSubmit={handleGroupSubmit}
initialData={groupToEdit}
/>
)}
/>
<div className="mt-8 mb-4">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Groupes de fichiers</h2>
<button
onClick={() => setIsGroupModalOpen(true)}
className="flex items-center bg-blue-600 text-white p-2 rounded-full shadow hover:bg-blue-900 transition duration-200"
>
<FolderPlus className="w-5 h-5" />
</button>
</div>
<Table
data={groups}
columns={columnsGroups}
itemsPerPage={5}
currentPage={1}
totalPages={Math.ceil(groups.length / 5)}
/>
</div>
{groups.length > 0 && (
<div className="mt-8">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Fichiers</h2>
<div className="flex items-center gap-4">
<select
className="border rounded p-2"
value={selectedGroup || ''}
onChange={(e) => setSelectedGroup(e.target.value)}
>
<option value="">Tous les groupes</option>
{groups.map(group => (
<option key={group.id} value={group.id}>{group.name}</option>
))}
</select>
<button
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
>
<Plus className="w-5 h-5" />
</button>
</div>
</div>
<Table
data={filteredFiles}
columns={columnsFiles}
itemsPerPage={10}
currentPage={1}
totalPages={Math.ceil(filteredFiles.length / 10)}
/>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,45 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
const EstablishmentContext = createContext();
export const EstablishmentProvider = ({ children }) => {
const [selectedEstablishmentId, setSelectedEstablishmentId] = useState(() => {
// Récupérer l'ID de l'établissement depuis le localStorage
if (typeof window !== 'undefined') {
return localStorage.getItem('selectedEstablishmentId') || null;
}
return null;
});
const [profileRole, setProfileRole] = useState(() => {
// Récupérer le rôle du profil depuis le localStorage
if (typeof window !== 'undefined') {
return localStorage.getItem('profileRole') || null;
}
return null;
});
useEffect(() => {
// Sauvegarder l'ID de l'établissement dans le localStorage
if (selectedEstablishmentId) {
localStorage.setItem('selectedEstablishmentId', selectedEstablishmentId);
}
}, [selectedEstablishmentId]);
useEffect(() => {
// Sauvegarder le rôle du profil dans le localStorage
if (profileRole) {
localStorage.setItem('profileRole', profileRole);
}
}, [profileRole]);
return (
<EstablishmentContext.Provider value={{ selectedEstablishmentId, setSelectedEstablishmentId, profileRole, setProfileRole }}>
{children}
</EstablishmentContext.Provider>
);
};
export const useEstablishment = () => {
return useContext(EstablishmentContext);
};

View File

@ -11,24 +11,23 @@ const options = {
name: 'Credentials', name: 'Credentials',
credentials: { credentials: {
email: { label: 'Email', type: 'email' }, email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' } password: { label: 'Password', type: 'password' },
role_type: { label: 'Role Type', type: 'text' }
}, },
authorize: async (credentials, req) => { authorize: async (credentials, req) => {
try { try {
const data = { const data = {
email: credentials.email, email: credentials.email,
password: credentials.password password: credentials.password,
role_type: credentials.role_type
}; };
const user = await getJWT(data); const user = await getJWT(data);
if (user) { if (user) {
logger.debug("API response:", user);
return user; return user;
} }
logger.error('Invalid credentials')
} catch (error) { } catch (error) {
logger.error('Invalid credentials')
throw new Error(error.message || 'Invalid credentials'); throw new Error(error.message || 'Invalid credentials');
} }
} }
@ -88,16 +87,15 @@ const options = {
}, },
async session({ session, token }) { async session({ session, token }) {
if (token && token?.token) { if (token && token?.token) {
const {user_id, droit, email, establishment} = jwt_decode.decode(token.token); const { user_id, email, roles } = jwt_decode.decode(token.token);
session.user = { session.user = {
...session.user, ...session.user,
token: token.token, token: token.token,
refresh: token.refresh refresh: token.refresh,
user_id: user_id,
email: email,
roles: roles
}; };
session.user.user_id = user_id;
session.user.droit = droit;
session.user.email = email;
session.user.establishment = establishment;
} }
return session; return session;
} }

View File

@ -40,7 +40,9 @@ export const BE_SCHOOL_FEES_URL = `${BASE_URL}/School/fees`;
export const BE_SCHOOL_DISCOUNTS_URL = `${BASE_URL}/School/discounts`; export const BE_SCHOOL_DISCOUNTS_URL = `${BASE_URL}/School/discounts`;
export const BE_SCHOOL_PAYMENT_PLANS_URL = `${BASE_URL}/School/paymentPlans`; export const BE_SCHOOL_PAYMENT_PLANS_URL = `${BASE_URL}/School/paymentPlans`;
export const BE_SCHOOL_PAYMENT_MODES_URL = `${BASE_URL}/School/paymentModes`; export const BE_SCHOOL_PAYMENT_MODES_URL = `${BASE_URL}/School/paymentModes`;
export const BE_SCHOOL_ESTABLISHMENT_URL = `${BASE_URL}/School/establishments`;
// ESTABLISHMENT
export const BE_SCHOOL_ESTABLISHMENT_URL = `${BASE_URL}/Establishment/establishments`;
// GESTION PLANNING // GESTION PLANNING