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.utils.translation import gettext_lazy as _
from django.core.validators import EmailValidator
from Establishment.models import Establishment
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()])
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ('password', )
code = 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):
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 Auth.models import Profile
from Auth.models import Profile, ProfileRole
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):
id = serializers.IntegerField(required=False)
password = serializers.CharField(write_only=True)
roles = ProfileRoleSerializer(many=True, required=False)
class Meta:
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}}
def create(self, validated_data):
roles_data = validated_data.pop('roles', [])
user = Profile(
username=validated_data['username'],
email=validated_data['email'],
is_active=validated_data['is_active'],
droit=validated_data['droit'],
establishment=validated_data.get('establishment')
code=validated_data.get('code', ''),
datePeremption=validated_data.get('datePeremption', '')
)
user.set_password(validated_data['password'])
user.full_clean()
user.save()
for role_data in roles_data:
ProfileRole.objects.create(profile=user, **role_data)
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):
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.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

View File

@ -19,7 +19,7 @@ from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
import json
from . import validator
from .models import Profile
from .models import Profile, ProfileRole
from rest_framework.decorators import action, api_view
from Auth.serializers import ProfileSerializer, ProfilUpdateSerializer
@ -56,7 +56,10 @@ class SessionView(APIView):
'user': openapi.Schema(type=openapi.TYPE_OBJECT, properties={
'id': openapi.Schema(type=openapi.TYPE_INTEGER),
'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')
@ -67,15 +70,16 @@ class SessionView(APIView):
try:
decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
print(f'decode : {decoded_token}')
userid = decoded_token.get('id')
userid = decoded_token.get('user_id')
user = Profile.objects.get(id=userid)
roles = ProfileRole.objects.filter(profile=user).values('role_type', 'establishment__name')
response_data = {
'user': {
'id': user.id,
'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)
@ -103,13 +107,11 @@ class ProfileView(APIView):
}
)
def post(self, request):
profil_data=JSONParser().parse(request)
print(f'{profil_data}')
profil_data = JSONParser().parse(request)
profil_serializer = ProfileSerializer(data=profil_data)
if profil_serializer.is_valid():
profil_serializer.save()
profil = profil_serializer.save()
return JsonResponse(profil_serializer.data, safe=False)
return JsonResponse(profil_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@ -122,8 +124,8 @@ class ProfileSimpleView(APIView):
responses={200: ProfileSerializer}
)
def get(self, request, id):
profil=bdd.getObject(Profile, "id", id)
profil_serializer=ProfileSerializer(profil)
profil = bdd.getObject(Profile, "id", id)
profil_serializer = ProfileSerializer(profil)
return JsonResponse(profil_serializer.data, safe=False)
@swagger_auto_schema(
@ -135,12 +137,12 @@ class ProfileSimpleView(APIView):
}
)
def put(self, request, id):
data=JSONParser().parse(request)
data = JSONParser().parse(request)
profil = Profile.objects.get(id=id)
profil_serializer = ProfilUpdateSerializer(profil, data=data)
if profil_serializer.is_valid():
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)
@ -157,10 +159,11 @@ class LoginView(APIView):
operation_description="Connexion utilisateur",
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['email', 'password'],
required=['email', 'password', 'role_type'],
properties={
'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={
@ -193,40 +196,45 @@ class LoginView(APIView):
password=data.get('password'),
)
if user is not None:
if user.is_active:
login(request, user)
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(),
}
role_type = data.get('role_type')
primary_role = ProfileRole.objects.filter(profile=user, role_type=role_type, is_active=True).first()
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'])
if not primary_role:
return JsonResponse({"errorMessage": "Role not assigned to the user"}, status=status.HTTP_401_UNAUTHORIZED)
return JsonResponse({
'token': access_token,
'refresh': refresh_token
}, safe=False)
login(request, user)
user.save()
clear_cache()
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:
retour = error.returnMessage[error.WRONG_ID]
@ -235,7 +243,6 @@ class LoginView(APIView):
'errorMessage': retour,
}, safe=False, status=status.HTTP_400_BAD_REQUEST)
class RefreshJWTView(APIView):
@swagger_auto_schema(
operation_description="Rafraîchir le token d'accès",
@ -295,13 +302,20 @@ class RefreshJWTView(APIView):
# Récupérer les informations utilisateur
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
new_access_payload = {
'user_id': user.id,
'email': user.email,
'droit': user.droit,
'establishment': user.establishment.id,
'role_type': primary_role.get_role_type_display(),
'establishment': primary_role.establishment.id,
'type': 'access',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
@ -311,6 +325,7 @@ class RefreshJWTView(APIView):
new_refresh_payload = {
'user_id': user.id,
'role_type': role_type,
'type': 'refresh',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
@ -335,6 +350,14 @@ class RefreshJWTView(APIView):
class SubscribeView(APIView):
@swagger_auto_schema(
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(
type=openapi.TYPE_OBJECT,
required=['email', 'password1', 'password2'],
@ -359,37 +382,54 @@ class SubscribeView(APIView):
def post(self, request):
retourErreur = error.returnMessage[error.BAD_URL]
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)
validationOk, errorFields = validatorSubscription.validate()
if validationOk:
# 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'))
if profil == None:
if profil is None:
retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS]
else:
if profil.is_active:
retourErreur=error.returnMessage[error.PROFIL_ACTIVE]
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields, "id":profil.id}, safe=False)
# Vérifier si le profil a déjà un rôle actif pour l'établissement donné
active_roles = ProfileRole.objects.filter(profile=profil, establishment_id=establishment_id, is_active=True)
if active_roles.exists():
retourErreur = error.returnMessage[error.PROFIL_ACTIVE]
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": profil.id}, safe=False)
else:
try:
profil.set_password(newProfilConnection.get('password1'))
profil.is_active = True
profil.full_clean()
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()
retour = error.returnMessage[error.MESSAGE_ACTIVATION_PROFILE]
retourErreur=''
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields, "id":profil.id}, safe=False)
retourErreur = ''
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": profil.id}, safe=False)
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, "id":-1}, safe=False)
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": -1}, safe=False)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
@ -417,26 +457,29 @@ class NewPasswordView(APIView):
def post(self, request):
retourErreur = error.returnMessage[error.BAD_URL]
retour = ''
newProfilConnection=JSONParser().parse(request)
newProfilConnection = JSONParser().parse(request)
validatorNewPassword = validator.ValidatorNewPassword(data=newProfilConnection)
validationOk, errorFields = validatorNewPassword.validate()
if validationOk:
profil = bdd.getProfile(Profile.objects.all(), newProfilConnection.get('email'))
if profil == None:
if profil is None:
retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS]
else:
# Génération d'une URL provisoire pour modifier le mot de passe
profil.code = util.genereRandomCode(12)
profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS)
profil.save()
clear_cache()
retourErreur = ''
retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD]%(newProfilConnection.get('email'))
mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code)
try:
# Génération d'une URL provisoire pour modifier le mot de passe
profil.code = util.genereRandomCode(12)
profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS)
profil.save()
clear_cache()
retourErreur = ''
retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD] % (newProfilConnection.get('email'))
mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code)
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(ensure_csrf_cookie, name='dispatch')
@ -465,7 +508,7 @@ class ResetPasswordView(APIView):
def post(self, request, code):
retourErreur = error.returnMessage[error.BAD_URL]
retour = ''
newProfilConnection=JSONParser().parse(request)
newProfilConnection = JSONParser().parse(request)
validatorResetPassword = validator.ValidatorResetPassword(data=newProfilConnection)
validationOk, errorFields = validatorResetPassword.validate()
@ -474,16 +517,15 @@ class ResetPasswordView(APIView):
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'):
retourErreur = error.returnMessage[error.EXPIRED_URL]%(_uuid)
retourErreur = error.returnMessage[error.EXPIRED_URL] % (_uuid)
elif validationOk:
retour = error.returnMessage[error.PASSWORD_CHANGED]
profil.set_password(newProfilConnection.get('password1'))
profil.code = ''
profil.datePeremption = ''
profil.is_active = True
profil.save()
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 Subscriptions.models import RegistrationForm
@receiver(post_save, sender=Messagerie)
def notification_MESSAGE(sender, instance, created, **kwargs):
if created:
Notification.objects.create(
user=instance.destinataire,
message=(TypeNotif.NOTIF_MESSAGE).label,
typeNotification=TypeNotif.NOTIF_MESSAGE
)
# @receiver(post_save, sender=Messagerie)
# def notification_MESSAGE(sender, instance, created, **kwargs):
# if created:
# Notification.objects.create(
# user=instance.destinataire,
# message=(TypeNotif.NOTIF_MESSAGE).label,
# typeNotification=TypeNotif.NOTIF_MESSAGE
# )
@receiver(post_save, sender=RegistrationForm)
def notification_DI(sender, instance, created, **kwargs):
for responsable in instance.student.guardians.all():
Notification.objects.create(
user=responsable.associated_profile,
message=(TypeNotif.NOTIF_DI).label,
typeNotification=TypeNotif.NOTIF_DI
)
# @receiver(post_save, sender=RegistrationForm)
# def notification_DI(sender, instance, created, **kwargs):
# for responsable in instance.student.guardians.all():
# Notification.objects.create(
# user=responsable.associated_profile,
# message=(TypeNotif.NOTIF_DI).label,
# typeNotification=TypeNotif.NOTIF_DI
# )

