feat: Ajout de la configuration des tarifs de l'école [#18]

This commit is contained in:
N3WT DE COMPET
2025-01-19 21:00:58 +01:00
committed by Luc SORIGNET
parent 147a70135d
commit 5a0e65bb75
45 changed files with 2089 additions and 376 deletions

View File

@ -3,6 +3,9 @@ from Auth.models import Profile
from django.db.models import JSONField
from django.dispatch import receiver
from django.contrib.postgres.fields import ArrayField
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
LEVEL_CHOICES = [
(1, 'Très Petite Section (TPS)'),
@ -47,7 +50,7 @@ class SchoolClass(models.Model):
number_of_students = models.PositiveIntegerField(blank=True)
teaching_language = models.CharField(max_length=255, blank=True)
school_year = models.CharField(max_length=9, blank=True)
updated_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)
teachers = models.ManyToManyField(Teacher, blank=True)
levels = ArrayField(models.IntegerField(choices=LEVEL_CHOICES), default=list)
type = models.IntegerField(choices=PLANNING_TYPE_CHOICES, default=1)
@ -64,3 +67,56 @@ class Planning(models.Model):
def __str__(self):
return f'Planning for {self.level} of {self.school_class.atmosphere_name}'
class Discount(models.Model):
name = models.CharField(max_length=255, unique=True)
amount = models.DecimalField(max_digits=10, decimal_places=2)
description = models.TextField(blank=True)
def __str__(self):
return self.name
class Fee(models.Model):
name = models.CharField(max_length=255, unique=True)
amount = models.DecimalField(max_digits=10, decimal_places=2)
description = models.TextField(blank=True)
def __str__(self):
return self.name
class TuitionFee(models.Model):
class PaymentOptions(models.IntegerChoices):
SINGLE_PAYMENT = 0, _('Paiement en une seule fois')
FOUR_TIME_PAYMENT = 1, _('Paiement en 4 fois')
TEN_TIME_PAYMENT = 2, _('Paiement en 10 fois')
name = models.CharField(max_length=255, unique=True)
description = models.TextField(blank=True)
base_amount = models.DecimalField(max_digits=10, decimal_places=2)
currency = models.CharField(max_length=3, default='EUR')
discounts = models.ManyToManyField('Discount', blank=True)
validity_start_date = models.DateField()
validity_end_date = models.DateField()
payment_option = models.IntegerField(choices=PaymentOptions, default=PaymentOptions.SINGLE_PAYMENT)
is_active = models.BooleanField(default=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
def clean(self):
if self.validity_end_date <= self.validity_start_date:
raise ValidationError(_('La date de fin de validité doit être après la date de début de validité.'))
def calculate_final_amount(self):
amount = self.base_amount
# Apply fees (supplements and taxes)
# for fee in self.fees.all():
# amount += fee.amount
# Apply discounts
for discount in self.discounts.all():
amount -= discount.amount
return amount

View File

@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, TuitionFee, Fee
from Subscriptions.models import RegistrationForm
from Subscriptions.serializers import StudentSerializer
from Auth.serializers import ProfileSerializer
@ -172,4 +172,57 @@ class SchoolClassSerializer(serializers.ModelSerializer):
utc_time = timezone.localtime(obj.updated_date)
local_tz = pytz.timezone(settings.TZ_APPLI)
local_time = utc_time.astimezone(local_tz)
return local_time.strftime("%d-%m-%Y %H:%M")
return local_time.strftime("%d-%m-%Y %H:%M")
class DiscountSerializer(serializers.ModelSerializer):
class Meta:
model = Discount
fields = '__all__'
class FeeSerializer(serializers.ModelSerializer):
class Meta:
model = Fee
fields = '__all__'
class TuitionFeeSerializer(serializers.ModelSerializer):
discounts = serializers.PrimaryKeyRelatedField(queryset=Discount.objects.all(), many=True)
final_amount = serializers.SerializerMethodField()
class Meta:
model = TuitionFee
fields = '__all__'
def get_final_amount(self, obj):
return obj.calculate_final_amount()
def create(self, validated_data):
discounts_data = validated_data.pop('discounts', [])
# Create the TuitionFee instance
tuition_fee = TuitionFee.objects.create(**validated_data)
# Add discounts if provided
for discount in discounts_data:
tuition_fee.discounts.add(discount)
return tuition_fee
def update(self, instance, validated_data):
discounts_data = validated_data.pop('discounts', [])
# Update the TuitionFee instance
instance.name = validated_data.get('name', instance.name)
instance.description = validated_data.get('description', instance.description)
instance.base_amount = validated_data.get('base_amount', instance.base_amount)
instance.currency = validated_data.get('currency', instance.currency)
instance.validity_start_date = validated_data.get('validity_start_date', instance.validity_start_date)
instance.validity_end_date = validated_data.get('validity_end_date', instance.validity_end_date)
instance.payment_option = validated_data.get('payment_option', instance.payment_option)
instance.is_active = validated_data.get('is_active', instance.is_active)
instance.save()
# Update discounts if provided
if discounts_data:
instance.discounts.set(discounts_data)
return instance

View File

@ -1,6 +1,21 @@
from django.urls import path, re_path
from School.views import TeachersView, TeacherView, SpecialitiesView, SpecialityView, ClassesView, ClasseView, PlanningsView, PlanningView
from School.views import (
TeachersView,
TeacherView,
SpecialitiesView,
SpecialityView,
ClassesView,
ClasseView,
PlanningsView,
PlanningView,
FeesView,
FeeView,
TuitionFeesView,
TuitionFeeView,
DiscountsView,
DiscountView,
)
urlpatterns = [
re_path(r'^specialities$', SpecialitiesView.as_view(), name="specialities"),
@ -18,4 +33,16 @@ urlpatterns = [
re_path(r'^plannings$', PlanningsView.as_view(), name="plannings"),
re_path(r'^planning$', PlanningView.as_view(), name="planning"),
re_path(r'^planning/([0-9]+)$', PlanningView.as_view(), name="planning"),
re_path(r'^fees$', FeesView.as_view(), name="fees"),
re_path(r'^fee$', FeeView.as_view(), name="fee"),
re_path(r'^fee/([0-9]+)$', FeeView.as_view(), name="fee"),
re_path(r'^tuitionFees$', TuitionFeesView.as_view(), name="tuitionFees"),
re_path(r'^tuitionFee$', TuitionFeeView.as_view(), name="tuitionFee"),
re_path(r'^tuitionFee/([0-9]+)$', TuitionFeeView.as_view(), name="tuitionFee"),
re_path(r'^discounts$', DiscountsView.as_view(), name="discounts"),
re_path(r'^discount$', DiscountView.as_view(), name="discount"),
re_path(r'^discount/([0-9]+)$', DiscountView.as_view(), name="discount"),
]

View File

@ -5,46 +5,41 @@ from rest_framework.parsers import JSONParser
from rest_framework.views import APIView
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist
from .models import Teacher, Speciality, SchoolClass, Planning
from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer
from .models import Teacher, Speciality, SchoolClass, Planning, Discount, TuitionFee, Fee
from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer, DiscountSerializer, TuitionFeeSerializer, FeeSerializer
from N3wtSchool import bdd
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class SpecialitiesView(APIView):
def get(self, request):
specialitiesList=bdd.getAllObjects(Speciality)
specialities_serializer=SpecialitySerializer(specialitiesList, many=True)
specialitiesList = getAllObjects(Speciality)
specialities_serializer = SpecialitySerializer(specialitiesList, many=True)
return JsonResponse(specialities_serializer.data, safe=False)
def post(self, request):
specialities_data=JSONParser().parse(request)
specialities_data = JSONParser().parse(request)
all_valid = True
for speciality_data in specialities_data:
speciality_serializer = SpecialitySerializer(data=speciality_data)
if speciality_serializer.is_valid():
speciality_serializer.save()
else:
all_valid = False
break
if all_valid:
specialitiesList = bdd.getAllObjects(Speciality)
specialitiesList = getAllObjects(Speciality)
specialities_serializer = SpecialitySerializer(specialitiesList, many=True)
return JsonResponse(specialities_serializer.data, safe=False)
return JsonResponse(speciality_serializer.errors, safe=False)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class SpecialityView(APIView):
def get (self, request, _id):
speciality = bdd.getObject(_objectName=Speciality, _columnName='id', _value=_id)
speciality_serializer=SpecialitySerializer(speciality)
def get(self, request, _id):
speciality = getObject(_objectName=Speciality, _columnName='id', _value=_id)
speciality_serializer = SpecialitySerializer(speciality)
return JsonResponse(speciality_serializer.data, safe=False)
def post(self, request):
@ -59,7 +54,7 @@ class SpecialityView(APIView):
def put(self, request, _id):
speciality_data=JSONParser().parse(request)
speciality = bdd.getObject(_objectName=Speciality, _columnName='id', _value=_id)
speciality = getObject(_objectName=Speciality, _columnName='id', _value=_id)
speciality_serializer = SpecialitySerializer(speciality, data=speciality_data)
if speciality_serializer.is_valid():
speciality_serializer.save()
@ -68,11 +63,62 @@ class SpecialityView(APIView):
return JsonResponse(speciality_serializer.errors, safe=False)
def delete(self, request, _id):
return bdd.delete_object(Speciality, _id)
return delete_object(Speciality, _id)
# Vues pour les réductions (Discount)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class DiscountsView(APIView):
def get(self, request):
discountsList = Discount.objects.all()
discounts_serializer = DiscountSerializer(discountsList, many=True)
return JsonResponse(discounts_serializer.data, safe=False)
def post(self, request):
discount_data = JSONParser().parse(request)
discount_serializer = DiscountSerializer(data=discount_data)
if discount_serializer.is_valid():
discount_serializer.save()
return JsonResponse(discount_serializer.data, safe=False, status=201)
return JsonResponse(discount_serializer.errors, safe=False, status=400)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class DiscountView(APIView):
def get(self, request, _id):
try:
discount = Discount.objects.get(id=_id)
discount_serializer = DiscountSerializer(discount)
return JsonResponse(discount_serializer.data, safe=False)
except Discount.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=404)
def post(self, request):
discount_data = JSONParser().parse(request)
discount_serializer = DiscountSerializer(data=discount_data)
if discount_serializer.is_valid():
discount_serializer.save()
return JsonResponse(discount_serializer.data, safe=False, status=201)
return JsonResponse(discount_serializer.errors, safe=False, status=400)
def put(self, request, _id):
discount_data = JSONParser().parse(request)
try:
discount = Discount.objects.get(id=_id)
except Discount.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=404)
discount_serializer = DiscountSerializer(discount, data=discount_data, partial=True) # Utilisation de partial=True
if discount_serializer.is_valid():
discount_serializer.save()
return JsonResponse(discount_serializer.data, safe=False)
return JsonResponse(discount_serializer.errors, safe=False, status=400)
def delete(self, request, _id):
return delete_object(Discount, _id)
class TeachersView(APIView):
def get(self, request):
teachersList=bdd.getAllObjects(Teacher)
teachersList=getAllObjects(Teacher)
teachers_serializer=TeacherSerializer(teachersList, many=True)
return JsonResponse(teachers_serializer.data, safe=False)
@ -81,7 +127,7 @@ class TeachersView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch')
class TeacherView(APIView):
def get (self, request, _id):
teacher = bdd.getObject(_objectName=Teacher, _columnName='id', _value=_id)
teacher = getObject(_objectName=Teacher, _columnName='id', _value=_id)
teacher_serializer=TeacherSerializer(teacher)
return JsonResponse(teacher_serializer.data, safe=False)
@ -99,7 +145,7 @@ class TeacherView(APIView):
def put(self, request, _id):
teacher_data=JSONParser().parse(request)
teacher = bdd.getObject(_objectName=Teacher, _columnName='id', _value=_id)
teacher = getObject(_objectName=Teacher, _columnName='id', _value=_id)
teacher_serializer = TeacherSerializer(teacher, data=teacher_data)
if teacher_serializer.is_valid():
teacher_serializer.save()
@ -108,13 +154,13 @@ class TeacherView(APIView):
return JsonResponse(teacher_serializer.errors, safe=False)
def delete(self, request, _id):
return bdd.delete_object(Teacher, _id, related_field='associated_profile')
return delete_object(Teacher, _id, related_field='associated_profile')
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class ClassesView(APIView):
def get(self, request):
classesList=bdd.getAllObjects(SchoolClass)
classesList=getAllObjects(SchoolClass)
classes_serializer=SchoolClassSerializer(classesList, many=True)
return JsonResponse(classes_serializer.data, safe=False)
@ -131,7 +177,7 @@ class ClassesView(APIView):
break
if all_valid:
classesList = bdd.getAllObjects(SchoolClass)
classesList = getAllObjects(SchoolClass)
classes_serializer = SchoolClassSerializer(classesList, many=True)
return JsonResponse(classes_serializer.data, safe=False)
@ -142,7 +188,7 @@ class ClassesView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch')
class ClasseView(APIView):
def get (self, request, _id):
schoolClass = bdd.getObject(_objectName=SchoolClass, _columnName='id', _value=_id)
schoolClass = getObject(_objectName=SchoolClass, _columnName='id', _value=_id)
classe_serializer=SchoolClassSerializer(schoolClass)
return JsonResponse(classe_serializer.data, safe=False)
@ -159,7 +205,7 @@ class ClasseView(APIView):
def put(self, request, _id):
classe_data=JSONParser().parse(request)
schoolClass = bdd.getObject(_objectName=SchoolClass, _columnName='id', _value=_id)
schoolClass = getObject(_objectName=SchoolClass, _columnName='id', _value=_id)
classe_serializer = SchoolClassSerializer(schoolClass, data=classe_data)
if classe_serializer.is_valid():
classe_serializer.save()
@ -168,14 +214,14 @@ class ClasseView(APIView):
return JsonResponse(classe_serializer.errors, safe=False)
def delete(self, request, _id):
return bdd.delete_object(SchoolClass, _id)
return delete_object(SchoolClass, _id)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class PlanningsView(APIView):
def get(self, request):
schedulesList=bdd.getAllObjects(Planning)
schedulesList=getAllObjects(Planning)
schedules_serializer=PlanningSerializer(schedulesList, many=True)
return JsonResponse(schedules_serializer.data, safe=False)
@ -183,7 +229,7 @@ class PlanningsView(APIView):
@method_decorator(ensure_csrf_cookie, name='dispatch')
class PlanningView(APIView):
def get (self, request, _id):
planning = bdd.getObject(_objectName=Planning, _columnName='classe__id', _value=_id)
planning = getObject(_objectName=Planning, _columnName='classe__id', _value=_id)
planning_serializer=PlanningSerializer(planning)
return JsonResponse(planning_serializer.data, safe=False)
@ -215,3 +261,98 @@ class PlanningView(APIView):
return JsonResponse(planning_serializer.data, safe=False)
return JsonResponse(planning_serializer.errors, safe=False)
# Vues pour les frais (Fee)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class FeesView(APIView):
def get(self, request):
feesList = Fee.objects.all()
fees_serializer = FeeSerializer(feesList, many=True)
return JsonResponse(fees_serializer.data, safe=False)
def post(self, request):
fee_data = JSONParser().parse(request)
fee_serializer = FeeSerializer(data=fee_data)
if fee_serializer.is_valid():
fee_serializer.save()
return JsonResponse(fee_serializer.data, safe=False, status=201)
return JsonResponse(fee_serializer.errors, safe=False, status=400)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class FeeView(APIView):
def get(self, request, _id):
try:
fee = Fee.objects.get(id=_id)
fee_serializer = FeeSerializer(fee)
return JsonResponse(fee_serializer.data, safe=False)
except Fee.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=404)
def post(self, request):
fee_data = JSONParser().parse(request)
fee_serializer = FeeSerializer(data=fee_data)
if fee_serializer.is_valid():
fee_serializer.save()
return JsonResponse(fee_serializer.data, safe=False, status=201)
return JsonResponse(fee_serializer.errors, safe=False, status=400)
def put(self, request, _id):
fee_data = JSONParser().parse(request)
try:
fee = Fee.objects.get(id=_id)
except Fee.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=404)
fee_serializer = FeeSerializer(fee, data=fee_data, partial=True) # Utilisation de partial=True
if fee_serializer.is_valid():
fee_serializer.save()
return JsonResponse(fee_serializer.data, safe=False)
return JsonResponse(fee_serializer.errors, safe=False, status=400)
def delete(self, request, _id):
return delete_object(Fee, _id)
# Vues pour les frais de scolarité (TuitionFee)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class TuitionFeesView(APIView):
def get(self, request):
tuitionFeesList = TuitionFee.objects.all()
tuitionFees_serializer = TuitionFeeSerializer(tuitionFeesList, many=True)
return JsonResponse(tuitionFees_serializer.data, safe=False)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class TuitionFeeView(APIView):
def get(self, request, _id):
try:
tuitionFee = TuitionFee.objects.get(id=_id)
tuitionFee_serializer = TuitionFeeSerializer(tuitionFee)
return JsonResponse(tuitionFee_serializer.data, safe=False)
except TuitionFee.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=404)
def post(self, request):
tuitionFee_data = JSONParser().parse(request)
tuitionFee_serializer = TuitionFeeSerializer(data=tuitionFee_data)
if tuitionFee_serializer.is_valid():
tuitionFee_serializer.save()
return JsonResponse(tuitionFee_serializer.data, safe=False, status=201)
return JsonResponse(tuitionFee_serializer.errors, safe=False, status=400)
def put(self, request, _id):
tuitionFee_data = JSONParser().parse(request)
try:
tuitionFee = TuitionFee.objects.get(id=_id)
except TuitionFee.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=404)
tuitionFee_serializer = TuitionFeeSerializer(tuitionFee, data=tuitionFee_data, partial=True) # Utilisation de partial=True
if tuitionFee_serializer.is_valid():
tuitionFee_serializer.save()
return JsonResponse(tuitionFee_serializer.data, safe=False)
return JsonResponse(tuitionFee_serializer.errors, safe=False, status=400)
def delete(self, request, _id):
return delete_object(TuitionFee, _id)