From 5462306a6020493cf747ea3bb8edb3240c36286f Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Tue, 21 Jan 2025 20:39:36 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Harmonisation=20des=20fees=20/=20ajout?= =?UTF-8?q?=20de=20type=20de=20r=C3=A9duction=20/=20mise=20=C3=A0=20jour?= =?UTF-8?q?=20du=20calcul=20[#18]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Back-End/School/models.py | 35 ++++----- Back-End/School/serializers.py | 39 +--------- Back-End/School/urls.py | 10 +-- Back-End/School/views.py | 69 ++++-------------- .../src/app/[locale]/admin/structure/page.js | 33 +++++---- Front-End/src/app/lib/schoolAction.js | 11 ++- .../Configuration/DiscountsSection.js | 71 +++++++++++++++---- .../Structure/Configuration/FeesManagement.js | 31 ++++---- ...sSection.js => RegistrationFeesSection.js} | 37 ++++++---- .../Configuration/TuitionFeesSection.js | 25 +++++-- Front-End/src/utils/Url.js | 2 - 11 files changed, 169 insertions(+), 194 deletions(-) rename Front-End/src/components/Structure/Configuration/{FeesSection.js => RegistrationFeesSection.js} (88%) diff --git a/Back-End/School/models.py b/Back-End/School/models.py index 25967b2..937b1e5 100644 --- a/Back-End/School/models.py +++ b/Back-End/School/models.py @@ -73,40 +73,33 @@ class PaymentOptions(models.IntegerChoices): FOUR_TIME_PAYMENT = 1, _('Paiement en 4 fois') TEN_TIME_PAYMENT = 2, _('Paiement en 10 fois') +class DiscountType(models.IntegerChoices): + CURRENCY = 0, 'Currency' + PERCENT = 1, 'Percent' + +class FeeType(models.IntegerChoices): + REGISTRATION_FEE = 0, 'Registration Fee' + TUITION_FEE = 1, 'Tuition Fee' + class Discount(models.Model): name = models.CharField(max_length=255, unique=True) - amount = models.DecimalField(max_digits=10, decimal_places=2) + amount = models.DecimalField(max_digits=10, decimal_places=2, default=0) description = models.TextField(blank=True) + discount_type = models.IntegerField(choices=DiscountType.choices, default=DiscountType.CURRENCY) def __str__(self): return self.name class Fee(models.Model): name = models.CharField(max_length=255, unique=True) - description = models.TextField(blank=True) base_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0) - currency = models.CharField(max_length=3, default='EUR') + description = models.TextField(blank=True) + payment_option = models.IntegerField(choices=PaymentOptions.choices, default=PaymentOptions.SINGLE_PAYMENT) discounts = models.ManyToManyField('Discount', blank=True) - payment_option = models.IntegerField(choices=PaymentOptions, default=PaymentOptions.SINGLE_PAYMENT) is_active = models.BooleanField(default=True) updated_at = models.DateTimeField(auto_now=True) + currency = models.CharField(max_length=3, default='EUR') + type = models.IntegerField(choices=FeeType.choices, default=FeeType.REGISTRATION_FEE) def __str__(self): return self.name - -class TuitionFee(models.Model): - name = models.CharField(max_length=255, unique=True) - description = models.TextField(blank=True) - base_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0) - currency = models.CharField(max_length=3, default='EUR') - discounts = models.ManyToManyField('Discount', blank=True) - 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é.')) diff --git a/Back-End/School/serializers.py b/Back-End/School/serializers.py index 08b5588..0aaccc1 100644 --- a/Back-End/School/serializers.py +++ b/Back-End/School/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, TuitionFee, Fee +from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee from Subscriptions.models import RegistrationForm from Subscriptions.serializers import StudentSerializer from Auth.serializers import ProfileSerializer @@ -208,42 +208,7 @@ class FeeSerializer(serializers.ModelSerializer): instance.payment_option = validated_data.get('payment_option', instance.payment_option) instance.is_active = validated_data.get('is_active', instance.is_active) instance.updated_at = validated_data.get('updated_at', instance.updated_at) - instance.save() - - # Update discounts if provided - instance.discounts.set(discounts_data) - - return instance - -class TuitionFeeSerializer(serializers.ModelSerializer): - discounts = serializers.PrimaryKeyRelatedField(queryset=Discount.objects.all(), many=True) - - class Meta: - model = TuitionFee - fields = '__all__' - - 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 - tuition_fee.discounts.set(discounts_data) - - 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.payment_option = validated_data.get('payment_option', instance.payment_option) - instance.is_active = validated_data.get('is_active', instance.is_active) - instance.updated_at = validated_data.get('updated_at', instance.updated_at) + instance.type = validated_data.get('type', instance.type) instance.save() # Update discounts if provided diff --git a/Back-End/School/urls.py b/Back-End/School/urls.py index 4ccae46..cb04f28 100644 --- a/Back-End/School/urls.py +++ b/Back-End/School/urls.py @@ -11,8 +11,6 @@ from School.views import ( PlanningView, FeesView, FeeView, - TuitionFeesView, - TuitionFeeView, DiscountsView, DiscountView, ) @@ -33,15 +31,11 @@ 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'^fees/(?P<_filter>[a-zA-z]+)$', 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"), diff --git a/Back-End/School/views.py b/Back-End/School/views.py index e06e944..35b648a 100644 --- a/Back-End/School/views.py +++ b/Back-End/School/views.py @@ -5,8 +5,8 @@ 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, Discount, TuitionFee, Fee -from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer, DiscountSerializer, TuitionFeeSerializer, FeeSerializer +from .models import Teacher, Speciality, SchoolClass, Planning, Discount, Fee +from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer, DiscountSerializer, FeeSerializer from N3wtSchool import bdd from N3wtSchool.bdd import delete_object, getAllObjects, getObject @@ -267,18 +267,16 @@ class PlanningView(APIView): @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 get(self, request, _filter, *args, **kwargs): - 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) + if _filter not in ['registration', 'tuition']: + return JsonResponse({"error": "Invalid type parameter. Must be 'registration' or 'tuition'."}, safe=False, status=400) + + fee_type_value = 0 if _filter == 'registration' else 1 + fees = Fee.objects.filter(type=fee_type_value) + fee_serializer = FeeSerializer(fees, many=True) + + return JsonResponse(fee_serializer.data, safe=False, status=200) @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') @@ -312,47 +310,4 @@ class FeeView(APIView): 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) \ No newline at end of file + return delete_object(Fee, _id) \ No newline at end of file diff --git a/Front-End/src/app/[locale]/admin/structure/page.js b/Front-End/src/app/[locale]/admin/structure/page.js index 4300560..0d893e6 100644 --- a/Front-End/src/app/[locale]/admin/structure/page.js +++ b/Front-End/src/app/[locale]/admin/structure/page.js @@ -1,20 +1,19 @@ 'use client' import React, { useState, useEffect } from 'react'; -import { School, Calendar, DollarSign } from 'lucide-react'; // Import de l'icône DollarSign import StructureManagement from '@/components/Structure/Configuration/StructureManagement'; import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement'; import FeesManagement from '@/components/Structure/Configuration/FeesManagement'; import DjangoCSRFToken from '@/components/DjangoCSRFToken'; import useCsrfToken from '@/hooks/useCsrfToken'; import { ClassesProvider } from '@/context/ClassesContext'; -import { fetchSpecialities, fetchTeachers, fetchClasses, fetchSchedules, fetchDiscounts, fetchFees, fetchTuitionFees } from '@/app/lib/schoolAction'; +import { fetchSpecialities, fetchTeachers, fetchClasses, fetchSchedules, fetchDiscounts, fetchRegistrationFees, fetchTuitionFees } from '@/app/lib/schoolAction'; import SidebarTabs from '@/components/SidebarTabs'; export default function Page() { const [specialities, setSpecialities] = useState([]); const [classes, setClasses] = useState([]); const [teachers, setTeachers] = useState([]); - const [fees, setFees] = useState([]); + const [registrationFees, setRegistrationFees] = useState([]); const [discounts, setDiscounts] = useState([]); const [tuitionFees, setTuitionFees] = useState([]); @@ -33,13 +32,13 @@ export default function Page() { // Fetch data for schedules handleSchedules(); - // Fetch data for fees - handleFees(); - // Fetch data for discounts handleDiscounts(); + + // Fetch data for registration fees + handleRegistrationFees(); - // Fetch data for TuitionFee + // Fetch data for tuition fees handleTuitionFees(); }, []); @@ -75,14 +74,6 @@ export default function Page() { .catch(error => console.error('Error fetching schedules:', error)); }; - const handleFees = () => { - fetchFees() - .then(data => { - setFees(data); - }) - .catch(error => console.error('Error fetching fees:', error)); - }; - const handleDiscounts = () => { fetchDiscounts() .then(data => { @@ -91,6 +82,14 @@ export default function Page() { .catch(error => console.error('Error fetching discounts:', error)); }; + const handleRegistrationFees = () => { + fetchRegistrationFees() + .then(data => { + setRegistrationFees(data); + }) + .catch(error => console.error('Error fetching registration fees:', error)); + }; + const handleTuitionFees = () => { fetchTuitionFees() .then(data => { @@ -237,10 +236,10 @@ export default function Page() { label: 'Tarifications', content: ( { @@ -46,12 +45,12 @@ export const fetchDiscounts = () => { .then(requestResponseHandler) }; -export const fetchFees = () => { - return fetch(`${BE_SCHOOL_FEES_URL}`) +export const fetchRegistrationFees = () => { + return fetch(`${BE_SCHOOL_FEES_URL}/registration`) .then(requestResponseHandler) }; export const fetchTuitionFees = () => { - return fetch(`${BE_SCHOOL_TUITION_FEES_URL}`) + return fetch(`${BE_SCHOOL_FEES_URL}/tuition`) .then(requestResponseHandler) -}; +}; \ No newline at end of file diff --git a/Front-End/src/components/Structure/Configuration/DiscountsSection.js b/Front-End/src/components/Structure/Configuration/DiscountsSection.js index 36d2271..f6c48d6 100644 --- a/Front-End/src/components/Structure/Configuration/DiscountsSection.js +++ b/Front-End/src/components/Structure/Configuration/DiscountsSection.js @@ -1,10 +1,10 @@ import React, { useState } from 'react'; -import { Plus, Trash, Edit3, Check, X } from 'lucide-react'; +import { Plus, Trash, Edit3, Check, X, Percent, EuroIcon } from 'lucide-react'; import Table from '@/components/Table'; import InputTextIcon from '@/components/InputTextIcon'; import Popup from '@/components/Popup'; -const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete }) => { +const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, handleDelete, errors }) => { const [editingDiscount, setEditingDiscount] = useState(null); const [newDiscount, setNewDiscount] = useState(null); const [formData, setFormData] = useState({}); @@ -13,17 +13,24 @@ const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete }) const [popupMessage, setPopupMessage] = useState(""); const handleAddDiscount = () => { - setNewDiscount({ id: Date.now(), name: '', amount: '', description: '' }); + setNewDiscount({ id: Date.now(), name: '', amount: '', description: '', discountType: 'amount' }); }; const handleRemoveDiscount = (id) => { handleDelete(id) + .then(() => { + setDiscounts(prevDiscounts => prevDiscounts.filter(discount => discount.id !== id)); + }) + .catch(error => { + console.error(error); + }); }; const handleSaveNewDiscount = () => { if (newDiscount.name && newDiscount.amount) { handleCreate(newDiscount) - .then(() => { + .then((createdDiscount) => { + setDiscounts([createdDiscount, ...discounts]); setNewDiscount(null); setLocalErrors({}); }) @@ -60,9 +67,29 @@ const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete }) } }; + const handleToggleDiscountType = (id, newType) => { + const discount = discounts.find(discount => discount.id === id); + if (!discount) return; + + const updatedData = { + ...discount, + discount_type: newType + }; + + handleEdit(id, updatedData) + .then(() => { + setDiscounts(prevDiscounts => prevDiscounts.map(discount => discount.id === id ? { ...discount, discount_type: updatedData.discount_type } : discount)); + }) + .catch(error => { + console.error(error); + }); + }; + const handleChange = (e) => { const { name, value } = e.target; - if (editingDiscount) { + if (name === 'discountType') { + setDiscountType(value); + } else if (editingDiscount) { setFormData((prevData) => ({ ...prevData, [name]: value, @@ -97,13 +124,13 @@ const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete }) switch (column) { case 'LIBELLE': return renderInputField('name', currentData.name, handleChange, 'Libellé de la réduction'); - case 'MONTANT': - return renderInputField('amount', currentData.amount, handleChange, 'Montant'); + case 'VALEUR': + return renderInputField('amount', currentData.amount, handleChange, discount.discount_type === 0 ? 'Montant' : 'Pourcentage'); case 'DESCRIPTION': return renderInputField('description', currentData.description, handleChange, 'Description'); case 'ACTIONS': return ( -
+
+ +
+ ); + case 'DESCRIPTION': return discount.description; case 'ACTIONS': return ( @@ -168,7 +214,8 @@ const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete }) data={newDiscount ? [newDiscount, ...discounts] : discounts} columns={[ { name: 'LIBELLE', label: 'Libellé' }, - { name: 'MONTANT', label: 'Montant' }, + { name: 'VALEUR', label: 'Valeur' }, + { name: 'TYPE DE REMISE', label: 'Type de remise' }, { name: 'DESCRIPTION', label: 'Description' }, { name: 'ACTIONS', label: 'Actions' } ]} diff --git a/Front-End/src/components/Structure/Configuration/FeesManagement.js b/Front-End/src/components/Structure/Configuration/FeesManagement.js index 7c733bc..0dbbd87 100644 --- a/Front-End/src/components/Structure/Configuration/FeesManagement.js +++ b/Front-End/src/components/Structure/Configuration/FeesManagement.js @@ -1,29 +1,30 @@ import React, { useState } from 'react'; -import FeesSection from './FeesSection'; -import DiscountsSection from './DiscountsSection'; -import TuitionFeesSection from './TuitionFeesSection'; -import { BE_SCHOOL_FEE_URL, BE_SCHOOL_DISCOUNT_URL, BE_SCHOOL_TUITION_FEE_URL } from '@/utils/Url'; +import RegistrationFeesSection from '@/components/Structure/Configuration/RegistrationFeesSection'; +import DiscountsSection from '@/components/Structure/Configuration/DiscountsSection'; +import TuitionFeesSection from '@/components/Structure/Configuration/TuitionFeesSection'; +import { BE_SCHOOL_FEE_URL, BE_SCHOOL_DISCOUNT_URL } from '@/utils/Url'; -const FeesManagement = ({ fees, setFees, discounts, setDiscounts, tuitionFees, setTuitionFees, handleCreate, handleEdit, handleDelete }) => { +const FeesManagement = ({ discounts, setDiscounts, registrationFees, setRegistrationFees, tuitionFees, setTuitionFees, handleCreate, handleEdit, handleDelete }) => { return (
handleCreate(`${BE_SCHOOL_DISCOUNT_URL}`, newData, setDiscounts)} handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_DISCOUNT_URL}`, id, updatedData, setDiscounts)} handleDelete={(id) => handleDelete(`${BE_SCHOOL_DISCOUNT_URL}`, id, setDiscounts)} />
- handleCreate(`${BE_SCHOOL_FEE_URL}`, newData, setFees)} - handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_FEE_URL}`, id, updatedData, setFees)} - handleDelete={(id) => handleDelete(`${BE_SCHOOL_FEE_URL}`, id, setFees)} + handleCreate={(newData) => handleCreate(`${BE_SCHOOL_FEE_URL}`, newData, setRegistrationFees)} + handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_FEE_URL}`, id, updatedData, setRegistrationFees)} + handleDelete={(id) => handleDelete(`${BE_SCHOOL_FEE_URL}`, id, setRegistrationFees)} />
@@ -31,10 +32,10 @@ const FeesManagement = ({ fees, setFees, discounts, setDiscounts, tuitionFees, s tuitionFees={tuitionFees} setTuitionFees={setTuitionFees} discounts={discounts} - fees={fees} - handleCreate={(newData) => handleCreate(`${BE_SCHOOL_TUITION_FEE_URL}`, newData, setTuitionFees)} - handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_TUITION_FEE_URL}`, id, updatedData, setTuitionFees)} - handleDelete={(id) => handleDelete(`${BE_SCHOOL_TUITION_FEE_URL}`, id, setTuitionFees)} + registrationFees={registrationFees} + handleCreate={(newData) => handleCreate(`${BE_SCHOOL_FEE_URL}`, newData, setTuitionFees)} + handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_FEE_URL}`, id, updatedData, setTuitionFees)} + handleDelete={(id) => handleDelete(`${BE_SCHOOL_FEE_URL}`, id, setTuitionFees)} />
diff --git a/Front-End/src/components/Structure/Configuration/FeesSection.js b/Front-End/src/components/Structure/Configuration/RegistrationFeesSection.js similarity index 88% rename from Front-End/src/components/Structure/Configuration/FeesSection.js rename to Front-End/src/components/Structure/Configuration/RegistrationFeesSection.js index 518c48e..f8a9886 100644 --- a/Front-End/src/components/Structure/Configuration/FeesSection.js +++ b/Front-End/src/components/Structure/Configuration/RegistrationFeesSection.js @@ -1,11 +1,11 @@ import React, { useState } from 'react'; -import { Plus, Trash, Edit3, Check, X } from 'lucide-react'; +import { Plus, Trash, Edit3, Check, X, EyeOff, Eye } from 'lucide-react'; import Table from '@/components/Table'; import InputTextIcon from '@/components/InputTextIcon'; import Popup from '@/components/Popup'; import SelectChoice from '@/components/SelectChoice'; -const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handleDelete }) => { +const RegistrationFeesSection = ({ registrationFees, setRegistrationFees, discounts, handleCreate, handleEdit, handleDelete }) => { const [editingFee, setEditingFee] = useState(null); const [newFee, setNewFee] = useState(null); const [formData, setFormData] = useState({}); @@ -29,8 +29,14 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl const handleSaveNewFee = () => { if (newFee.name && newFee.base_amount) { - handleCreate(newFee) - .then(() => { + const feeData = { + ...newFee, + type: 0 + }; + + handleCreate(feeData) + .then((createdFee) => { + setRegistrationFees([createdFee, ...registrationFees]); setNewFee(null); setLocalErrors({}); }) @@ -68,7 +74,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl }; const handleToggleActive = (id, isActive) => { - const fee = fees.find(fee => fee.id === id); + const fee = registrationFees.find(fee => fee.id === id); if (!fee) return; const updatedData = { @@ -78,7 +84,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl handleEdit(id, updatedData) .then(() => { - setFees(prevFees => prevFees.map(fee => fee.id === id ? { ...fee, is_active: !isActive } : fee)); + setRegistrationFees(prevFees => prevFees.map(fee => fee.id === id ? { ...fee, is_active: !isActive } : fee)); }) .catch(error => { console.error(error); @@ -107,11 +113,18 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl const calculateFinalAmount = (baseAmount, discountIds) => { const totalDiscounts = discountIds.reduce((sum, discountId) => { const discount = discounts.find(d => d.id === discountId); - return discount ? sum + parseFloat(discount.amount) : sum; + if (discount) { + if (discount.discount_type === 0) { // Currency + return sum + parseFloat(discount.amount); + } else if (discount.discount_type === 1) { // Percent + return sum + (parseFloat(baseAmount) * parseFloat(discount.amount) / 100); + } + } + return sum; }, 0); - + const finalAmount = parseFloat(baseAmount) - totalDiscounts; - + return finalAmount.toFixed(2); }; @@ -220,7 +233,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl onClick={() => handleToggleActive(fee.id, fee.is_active)} className={`text-${fee.is_active ? 'gray' : 'green'}-500 hover:text-${fee.is_active ? 'gray' : 'green'}-700`} > - {fee.is_active ? : } + {fee.is_active ? : }
); @@ -240,7 +253,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl { +const TuitionFeesSection = ({ tuitionFees, setTuitionFees, discounts, registrationFees, handleCreate, handleEdit, handleDelete }) => { const [editingTuitionFee, setEditingTuitionFee] = useState(null); const [newTuitionFee, setNewTuitionFee] = useState(null); const [formData, setFormData] = useState({}); @@ -39,7 +39,11 @@ const TuitionFeesSection = ({ tuitionFees, setTuitionFees, discounts, fees, hand newTuitionFee.base_amount && newTuitionFee.payment_option >= 0 ) { - handleCreate(newTuitionFee) + const tuitionFeeData = { + ...newTuitionFee, + type: 1 + }; + handleCreate(tuitionFeeData) .then((createdTuitionFee) => { setTuitionFees([createdTuitionFee, ...tuitionFees]); setNewTuitionFee(null); @@ -151,11 +155,18 @@ const TuitionFeesSection = ({ tuitionFees, setTuitionFees, discounts, fees, hand const calculateFinalAmount = (baseAmount, discountIds) => { const totalDiscounts = discountIds.reduce((sum, discountId) => { const discount = discounts.find(d => d.id === discountId); - return discount ? sum + parseFloat(discount.amount) : sum; + if (discount) { + if (discount.discount_type === 0) { // Currency + return sum + parseFloat(discount.amount); + } else if (discount.discount_type === 1) { // Percent + return sum + (parseFloat(baseAmount) * parseFloat(discount.amount) / 100); + } + } + return sum; }, 0); - + const finalAmount = parseFloat(baseAmount) - totalDiscounts; - + return finalAmount.toFixed(2); }; @@ -238,7 +249,7 @@ const TuitionFeesSection = ({ tuitionFees, setTuitionFees, discounts, fees, hand onClick={() => handleToggleActive(tuitionFee.id, tuitionFee.is_active)} className={`text-${tuitionFee.is_active ? 'gray' : 'green'}-500 hover:text-${tuitionFee.is_active ? 'gray' : 'green'}-700`} > - {tuitionFee.is_active ? : } + {tuitionFee.is_active ? : } ); diff --git a/Front-End/src/utils/Url.js b/Front-End/src/utils/Url.js index 82c3e20..4fbad08 100644 --- a/Front-End/src/utils/Url.js +++ b/Front-End/src/utils/Url.js @@ -39,8 +39,6 @@ export const BE_SCHOOL_FEE_URL = `${BASE_URL}/School/fee`; export const BE_SCHOOL_FEES_URL = `${BASE_URL}/School/fees`; export const BE_SCHOOL_DISCOUNT_URL = `${BASE_URL}/School/discount`; export const BE_SCHOOL_DISCOUNTS_URL = `${BASE_URL}/School/discounts`; -export const BE_SCHOOL_TUITION_FEE_URL = `${BASE_URL}/School/tuitionFee`; -export const BE_SCHOOL_TUITION_FEES_URL = `${BASE_URL}/School/tuitionFees`; // GESTION MESSAGERIE export const BE_GESTIONMESSAGERIE_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messagerie`