View File

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

View File

@ -46,6 +46,7 @@ urlpatterns = [
path("School/", include(("School.urls", 'School'), namespace='School')),
path("DocuSeal/", include(("DocuSeal.urls", 'DocuSeal'), namespace='DocuSeal')),
path("Planning/", include(("Planning.urls", 'Planning'), namespace='Planning')),
path("Establishment/", include(("Establishment.urls", 'Establishment'), namespace='Establishment')),
# Documentation Api
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'),

View File

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

View File

@ -1,14 +1,5 @@
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):
default_auto_field = 'django.db.models.BigAutoField'
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.contrib.auth.models import User
from Subscriptions.models import (
RegistrationForm,
Student,
@ -10,9 +9,8 @@ from Subscriptions.models import (
RegistrationTemplateMaster,
RegistrationTemplate
)
from Auth.models import Profile
from Auth.models import Profile, ProfileRole
from School.models import (
Establishment,
FeeType,
Speciality,
Teacher,
@ -21,7 +19,6 @@ from School.models import (
PaymentModeType,
PaymentPlan,
PaymentPlanType,
StructureType,
DiscountType
)
from django.utils import timezone
@ -34,6 +31,19 @@ from faker import Faker
import random
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
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'
def handle(self, *args, **kwargs):
self.create_or_update_establishments()
self.create_or_update_fees()
self.create_or_update_discounts()
self.create_or_update_payment_modes()
self.create_or_update_payment_plans()
self.create_or_update_specialities()
self.create_or_update_teachers()
self.create_or_update_school_classes()
self.create_or_update_registration_file_group()
self.create_register_form()
self.init_establishments()
self.init_profiles()
self.init_fees()
self.init_discounts()
self.init_payment_modes()
self.init_payment_plans()
self.init_specialities()
self.init_teachers()
self.init_guardians()
self.init_school_classes()
self.init_file_group()
self.init_register_form()
def load_data(self, filename):
with open(os.path.join(MOCK_DATAS_PATH, filename), 'r') as file:
return json.load(file)
def create_or_update_establishments(self):
def init_establishments(self):
establishments_data = self.load_data('establishments.json')
self.establishments = []
for establishment_data in establishments_data:
establishment, created = Establishment.objects.update_or_create(
name=establishment_data["name"],
defaults=establishment_data
)
self.establishments.append(establishment)
if created:
self.stdout.write(self.style.SUCCESS(f'Establishment {establishment.name} created successfully'))
serializer = EstablishmentSerializer(data=establishment_data)
if serializer.is_valid():
establishment = serializer.save()
self.establishments.append(establishment)
self.stdout.write(self.style.SUCCESS(f'Establishment {establishment.name} created or updated successfully'))
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')
for fee_data in fees_data:
establishment = random.choice(self.establishments)
print(f'establishment : {establishment}')
fee_data["name"] = f"{fee_data['name']} - {establishment.name}"
fee_data["establishment"] = establishment
Fee.objects.update_or_create(
name=fee_data["name"],
type=fee_data["type"],
defaults=fee_data
)
fee_data["establishment"] = establishment.id
fee_data["type"] = random.choice([FeeType.REGISTRATION_FEE, FeeType.TUITION_FEE])
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')
for discount_data in discounts_data:
establishment = random.choice(self.establishments)
discount_data["name"] = f"{discount_data['name']} - {establishment.name}"
discount_data["establishment"] = establishment
Discount.objects.update_or_create(
name=discount_data["name"],
type=discount_data["type"],
discount_type=discount_data["discount_type"],
defaults=discount_data
)
discount_data["establishment"] = establishment.id
discount_data["type"] = random.choice([FeeType.REGISTRATION_FEE, FeeType.TUITION_FEE])
discount_data["discount_type"] = random.choice([DiscountType.CURRENCY, DiscountType.PERCENT])
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):
payment_modes_data = self.load_data('payment_modes.json')
def init_payment_modes(self):
modes = [PaymentModeType.SEPA, PaymentModeType.TRANSFER, PaymentModeType.CHECK, PaymentModeType.CASH]
types = [FeeType.REGISTRATION_FEE, FeeType.TUITION_FEE]
for payment_mode_data in payment_modes_data:
establishment = random.choice(self.establishments)
payment_mode_data["establishment"] = establishment
PaymentMode.objects.update_or_create(
mode=payment_mode_data["mode"],
type=payment_mode_data["type"],
defaults=payment_mode_data
)
for establishment in self.establishments:
for mode in modes:
for type in types:
payment_mode_data = {
"mode": mode,
"type": type,
"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):
payment_plans_data = self.load_data('payment_plans.json')
def init_payment_plans(self):
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()
for payment_plan_data in payment_plans_data:
establishment = random.choice(self.establishments)
payment_plan_data["establishment"] = establishment
payment_plan_data["due_dates"] = [current_date + relativedelta(months=1)]
if payment_plan_data["frequency"] == PaymentPlanType.THREE_TIMES:
payment_plan_data["due_dates"] = [current_date + relativedelta(months=1+4*i) for i in range(3)]
elif payment_plan_data["frequency"] == PaymentPlanType.TEN_TIMES:
payment_plan_data["due_dates"] = [current_date + relativedelta(months=1+i) for i in range(10)]
elif payment_plan_data["frequency"] == PaymentPlanType.TWELVE_TIMES:
payment_plan_data["due_dates"] = [current_date + relativedelta(months=1+i) for i in range(12)]
for establishment in self.establishments:
for frequency in frequencies:
for type in types:
payment_plan_data = {
"frequency": frequency,
"type": type,
"is_active": random.choice([True, False]),
"establishment": establishment.id,
"due_dates": self.generate_due_dates(frequency, current_date)
}
PaymentPlan.objects.update_or_create(
frequency=payment_plan_data["frequency"],
type=payment_plan_data["type"],
defaults=payment_plan_data
)
serializer = PaymentPlanSerializer(data=payment_plan_data)
if serializer.is_valid():
payment_plan = serializer.save()
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')
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)
speciality_data["name"] = f"{speciality_data['name']} - {establishment.name}"
speciality_data["establishment"] = establishment.id
# Create or update the user profile
user, created = Profile.objects.update_or_create(
email=email,
defaults={
"username": email,
"email": email,
"is_active": True,
"password": "Provisoire01!",
"droit": droit,
"establishment": establishment
serializer = SpecialitySerializer(data=speciality_data)
if serializer.is_valid():
speciality = serializer.save()
self.stdout.write(self.style.SUCCESS(f'Speciality {speciality.name} created successfully'))
else:
self.stdout.write(self.style.ERROR(f'Error in data for speciality: {serializer.errors}'))
def init_teachers(self):
fake = Faker()
# 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
teacher, created = Teacher.objects.update_or_create(
email=email,
defaults={**teacher_data, "associated_profile_id": user.id}
)
teacher.specialities.set(Speciality.objects.filter(name__in=specialities))
teacher.save()
establishment_specialities = list(Speciality.objects.filter(establishment=establishment))
num_specialities = min(random.randint(1, 3), len(establishment_specialities))
selected_specialities = random.sample(establishment_specialities, num_specialities)
# Créer l'enseignant si il n'existe pas
teacher_serializer = TeacherSerializer(data=teacher_data)
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'))
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')
for index, class_data in enumerate(school_classes_data, start=1):
teachers_ids = class_data.pop("teachers")
# Randomize establishment
establishment = random.choice(self.establishments)
class_data["atmosphere_name"] = f"Classe {index} - {establishment.name}"
class_data["establishment"] = establishment
school_class, created = SchoolClass.objects.update_or_create(
atmosphere_name=class_data["atmosphere_name"],
school_year=class_data["school_year"],
defaults=class_data
)
school_class.teachers.set(teachers_ids)
school_class.save()
class_data["establishment"] = establishment.id
# Randomize levels
class_data["levels"] = random.sample(range(1, 10), random.randint(1, 5))
# Randomize teachers
establishment_teachers = list(Teacher.objects.filter(profile_role__establishment=establishment))
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'))
def create_or_update_registration_file_group(self):
file_groups_data = self.load_data('file_groups.json')
def init_file_group(self):
fake = Faker()
for group_data in file_groups_data:
RegistrationFileGroup.objects.update_or_create(name=group_data["name"], defaults=group_data)
self.stdout.write(self.style.SUCCESS(f'RegistrationFileGroup {group_data["name"]} initialized or updated successfully'))
for establishment in self.establishments:
for i in range(1, 4): # Créer 3 groupes de fichiers par établissement
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
file_group_count = RegistrationFileGroup.objects.count()
@ -218,40 +358,13 @@ class Command(BaseCommand):
for _ in range(50):
establishment = random.choice(self.establishments)
# Générer des données fictives pour le profil
profile_data = {
"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"],
}
# Récupérer un guardian aléatoire déjà créé
guardian = Guardian.objects.order_by('?').first()
# Générer des données fictives pour l'étudiant
student_data = {
"last_name": f"{fake.last_name()} - {establishment.name}",
"first_name": fake.first_name(),
"last_name": fake.last_name(),
"first_name": f"{fake.first_name()} - {establishment.name}",
"address": fake.address(),
"birth_date": fake.date_of_birth(),
"birth_place": fake.city(),
@ -261,16 +374,12 @@ class Command(BaseCommand):
"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(
last_name=student_data["last_name"],
first_name=student_data["first_name"],
defaults=student_data
)
guardian, created = Guardian.objects.get_or_create(
last_name=guardian_data["email"],
defaults=guardian_data
)
student.guardians.add(guardian)
# 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
register_form_data = {
"student": student,
"fileGroup": RegistrationFileGroup.objects.get(id=fake.random_int(min=1, max=file_group_count)),
"establishment": establishment,
"status": fake.random_int(min=1, max=3)
}
# 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.fees.set(fees)
register_form.discounts.set(discounts)
if not created:
register_form.fileGroup = file_group
register_form.save()
register_form, created = RegistrationForm.objects.get_or_create(
student=student,
establishment=establishment,
defaults=register_form_data
)
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'))

View File

@ -2,15 +2,41 @@
{
"name": "Parrainage",
"amount": "10.00",
"description": "Réduction pour parrainage",
"discount_type": 1,
"type": 1
"description": "Réduction pour parrainage"
},
{
"name": "Réinscription",
"amount": "100.00",
"description": "Réduction pour Réinscription",
"discount_type": 1,
"type": 0
"description": "Réduction pour Réinscription"
},
{
"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",
"base_amount": "150.00",
"description": "Montant de base",
"is_active": true,
"type": 0
"is_active": true
},
{
"name": "Matériel",
"base_amount": "85.00",
"description": "Livres / jouets",
"is_active": true,
"type": 0
"is_active": true
},
{
"name": "Sorties périscolaires",
"base_amount": "120.00",
"description": "Sorties",
"is_active": true,
"type": 0
"is_active": true
},
{
"name": "Les colibris",
"base_amount": "4500.00",
"description": "TPS / PS / MS / GS",
"is_active": true,
"type": 1
"is_active": true
},
{
"name": "Les butterflies",
"base_amount": "5000.00",
"description": "CP / CE1 / CE2 / CM1 / CM2",
"is_active": true,
"type": 1
"is_active": true
}
]

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

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 Auth.models import Profile
from Auth.models import ProfileRole
from Establishment.models import Establishment
from django.db.models import JSONField
from django.dispatch import receiver
from django.contrib.postgres.fields import ArrayField
@ -18,27 +19,11 @@ LEVEL_CHOICES = [
(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):
name = models.CharField(max_length=100)
updated_date = models.DateTimeField(auto_now=True)
color_code = models.CharField(max_length=7, default='#FFFFFF')
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='specialities')
def __str__(self):
return self.name
@ -46,9 +31,8 @@ class Speciality(models.Model):
class Teacher(models.Model):
last_name = models.CharField(max_length=100)
first_name = models.CharField(max_length=100)
email = models.EmailField(unique=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)
def __str__(self):

View File

@ -1,7 +1,8 @@
from rest_framework import serializers
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan, PaymentMode, Establishment
from Auth.models import Profile
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan, PaymentMode
from Auth.models import Profile, ProfileRole
from Subscriptions.models import Student
from Establishment.models import Establishment
from N3wtSchool import settings, bdd
from django.utils import timezone
import pytz
@ -30,9 +31,10 @@ class TeacherDetailSerializer(serializers.ModelSerializer):
class TeacherSerializer(serializers.ModelSerializer):
specialities = serializers.PrimaryKeyRelatedField(queryset=Speciality.objects.all(), many=True, required=False)
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()
droit = serializers.SerializerMethodField()
role_type = serializers.SerializerMethodField()
associated_profile_email = serializers.SerializerMethodField()
class Meta:
model = Teacher
@ -40,12 +42,12 @@ class TeacherSerializer(serializers.ModelSerializer):
def create(self, validated_data):
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)
if specialities_data:
teacher.specialities.set(specialities_data)
if associated_profile:
teacher.associated_profile = associated_profile
if profile_role:
teacher.profile_role = profile_role
teacher.save()
return teacher
@ -54,7 +56,7 @@ class TeacherSerializer(serializers.ModelSerializer):
instance.last_name = validated_data.get('last_name', instance.last_name)
instance.first_name = validated_data.get('first_name', instance.first_name)
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()
if 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
local_tz = pytz.timezone(settings.TZ_APPLI)
local_time = utc_time.astimezone(local_tz)
return local_time.strftime("%d-%m-%Y %H:%M")
def get_droit(self, obj):
if obj.associated_profile:
return obj.associated_profile.droit
return None
def get_role_type(self, obj):
profile_role = obj.profile_role
return {'role_type': profile_role.role_type, 'establishment': profile_role.establishment.name}
def get_specialities_details(self, obj):
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 Meta:
model = Planning
@ -208,8 +211,3 @@ class PaymentModeSerializer(serializers.ModelSerializer):
class Meta:
model = PaymentMode
fields = '__all__'
class EstablishmentSerializer(serializers.ModelSerializer):
class Meta:
model = Establishment
fields = '__all__'

