feat: Ajout des frais d'inscription lors de la création d'un RF [#18]

This commit is contained in:
N3WT DE COMPET
2025-01-25 16:40:08 +01:00
parent 799e1c6717
commit ece23deb19
12 changed files with 333 additions and 136 deletions

View File

@ -96,7 +96,6 @@ class Fee(models.Model):
name = models.CharField(max_length=255, unique=True) name = models.CharField(max_length=255, unique=True)
base_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0) base_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
description = models.TextField(blank=True) description = models.TextField(blank=True)
discounts = models.ManyToManyField('Discount', blank=True)
is_active = models.BooleanField(default=True) is_active = models.BooleanField(default=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
type = models.IntegerField(choices=FeeType.choices, default=FeeType.REGISTRATION_FEE) type = models.IntegerField(choices=FeeType.choices, default=FeeType.REGISTRATION_FEE)

View File

@ -1,8 +1,5 @@
from rest_framework import serializers from rest_framework import serializers
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, 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
from Auth.models import Profile from Auth.models import Profile
from N3wtSchool import settings, bdd from N3wtSchool import settings, bdd
from django.utils import timezone from django.utils import timezone
@ -187,41 +184,12 @@ class DiscountSerializer(serializers.ModelSerializer):
return local_time.strftime("%d-%m-%Y %H:%M") return local_time.strftime("%d-%m-%Y %H:%M")
class FeeSerializer(serializers.ModelSerializer): class FeeSerializer(serializers.ModelSerializer):
discounts = serializers.PrimaryKeyRelatedField(queryset=Discount.objects.all(), many=True)
updated_at_formatted = serializers.SerializerMethodField() updated_at_formatted = serializers.SerializerMethodField()
class Meta: class Meta:
model = Fee model = Fee
fields = '__all__' fields = '__all__'
def create(self, validated_data):
discounts_data = validated_data.pop('discounts', [])
# Create the Fee instance
fee = Fee.objects.create(**validated_data)
# Add discounts if provided
fee.discounts.set(discounts_data)
return fee
def update(self, instance, validated_data):
discounts_data = validated_data.pop('discounts', [])
# Update the Fee 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.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
instance.discounts.set(discounts_data)
return instance
def get_updated_at_formatted(self, obj): def get_updated_at_formatted(self, obj):
utc_time = timezone.localtime(obj.updated_at) utc_time = timezone.localtime(obj.updated_at)
local_tz = pytz.timezone(settings.TZ_APPLI) local_tz = pytz.timezone(settings.TZ_APPLI)

View File

@ -4,7 +4,7 @@ from django.conf import settings
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from Auth.models import Profile from Auth.models import Profile
from School.models import SchoolClass from School.models import SchoolClass, Fee, Discount
from datetime import datetime from datetime import datetime
@ -204,6 +204,12 @@ class RegistrationForm(models.Model):
registration_file = models.FileField(upload_to=settings.DOCUMENT_DIR, default="", blank=True) registration_file = models.FileField(upload_to=settings.DOCUMENT_DIR, default="", blank=True)
associated_rf = models.CharField(max_length=200, default="", blank=True) associated_rf = models.CharField(max_length=200, default="", blank=True)
# Many-to-Many Relationship
fees = models.ManyToManyField(Fee, blank=True, related_name='register_forms')
# Many-to-Many Relationship
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
def __str__(self): def __str__(self):
return "RF_" + self.student.last_name + "_" + self.student.first_name return "RF_" + self.student.last_name + "_" + self.student.first_name

View File

@ -1,6 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from .models import RegistrationFileTemplate, RegistrationFile, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationFee from .models import RegistrationFileTemplate, RegistrationFile, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationFee
from School.models import SchoolClass from School.models import SchoolClass, Fee, Discount
from School.serializers import FeeSerializer, DiscountSerializer
from Auth.models import Profile from Auth.models import Profile
from Auth.serializers import ProfileSerializer from Auth.serializers import ProfileSerializer
from GestionMessagerie.models import Messagerie from GestionMessagerie.models import Messagerie
@ -133,6 +134,9 @@ class RegistrationFormSerializer(serializers.ModelSerializer):
status_label = serializers.SerializerMethodField() status_label = serializers.SerializerMethodField()
formatted_last_update = serializers.SerializerMethodField() formatted_last_update = serializers.SerializerMethodField()
registration_files = RegistrationFileSerializer(many=True, required=False) registration_files = RegistrationFileSerializer(many=True, required=False)
fees = serializers.PrimaryKeyRelatedField(queryset=Fee.objects.all(), many=True, required=False)
discounts = serializers.PrimaryKeyRelatedField(queryset=Discount.objects.all(), many=True, required=False)
class Meta: class Meta:
model = RegistrationForm model = RegistrationForm
fields = '__all__' fields = '__all__'
@ -140,11 +144,19 @@ class RegistrationFormSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
student_data = validated_data.pop('student') student_data = validated_data.pop('student')
student = StudentSerializer.create(StudentSerializer(), student_data) student = StudentSerializer.create(StudentSerializer(), student_data)
fees_data = validated_data.pop('fees', [])
discounts_data = validated_data.pop('discounts', [])
registrationForm = RegistrationForm.objects.create(student=student, **validated_data) registrationForm = RegistrationForm.objects.create(student=student, **validated_data)
# Associer les IDs des objets Fee et Discount au RegistrationForm
registrationForm.fees.set([fee.id for fee in fees_data])
registrationForm.discounts.set([discount.id for discount in discounts_data])
return registrationForm return registrationForm
def update(self, instance, validated_data): def update(self, instance, validated_data):
student_data = validated_data.pop('student', None) student_data = validated_data.pop('student', None)
fees_data = validated_data.pop('fees', [])
discounts_data = validated_data.pop('discounts', [])
if student_data: if student_data:
student = instance.student student = instance.student
StudentSerializer.update(StudentSerializer(), student, student_data) StudentSerializer.update(StudentSerializer(), student, student_data)
@ -156,6 +168,10 @@ class RegistrationFormSerializer(serializers.ModelSerializer):
pass pass
instance.save() instance.save()
# Associer les IDs des objets Fee et Discount au RegistrationForm
instance.fees.set([fee.id for fee in fees_data])
instance.discounts.set([discount.id for discount in discounts_data])
return instance return instance
def get_status_label(self, obj): def get_status_label(self, obj):

View File

@ -2,7 +2,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import StructureManagement from '@/components/Structure/Configuration/StructureManagement'; import StructureManagement from '@/components/Structure/Configuration/StructureManagement';
import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement'; import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement';
import FeesManagement from '@/components/Structure/Configuration/FeesManagement'; import FeesManagement from '@/components/Structure/Tarification/FeesManagement';
import DjangoCSRFToken from '@/components/DjangoCSRFToken'; import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import useCsrfToken from '@/hooks/useCsrfToken'; import useCsrfToken from '@/hooks/useCsrfToken';
import { ClassesProvider } from '@/context/ClassesContext'; import { ClassesProvider } from '@/context/ClassesContext';

View File

@ -32,7 +32,13 @@ import {
fetchStudents, fetchStudents,
editRegisterForm } from "@/app/lib/subscriptionAction" editRegisterForm } from "@/app/lib/subscriptionAction"
import { fetchClasses } from '@/app/lib/schoolAction'; import {
fetchClasses,
fetchRegistrationDiscounts,
fetchTuitionDiscounts,
fetchRegistrationFees,
fetchTuitionFees } from '@/app/lib/schoolAction';
import { createProfile } from '@/app/lib/authAction'; import { createProfile } from '@/app/lib/authAction';
import { import {
@ -75,6 +81,11 @@ export default function Page({ params: { locale } }) {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [fileToEdit, setFileToEdit] = useState(null); const [fileToEdit, setFileToEdit] = useState(null);
const [registrationDiscounts, setRegistrationDiscounts] = useState([]);
const [tuitionDiscounts, setTuitionDiscounts] = useState([]);
const [registrationFees, setRegistrationFees] = useState([]);
const [tuitionFees, setTuitionFees] = useState([]);
const csrfToken = useCsrfToken(); const csrfToken = useCsrfToken();
const openModal = () => { const openModal = () => {
@ -151,6 +162,7 @@ const registerFormArchivedDataHandler = (data) => {
} }
} }
} }
// TODO: revoir le système de pagination et de UseEffect // TODO: revoir le système de pagination et de UseEffect
useEffect(() => { useEffect(() => {
@ -195,7 +207,27 @@ const registerFormArchivedDataHandler = (data) => {
setFichiers(data) setFichiers(data)
}) })
.catch((err)=>{ err = err.message; console.log(err);}); .catch((err)=>{ err = err.message; console.log(err);})
fetchRegistrationDiscounts()
.then(data => {
setRegistrationDiscounts(data);
})
.catch(requestErrorHandler)
fetchTuitionDiscounts()
.then(data => {
setTuitionDiscounts(data);
})
.catch(requestErrorHandler)
fetchRegistrationFees()
.then(data => {
setRegistrationFees(data);
})
.catch(requestErrorHandler)
fetchTuitionFees()
.then(data => {
setTuitionFees(data);
})
.catch(requestErrorHandler);
} else { } else {
setTimeout(() => { setTimeout(() => {
setRegistrationFormsDataPending(mockFicheInscription); setRegistrationFormsDataPending(mockFicheInscription);
@ -321,6 +353,8 @@ useEffect(()=>{
const createRF = (updatedData) => { const createRF = (updatedData) => {
console.log('createRF updatedData:', updatedData); console.log('createRF updatedData:', updatedData);
const selectedRegistrationFeesIds = updatedData.selectedRegistrationFees.map(feeId => feeId)
const selectedRegistrationDiscountsIds = updatedData.selectedRegistrationDiscounts.map(discountId => discountId)
if (updatedData.selectedGuardians.length !== 0) { if (updatedData.selectedGuardians.length !== 0) {
const selectedGuardiansIds = updatedData.selectedGuardians.map(guardianId => guardianId) const selectedGuardiansIds = updatedData.selectedGuardians.map(guardianId => guardianId)
@ -330,7 +364,9 @@ useEffect(()=>{
last_name: updatedData.studentLastName, last_name: updatedData.studentLastName,
first_name: updatedData.studentFirstName, first_name: updatedData.studentFirstName,
}, },
idGuardians: selectedGuardiansIds idGuardians: selectedGuardiansIds,
fees: selectedRegistrationFeesIds,
discounts: selectedRegistrationDiscountsIds
}; };
createRegisterForm(data,csrfToken) createRegisterForm(data,csrfToken)
@ -379,7 +415,9 @@ useEffect(()=>{
} }
], ],
sibling: [] sibling: []
} },
fees: selectedRegistrationFeesIds,
discounts: selectedRegistrationDiscountsIds
}; };
createRegisterForm(data,csrfToken) createRegisterForm(data,csrfToken)
@ -784,6 +822,10 @@ const handleFileUpload = ({file, name, is_required, order}) => {
size='sm:w-1/4' size='sm:w-1/4'
ContentComponent={() => ( ContentComponent={() => (
<InscriptionForm students={students} <InscriptionForm students={students}
registrationDiscounts={registrationDiscounts}
tuitionDiscounts={tuitionDiscounts}
registrationFees={registrationFees}
tuitionFees={tuitionFees}
onSubmit={createRF} onSubmit={createRF}
/> />
)} )}

View File

@ -0,0 +1,39 @@
import React from 'react';
const CheckBox = ({ item, formData, handleChange, fieldName, itemLabelFunc = () => null, labelAttenuated = () => false, horizontal }) => {
const isChecked = formData[fieldName].includes(parseInt(item.id));
const isAttenuated = labelAttenuated(item) && !isChecked;
return (
<div key={item.id} className={`flex ${horizontal ? 'flex-col items-center' : 'flex-row items-center'}`}>
{horizontal && (
<label
htmlFor={`${fieldName}-${item.id}`}
className={`block text-sm text-center mb-1 ${isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'}`}
>
{itemLabelFunc(item)}
</label>
)}
<input
type="checkbox"
id={`${fieldName}-${item.id}`}
name={fieldName}
value={item.id}
checked={isChecked}
onChange={handleChange}
className={`form-checkbox h-4 w-4 rounded-mg text-emerald-600 hover:ring-emerald-400 checked:bg-emerald-600 hover:border-emerald-500 hover:bg-emerald-500 cursor-pointer ${horizontal ? 'mt-1' : 'mr-2'}`}
style={{ borderRadius: '6px', outline: 'none', boxShadow: 'none' }}
/>
{!horizontal && (
<label
htmlFor={`${fieldName}-${item.id}`}
className={`block text-sm ${isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'}`}
>
{itemLabelFunc(item)}
</label>
)}
</div>
);
};
export default CheckBox;

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import CheckBox from '@/components/CheckBox';
const CheckBoxList = ({ const CheckBoxList = ({
items, items,
@ -12,10 +13,6 @@ const CheckBoxList = ({
labelAttenuated = () => false, labelAttenuated = () => false,
horizontal = false // Ajouter l'option horizontal horizontal = false // Ajouter l'option horizontal
}) => { }) => {
const handleCheckboxChange = (e) => {
handleChange(e);
};
return ( return (
<div className={`mb-4 ${className}`}> <div className={`mb-4 ${className}`}>
<label className="block text-sm font-medium text-gray-700 flex items-center"> <label className="block text-sm font-medium text-gray-700 flex items-center">
@ -23,45 +20,18 @@ const CheckBoxList = ({
{label} {label}
</label> </label>
<div className={`mt-2 grid ${horizontal ? 'grid-cols-6 gap-2' : 'grid-cols-1 gap-4'}`}> <div className={`mt-2 grid ${horizontal ? 'grid-cols-6 gap-2' : 'grid-cols-1 gap-4'}`}>
{items.map(item => { {items.map(item => (
const isChecked = formData[fieldName].includes(parseInt(item.id)); <CheckBox
const isAttenuated = labelAttenuated(item) && !isChecked; key={item.id}
return ( item={item}
<div key={item.id} className={`flex ${horizontal ? 'flex-col items-center' : 'flex-row items-center'}`}> formData={formData}
{horizontal && ( handleChange={handleChange}
<label fieldName={fieldName}
htmlFor={`${fieldName}-${item.id}`} itemLabelFunc={itemLabelFunc}
className={`block text-sm text-center mb-1 ${ labelAttenuated={labelAttenuated}
isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600' horizontal={horizontal}
}`}
>
{itemLabelFunc(item)}
</label>
)}
<input
key={`${item.id}-${Math.random()}`}
type="checkbox"
id={`${fieldName}-${item.id}`}
name={fieldName}
value={item.id}
checked={isChecked}
onChange={handleCheckboxChange}
className={`form-checkbox h-4 w-4 rounded-mg text-emerald-600 hover:ring-emerald-400 checked:bg-emerald-600 hover:border-emerald-500 hover:bg-emerald-500 cursor-pointer ${horizontal ? 'mt-1' : 'mr-2'}`}
style={{ borderRadius: '6px', outline: 'none', boxShadow: 'none' }}
/> />
{!horizontal && ( ))}
<label
htmlFor={`${fieldName}-${item.id}`}
className={`block text-sm ${
isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'
}`}
>
{itemLabelFunc(item)}
</label>
)}
</div>
);
})}
</div> </div>
</div> </div>
); );

View File

@ -1,10 +1,13 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { User, Mail, Phone, UserCheck } from 'lucide-react'; import { User, Mail, Phone, UserCheck, DollarSign, Percent } from 'lucide-react';
import InputTextIcon from '@/components/InputTextIcon'; import InputTextIcon from '@/components/InputTextIcon';
import ToggleSwitch from '@/components/ToggleSwitch'; import ToggleSwitch from '@/components/ToggleSwitch';
import Button from '@/components/Button'; import Button from '@/components/Button';
import Table from '@/components/Table';
import FeesSection from '@/components/Structure/Tarification/FeesSection';
import DiscountsSection from '../Structure/Tarification/DiscountsSection';
const InscriptionForm = ( { students, onSubmit }) => { const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit }) => {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
studentLastName: '', studentLastName: '',
studentFirstName: '', studentFirstName: '',
@ -12,14 +15,26 @@ const InscriptionForm = ( { students, onSubmit }) => {
guardianPhone: '', guardianPhone: '',
selectedGuardians: [], selectedGuardians: [],
responsableType: 'new', responsableType: 'new',
autoMail: false autoMail: false,
selectedRegistrationDiscounts: [],
selectedRegistrationFees: registrationFees.map(fee => fee.id)
}); });
const [step, setStep] = useState(1); const [step, setStep] = useState(0);
const [selectedStudent, setSelectedEleve] = useState(''); const [selectedStudent, setSelectedEleve] = useState('');
const [existingGuardians, setExistingGuardians] = useState([]); const [existingGuardians, setExistingGuardians] = useState([]);
const [totalRegistrationAmount, setTotalRegistrationAmount] = useState(0);
const maxStep = 4 const maxStep = 4
useEffect(() => {
// Calcul du montant total lors de l'initialisation
const initialTotalAmount = calculateFinalRegistrationAmount(
registrationFees.map(fee => fee.id),
[]
);
setTotalRegistrationAmount(initialTotalAmount);
}, [registrationDiscounts, registrationFees]);
const handleToggleChange = () => { const handleToggleChange = () => {
setFormData({ ...formData, autoMail: !formData.autoMail }); setFormData({ ...formData, autoMail: !formData.autoMail });
}; };
@ -39,7 +54,7 @@ const InscriptionForm = ( { students, onSubmit }) => {
}; };
const prevStep = () => { const prevStep = () => {
if (step > 1) { if (step >= 1) {
setStep(step - 1); setStep(step - 1);
} }
}; };
@ -66,8 +81,122 @@ const InscriptionForm = ( { students, onSubmit }) => {
onSubmit(formData); onSubmit(formData);
} }
const handleFeeSelection = (feeId) => {
setFormData((prevData) => {
const selectedRegistrationFees = prevData.selectedRegistrationFees.includes(feeId)
? prevData.selectedRegistrationFees.filter(id => id !== feeId)
: [...prevData.selectedRegistrationFees, feeId];
const finalAmount = calculateFinalRegistrationAmount(selectedRegistrationFees, prevData.selectedRegistrationDiscounts);
setTotalRegistrationAmount(finalAmount);
return { ...prevData, selectedRegistrationFees };
});
};
const handleDiscountSelection = (discountId) => {
setFormData((prevData) => {
const selectedRegistrationDiscounts = prevData.selectedRegistrationDiscounts.includes(discountId)
? prevData.selectedRegistrationDiscounts.filter(id => id !== discountId)
: [...prevData.selectedRegistrationDiscounts, discountId];
const finalAmount = calculateFinalRegistrationAmount(prevData.selectedRegistrationFees, selectedRegistrationDiscounts);
setTotalRegistrationAmount(finalAmount);
return { ...prevData, selectedRegistrationDiscounts };
});
};
const calculateFinalRegistrationAmount = (selectedRegistrationFees, selectedRegistrationDiscounts) => {
const totalFees = selectedRegistrationFees.reduce((sum, feeId) => {
const fee = registrationFees.find(f => f.id === feeId);
if (fee && !isNaN(parseFloat(fee.base_amount))) {
return sum + parseFloat(fee.base_amount);
}
return sum;
}, 0);
console.log(totalFees);
const totalDiscounts = selectedRegistrationDiscounts.reduce((sum, discountId) => {
const discount = registrationDiscounts.find(d => d.id === discountId);
if (discount) {
if (discount.discount_type === 0 && !isNaN(parseFloat(discount.amount))) { // Currency
return sum + parseFloat(discount.amount);
} else if (discount.discount_type === 1 && !isNaN(parseFloat(discount.amount))) { // Percent
return sum + (totalFees * parseFloat(discount.amount) / 100);
}
}
return sum;
}, 0);
const finalAmount = totalFees - totalDiscounts;
return finalAmount.toFixed(2);
};
const isLabelAttenuated = (item) => {
return !formData.selectedRegistrationDiscounts.includes(parseInt(item.id));
};
const isLabelFunction = (item) => {
return item.name + ' : ' + item.amount
};
return ( return (
<div className="space-y-4 mt-8"> <div className="space-y-4 mt-8">
{step === 0 && (
<div>
<h2 className="text-l font-bold mb-4">Frais d'inscription</h2>
{registrationFees.length > 0 ? (
<>
<div className="mb-4">
<FeesSection
fees={registrationFees}
type={0}
subscriptionMode={true}
selectedFees={formData.selectedRegistrationFees}
handleFeeSelection={handleFeeSelection}
/>
</div>
<h2 className="text-l font-bold mb-4">Réductions</h2>
<div className="mb-4">
{registrationDiscounts.length > 0 ? (
<DiscountsSection
discounts={registrationDiscounts}
type={0}
subscriptionMode={true}
selectedDiscounts={formData.selectedRegistrationDiscounts}
handleDiscountSelection={handleDiscountSelection}
/>
) : (
<p className="bg-orange-100 border border-orange-400 text-orange-700 px-4 py-3 rounded relative" role="alert">
<strong className="font-bold">Information</strong>
<span className="block sm:inline"> Aucune réduction n'a été créée sur les frais d'inscription.</span>
</p>
)}
</div>
<Table
data={[ {id: 1}]}
columns={[
{
name: 'LIBELLE',
transform: () => <span>MONTANT TOTAL</span>
},
{
name: 'TOTAL',
transform: () => <b>{totalRegistrationAmount} €</b>
}
]}
defaultTheme='bg-cyan-100'
/>
</>
) : (
<p className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<strong className="font-bold">Attention!</strong>
<span className="block sm:inline"> Aucun frais d'inscription n'a été créé.</span>
</p>
)}
</div>
)}
{step === 1 && ( {step === 1 && (
<div> <div>
<h2 className="text-l font-bold mb-4">Nouvel élève</h2> <h2 className="text-l font-bold mb-4">Nouvel élève</h2>
@ -270,7 +399,7 @@ const InscriptionForm = ( { students, onSubmit }) => {
)} )}
<div className="flex justify-end mt-4 space-x-4"> <div className="flex justify-end mt-4 space-x-4">
{step > 1 && ( {step >= 1 && (
<Button text="Précédent" <Button text="Précédent"
onClick={prevStep} onClick={prevStep}
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md shadow-sm hover:bg-gray-400 focus:outline-none" className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md shadow-sm hover:bg-gray-400 focus:outline-none"

View File

@ -3,8 +3,9 @@ import { Plus, Trash, Edit3, Check, X, Percent, EuroIcon, Tag } from 'lucide-rea
import Table from '@/components/Table'; import Table from '@/components/Table';
import InputTextIcon from '@/components/InputTextIcon'; import InputTextIcon from '@/components/InputTextIcon';
import Popup from '@/components/Popup'; import Popup from '@/components/Popup';
import CheckBox from '@/components/CheckBox';
const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, handleDelete, type }) => { const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, handleDelete, type, subscriptionMode = false, selectedDiscounts, handleDiscountSelection }) => {
const [editingDiscount, setEditingDiscount] = useState(null); const [editingDiscount, setEditingDiscount] = useState(null);
const [newDiscount, setNewDiscount] = useState(null); const [newDiscount, setNewDiscount] = useState(null);
const [formData, setFormData] = useState({}); const [formData, setFormData] = useState({});
@ -184,14 +185,41 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
</button> </button>
</div> </div>
); );
case '':
return (
<div className="flex justify-center">
<CheckBox
item={discount}
formData={{ selectedDiscounts }}
handleChange={() => handleDiscountSelection(discount.id)}
fieldName="selectedDiscounts"
/>
</div>
);
default: default:
return null; return null;
} }
} }
}; };
const columns = subscriptionMode
? [
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'REMISE', label: 'Remise' },
{ name: '', label: 'Sélection' }
]
: [
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'REMISE', label: 'Remise' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
{ name: 'ACTIONS', label: 'Actions' }
];
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{!subscriptionMode && (
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex items-center mb-4"> <div className="flex items-center mb-4">
<Tag className="w-6 h-6 text-emerald-500 mr-2" /> <Tag className="w-6 h-6 text-emerald-500 mr-2" />
@ -201,15 +229,10 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
<Plus className="w-5 h-5" /> <Plus className="w-5 h-5" />
</button> </button>
</div> </div>
)}
<Table <Table
data={newDiscount ? [newDiscount, ...discounts] : discounts} data={newDiscount ? [newDiscount, ...discounts] : discounts}
columns={[ columns={columns}
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'REMISE', label: 'Valeur' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'MISE A JOUR', label: 'date mise à jour' },
{ name: 'ACTIONS', label: 'Actions' }
]}
renderCell={renderDiscountCell} renderCell={renderDiscountCell}
defaultTheme='bg-yellow-100' defaultTheme='bg-yellow-100'
/> />

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import FeesSection from '@/components/Structure/Configuration/FeesSection'; import FeesSection from '@/components/Structure/Tarification/FeesSection';
import DiscountsSection from '@/components/Structure/Configuration/DiscountsSection'; import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
import { BE_SCHOOL_FEE_URL, BE_SCHOOL_DISCOUNT_URL } from '@/utils/Url'; import { BE_SCHOOL_FEE_URL, BE_SCHOOL_DISCOUNT_URL } from '@/utils/Url';
const FeesManagement = ({ registrationDiscounts, setRegistrationDiscounts, tuitionDiscounts, setTuitionDiscounts, registrationFees, setRegistrationFees, tuitionFees, setTuitionFees, handleCreate, handleEdit, handleDelete }) => { const FeesManagement = ({ registrationDiscounts, setRegistrationDiscounts, tuitionDiscounts, setTuitionDiscounts, registrationFees, setRegistrationFees, tuitionFees, setTuitionFees, handleCreate, handleEdit, handleDelete }) => {

View File

@ -1,10 +1,11 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Plus, Trash, Edit3, Check, X, EyeOff, Eye, CreditCard } from 'lucide-react'; import { Plus, Trash, Edit3, Check, X, EyeOff, Eye, CreditCard, BookOpen } from 'lucide-react';
import Table from '@/components/Table'; import Table from '@/components/Table';
import InputTextIcon from '@/components/InputTextIcon'; import InputTextIcon from '@/components/InputTextIcon';
import Popup from '@/components/Popup'; import Popup from '@/components/Popup';
import CheckBox from '@/components/CheckBox';
const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handleDelete, type }) => { const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handleDelete, type, subscriptionMode = false, selectedFees, handleFeeSelection }) => {
const [editingFee, setEditingFee] = useState(null); const [editingFee, setEditingFee] = useState(null);
const [newFee, setNewFee] = useState(null); const [newFee, setNewFee] = useState(null);
const [formData, setFormData] = useState({}); const [formData, setFormData] = useState({});
@ -122,24 +123,6 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
</div> </div>
); );
const calculateFinalAmount = (baseAmount, discountIds) => {
const totalDiscounts = discountIds.reduce((sum, discountId) => {
const discount = discounts.find(d => d.id === discountId);
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);
};
const renderFeeCell = (fee, column) => { const renderFeeCell = (fee, column) => {
const isEditing = editingFee === fee.id; const isEditing = editingFee === fee.id;
const isCreating = newFee && newFee.id === fee.id; const isCreating = newFee && newFee.id === fee.id;
@ -211,14 +194,41 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
</button> </button>
</div> </div>
); );
case '':
return (
<div className="flex justify-center">
<CheckBox
item={fee}
formData={{ selectedFees }}
handleChange={() => handleFeeSelection(fee.id)}
fieldName="selectedFees"
/>
</div>
);
default: default:
return null; return null;
} }
} }
}; };
const columns = subscriptionMode
? [
{ name: 'NOM', label: 'Nom' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'MONTANT', label: 'Montant de base' },
{ name: '', label: 'Sélection' }
]
: [
{ name: 'NOM', label: 'Nom' },
{ name: 'MONTANT', label: 'Montant de base' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
{ name: 'ACTIONS', label: 'Actions' }
];
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{!subscriptionMode && (
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex items-center mb-4"> <div className="flex items-center mb-4">
<CreditCard className="w-6 h-6 text-emerald-500 mr-2" /> <CreditCard className="w-6 h-6 text-emerald-500 mr-2" />
@ -228,15 +238,10 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
<Plus className="w-5 h-5" /> <Plus className="w-5 h-5" />
</button> </button>
</div> </div>
)}
<Table <Table
data={newFee ? [newFee, ...fees] : fees} data={newFee ? [newFee, ...fees] : fees}
columns={[ columns={columns}
{ name: 'NOM', label: 'Nom' },
{ name: 'MONTANT', label: 'Montant de base' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'MISE A JOUR', label: 'date mise à jour' },
{ name: 'ACTIONS', label: 'Actions' }
]}
renderCell={renderFeeCell} renderCell={renderFeeCell}
/> />
<Popup <Popup