View File

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

View File

@ -12,8 +12,7 @@ from .models import (
Discount,
Fee,
PaymentPlan,
PaymentMode,
Establishment
PaymentMode
)
from .serializers import (
TeacherSerializer,
@ -23,8 +22,7 @@ from .serializers import (
DiscountSerializer,
FeeSerializer,
PaymentPlanSerializer,
PaymentModeSerializer,
EstablishmentSerializer
PaymentModeSerializer
)
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')
class SpecialityListCreateView(APIView):
def get(self, request):
specialitiesList = getAllObjects(Speciality)
specialities_serializer = SpecialitySerializer(specialitiesList, many=True)
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)
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)
def post(self, request):
@ -51,7 +55,7 @@ class SpecialityListCreateView(APIView):
class SpecialityDetailView(APIView):
def get(self, request, id):
speciality = getObject(_objectName=Speciality, _columnName='id', _value=id)
speciality_serializer = SpecialitySerializer(speciality)
speciality_serializer=SpecialitySerializer(speciality)
return JsonResponse(speciality_serializer.data, safe=False)
def put(self, request, id):
@ -71,9 +75,14 @@ class SpecialityDetailView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch')
class TeacherListCreateView(APIView):
def get(self, request):
teachersList=getAllObjects(Teacher)
teachers_serializer=TeacherSerializer(teachersList, many=True)
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)
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)
def post(self, request):
@ -107,14 +116,20 @@ class TeacherDetailView(APIView):
return JsonResponse(teacher_serializer.errors, safe=False)
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(ensure_csrf_cookie, name='dispatch')
class SchoolClassListCreateView(APIView):
def get(self, request):
classesList=getAllObjects(SchoolClass)
classes_serializer=SchoolClassSerializer(classesList, many=True)
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)
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)
def post(self, request):
@ -201,9 +216,14 @@ class PlanningDetailView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch')
class FeeListCreateView(APIView):
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()
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)
return JsonResponse(fee_serializer.data, safe=False, status=status.HTTP_200_OK)
@ -246,9 +266,14 @@ class FeeDetailView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch')
class DiscountListCreateView(APIView):
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()
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)
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')
class PaymentPlanListCreateView(APIView):
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()
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)
@ -336,10 +366,15 @@ class PaymentPlanDetailView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch')
class PaymentModeListCreateView(APIView):
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()
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)
@ -376,45 +411,3 @@ class PaymentModeDetailView(APIView):
def delete(self, request, 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 School.models import SchoolClass, Fee, Discount
from Auth.models import ProfileRole
from Establishment.models import Establishment
from datetime import datetime
@ -25,10 +27,9 @@ class Guardian(models.Model):
first_name = models.CharField(max_length=200, default="")
birth_date = 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)
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):
return self.last_name + "_" + self.first_name
@ -163,6 +164,7 @@ class Student(models.Model):
class RegistrationFileGroup(models.Model):
name = models.CharField(max_length=255, default="")
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):
return self.name
@ -214,7 +216,7 @@ class RegistrationForm(models.Model):
null=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):
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 School.models import SchoolClass, Fee, Discount, FeeType
from School.serializers import FeeSerializer, DiscountSerializer
from Auth.models import Profile
from Auth.models import ProfileRole
from Auth.serializers import ProfileSerializer
from GestionMessagerie.models import Messagerie
from GestionNotification.models import Notification
@ -68,7 +68,7 @@ class SiblingSerializer(serializers.ModelSerializer):
class GuardianSerializer(serializers.ModelSerializer):
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()
class Meta:
@ -76,7 +76,7 @@ class GuardianSerializer(serializers.ModelSerializer):
fields = '__all__'
def get_associated_profile_email(self, obj):
return obj.associated_profile.email
return obj.profile_role.profile.email
class StudentSerializer(serializers.ModelSerializer):
@ -248,7 +248,7 @@ class GuardianByDICreationSerializer(serializers.ModelSerializer):
class Meta:
model = Guardian
fields = ['id', 'last_name', 'first_name', 'email', 'associated_profile']
fields = ['id', 'last_name', 'first_name', 'email', 'profile']
class StudentByRFCreationSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)

View File

@ -90,6 +90,12 @@ class ChildrenListView(APIView):
# Récupération des élèves d'un parent
# idProfile : identifiant du profil connecté rattaché aux fiches d'élèves
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)
return JsonResponse(students_serializer.data, safe=False)

View File

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

View File

@ -3,7 +3,7 @@
import React, { useState, useEffect } from 'react';
import Sidebar from '@/components/Sidebar';
import { usePathname } from 'next/navigation';
import {useTranslations} from 'next-intl';
import { useTranslations } from 'next-intl';
import Image from 'next/image';
import {
Users,
@ -35,6 +35,9 @@ import ProtectedRoute from '@/components/ProtectedRoute';
import { getGravatarUrl } from '@/utils/gravatar';
import Footer from '@/components/Footer';
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({
children,
@ -51,11 +54,12 @@ export default function Layout({
"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 [isPopupVisible, setIsPopupVisible] = useState(false);
const [user, setUser] = useState(null);
const { data: session } = useSession();
const { selectedEstablishmentId, setSelectedEstablishmentId, profileRole, setProfileRole } = useEstablishment();
const pathname = usePathname();
const currentPage = pathname.split('/').pop();
@ -80,7 +84,7 @@ export default function Layout({
content: (
<div className="px-4 py-2">
<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>
)
},
@ -106,17 +110,34 @@ export default function Layout({
}, [pathname]);
useEffect(() => {
setIsLoading(true);
fetchEstablishment()
.then(data => {
setEstablishment(data);
getSession()
.then(session => {
if (session && session.user) {
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))
.finally(() => setIsLoading(false));
.catch(err => {
logger.error('Error fetching session:', err);
});
}, []);
useEffect(() => {
const fetchUser = async () => {
const fetchUser = async () => {
if (session) { // Vérifier que la session existe
const userData = await getUser();
setUser(userData);
@ -126,8 +147,6 @@ export default function Layout({
fetchUser();
}, [session]);
return (
<ProtectedRoute requiredRight={RIGHTS.ADMIN}>
{!isLoading && (
@ -138,12 +157,16 @@ export default function Layout({
style={{ height: '100vh' }} // Force la hauteur à 100% de la hauteur de la vue
>
<Sidebar
establishment={establishment}
establishments={establishments}
currentPage={currentPage}
items={Object.values(sidebarItems)}
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>

View File

@ -39,47 +39,61 @@ export default function DashboardPage() {
const [classes, setClasses] = useState([]);
const [establishmentId, setEstablishmentId] = useState(null);
useEffect(() => {
// Fetch data for classes
fetchClasses().then(data => {
setClasses(data);
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);
getSession()
.then(session => {
if (session && session.user) {
setEstablishmentId(session.user.establishment);
}
})
.catch(error => {
logger.error('Error fetching classes:', error);
.catch(err => {
logger.error('Error fetching session:', err);
});
}, []);
fetchRegisterForms().then(data => {
logger.info('Pending registrations fetched:', data);
setPendingRegistration(data.count);
})
.catch(error => {
logger.error('Error fetching pending registrations:', error);
});
useEffect(() => {
if (establishmentId) {
// Fetch data for classes
fetchClasses(establishmentId).then(data => {
setClasses(data);
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 => {
logger.error('Error fetching upcoming events:', error);
});
})
.catch(error => {
logger.error('Error fetching classes:', error);
});
// Simulation de chargement des données
setTimeout(() => {
setMonthlyStats({
inscriptions: [150, 180, 210, 245],
completionRate: 78
});
setIsLoading(false);
}, 1000);
}
, []);
fetchRegisterForms().then(data => {
logger.info('Pending registrations fetched:', data);
setPendingRegistration(data.count);
})
.catch(error => {
logger.error('Error fetching pending registrations:', error);
});
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 />;

View File

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

View File

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

View File

@ -37,8 +37,9 @@ export default function Layout({
setIsPopupVisible(false);
disconnect();
};
useEffect(() => {
const fetchUser = async () => {
useEffect(() => {
const fetchUser = async () => {
if (session) { // Vérifier que la session existe
const userData = await getUser();
setUser(userData);
@ -48,6 +49,7 @@ export default function Layout({
fetchUser();
}, [session]);
// useEffect(() => {
// if (status === 'loading') return;
// if (!session) {
@ -81,7 +83,7 @@ const dropdownItems = [
content: (
<div className="px-4 py-2">
<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>
)
},

View File

@ -9,13 +9,15 @@ import { fetchChildren } from '@/app/actions/subscriptionAction';
import logger from '@/utils/logger';
import { useSession } from 'next-auth/react';
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
import { useEstablishment } from '@/context/EstablishmentContext';
export default function ParentHomePage() {
const [children, setChildren] = useState([]);
const { data: session, status } = useSession();
const [userId, setUserId] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [establishments, setEstablishments] = useState([]);
const { selectedEstablishmentId, setSelectedEstablishmentId } = useEstablishment();
const router = useRouter();
@ -24,21 +26,42 @@ export default function ParentHomePage() {
if (!session) {
router.push(`${FE_USERS_LOGIN_URL}`);
}
console.log(session);
const userIdFromSession = session.user.user_id;
setUserId(userIdFromSession);
} else {
const userIdFromSession = session.user.user_id;
setUserId(userIdFromSession);
fetchChildren(userIdFromSession).then(data => {
setChildren(data);
});
}, [userId]);
const userEstablishments = session.user.roles.map(role => ({
id: role.establishment__id,
name: role.establishment__name,
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) {
// Logique pour éditer le dossier de l'élève
logger.debug(`Edit dossier for student id: ${eleveId}`);
router.push(`${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}`);
}
const actionColumns = [
{ name: 'Action', transform: (row) => row.action },
];
@ -106,6 +129,22 @@ export default function ParentHomePage() {
<Users className="h-6 w-6 text-emerald-600" />
Enfants
</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">
<Table
data={children}

View File

@ -13,18 +13,20 @@ import {
FE_PARENTS_HOME_URL
} from '@/utils/Url';
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 logger from '@/utils/logger';
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';
export default function Page() {
const searchParams = useSearchParams();
const [errorMessage, setErrorMessage] = useState("");
const [userFieldError,setUserFieldError] = useState("")
const [passwordFieldError,setPasswordFieldError] = useState("")
const [userFieldError, setUserFieldError] = useState("")
const [passwordFieldError, setPasswordFieldError] = useState("")
const [selectedProfile, setSelectedProfile] = useState(1); // Par défaut, sélectionnez ADMIN
const [isLoading, setIsLoading] = useState(false);
@ -35,52 +37,54 @@ export default function Page() {
return data.errorMessage === ""
}
function handleFormLogin(formData) {
setIsLoading(true);
function handleFormLogin(formData) {
setIsLoading(true);
login({
email: formData.get('login'),
password: formData.get('password'),
}).then(result => {
logger.debug('Sign In Result', result);
setIsLoading(false);
if (result.error) {
setErrorMessage(result.error);
} else {
getSession().then(session => {
if (!session || !session.user) {
throw new Error('Session not found');
}
const user = session.user;
logger.debug('User Session:', user);
login({
email: formData.get('login'),
password: formData.get('password'),
role_type: selectedProfile // Utilisez le profil sélectionné
}).then(result => {
logger.debug('Sign In Result', result);
setIsLoading(false);
if (result.error) {
setErrorMessage(result.error);
} else {
getSession().then(session => {
if (!session || !session.user) {
throw new Error('Session not found');
}
const user = session.user;
logger.debug('User Session:', user);
if (user.establishment_id) {
localStorage.setItem('establishment_id', user.establishment_id);
}
const roles = user.roles.filter(role => role.role_type === selectedProfile);
if (roles.length > 0) {
// const establishment = roles[0].establishment;
// localStorage.setItem('establishment_id', establishment);
if (user.droit === 0) {
// Vue ECOLE
} else if (user.droit === 1) {
// Vue ADMIN
// Redirection en fonction du rôle
if (roles[0].role_type === 1) {
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
} else if (user.droit === 2) {
// Vue PARENT
} else if (roles[0].role_type === 2) {
router.push(FE_PARENTS_HOME_URL);
} else {
// Cas anormal
}
}).catch(error => {
logger.error('Error during session retrieval:', error);
setIsLoading(false);
setErrorMessage('An error occurred during session retrieval.');
});
}
}).catch(error => {
logger.error('Error during sign in:', error);
setIsLoading(false);
setErrorMessage('An error occurred during sign in.');
});
}
} else {
setErrorMessage('No roles found for the specified role type.');
}
}).catch(error => {
logger.error('Error during session retrieval:', error);
setIsLoading(false);
setErrorMessage('An error occurred during session retrieval.');
});
}
}).catch(error => {
logger.error('Error during sign in:', error);
setIsLoading(false);
setErrorMessage('An error occurred during sign in.');
});
}
if (isLoading === true) {
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)); }}>
<DjangoCSRFToken csrfToken={csrfToken} />
<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>
<label className="text-red-500">{errorMessage}</label>

View File

@ -30,6 +30,7 @@ export const login = (data) => {
redirect: false,
email: data.email,
password: data.password,
role_type: data.role_type,
})
};
@ -194,7 +195,11 @@ export const getUser = async () => {
return {
id: session.user.user_id,
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) {

View File

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

View File

@ -100,7 +100,7 @@ export const archiveRegisterForm = (id) => {
}
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(
url,
{
@ -114,9 +114,9 @@ export const fetchStudents = (id=null, establishment) => {
};
export const fetchChildren = (id) =>{
export const fetchChildren = (id, establishment) =>{
const request = new Request(
`${BE_SUBSCRIPTION_CHILDRENS_URL}/${id}`,
`${BE_SUBSCRIPTION_CHILDRENS_URL}/${id}?establishment_id=${establishment}`,
{
method:'GET',
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 />;
}
// Vérifier le rôle de l'utilisateur
if (session && requiredRight && session.user.droit !== requiredRight) {
// Vérifier si l'utilisateur a au moins un rôle correspondant au requiredRight
const hasRequiredRight = session?.user?.roles?.some(role => role.role_type === requiredRight);
if (session && requiredRight && !hasRequiredRight) {
router.push(`${FE_USERS_LOGIN_URL}`);
return null;
}

View File

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

View File

@ -1,6 +1,7 @@
'use client'
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useEstablishment } from '@/context/EstablishmentContext';
const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
<div
@ -14,8 +15,9 @@ const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
</div>
);
function Sidebar({ establishment, currentPage, items }) {
function Sidebar({ establishments, currentPage, items, onCloseMobile, onEstablishmentChange }) {
const router = useRouter();
const { selectedEstablishmentId, setSelectedEstablishmentId, setProfileRole } = useEstablishment();
const [selectedItem, setSelectedItem] = useState(currentPage);
useEffect(() => {
@ -25,31 +27,49 @@ function Sidebar({ establishment, currentPage, items }) {
const handleItemClick = (url) => {
setSelectedItem(url);
router.push(url);
if (onCloseMobile) {
onCloseMobile();
}
};
return <>
{/* Sidebar */}
const handleEstablishmentChange = (e) => {
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="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>
<nav className="space-y-1">
{
items.map((item) => (
<SidebarItem
key={item.id}
icon={item.icon}
text={item.name}
active={item.id === selectedItem}
url={item.url}
onClick={() => handleItemClick(item.url)}
/>
))
}
{items.map((item) => (
<SidebarItem
key={item.id}
icon={item.icon}
text={item.name}
active={item.id === selectedItem}
url={item.url}
onClick={() => handleItemClick(item.url)}
/>
))}
</nav>
</div>
</>
);
}
export default Sidebar;

View File

@ -329,7 +329,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
<TeacherItem key={teacher.id} teacher={teacher} />
);
case 'EMAIL':
return teacher.email;
return teacher.associated_profile_email;
case 'SPECIALITES':
return (
<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',
credentials: {
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) => {
try {
const data = {
email: credentials.email,
password: credentials.password
password: credentials.password,
role_type: credentials.role_type
};
const user = await getJWT(data);
if (user) {
logger.debug("API response:", user);
return user;
}
logger.error('Invalid credentials')
} catch (error) {
logger.error('Invalid credentials')
throw new Error(error.message || 'Invalid credentials');
}
}
@ -88,16 +87,15 @@ const options = {
},
async session({ session, 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,
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;
}

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_PAYMENT_PLANS_URL = `${BASE_URL}/School/paymentPlans`;
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