feat: Ajout des modes de paiements + création d'une commande dans le

back permettant d'initialiser des données de test (pour les tarifs)
This commit is contained in:
N3WT DE COMPET
2025-02-12 15:13:15 +01:00
parent 23203c0397
commit 0c5e3aa098
12 changed files with 299 additions and 44 deletions

View File

@ -0,0 +1,85 @@
from django.core.management.base import BaseCommand
from School.models import Fee, Discount, FeeType, DiscountType
class Command(BaseCommand):
help = 'Initialize or update Fees and Discounts'
def handle(self, *args, **kwargs):
self.create_or_update_fees()
self.create_or_update_discounts()
def create_or_update_fees(self):
fees_data = [
{
"name": "Frais d'inscription",
"base_amount": "150.00",
"description": "Montant de base",
"is_active": True,
"type": FeeType.REGISTRATION_FEE
},
{
"name": "Matériel",
"base_amount": "85.00",
"description": "Livres / jouets",
"is_active": True,
"type": FeeType.REGISTRATION_FEE
},
{
"name": "Sorties périscolaires",
"base_amount": "120.00",
"description": "Sorties",
"is_active": True,
"type": FeeType.REGISTRATION_FEE
},
{
"name": "Les colibris",
"base_amount": "4500.00",
"description": "TPS / PS / MS / GS",
"is_active": True,
"type": FeeType.TUITION_FEE
},
{
"name": "Les butterflies",
"base_amount": "5000.00",
"description": "CP / CE1 / CE2 / CM1 / CM2",
"is_active": True,
"type": FeeType.TUITION_FEE
}
]
for fee_data in fees_data:
Fee.objects.update_or_create(
name=fee_data["name"],
type=fee_data["type"],
defaults=fee_data
)
self.stdout.write(self.style.SUCCESS('Fees initialized or updated successfully'))
def create_or_update_discounts(self):
discounts_data = [
{
"name": "Parrainage",
"amount": "10.00",
"description": "Réduction pour parrainage",
"discount_type": DiscountType.PERCENT,
"type": FeeType.TUITION_FEE
},
{
"name": "Réinscription",
"amount": "100.00",
"description": "Réduction pour Réinscription",
"discount_type": DiscountType.PERCENT,
"type": FeeType.REGISTRATION_FEE
}
]
for discount_data in discounts_data:
Discount.objects.update_or_create(
name=discount_data["name"],
type=discount_data["type"],
discount_type=discount_data["discount_type"],
defaults=discount_data
)
self.stdout.write(self.style.SUCCESS('Discounts initialized or updated successfully'))

View File

@ -0,0 +1,25 @@
from django.core.management.base import BaseCommand
from School.models import PaymentMode, PaymentModeType, FeeType
class Command(BaseCommand):
help = 'Initialize or update Payment Modes'
def handle(self, *args, **kwargs):
self.create_or_update_payment_modes()
def create_or_update_payment_modes(self):
for fee_type in FeeType.choices:
fee_type_value = fee_type[0]
for mode in PaymentModeType.choices:
mode_value = mode[0]
PaymentMode.objects.update_or_create(
mode=mode_value,
type=fee_type_value,
defaults={
'is_active': False
}
)
self.stdout.write(self.style.SUCCESS('Payment Modes initialized or updated successfully'))

View File

@ -82,6 +82,12 @@ class FeeType(models.IntegerChoices):
REGISTRATION_FEE = 0, 'Registration Fee' REGISTRATION_FEE = 0, 'Registration Fee'
TUITION_FEE = 1, 'Tuition Fee' TUITION_FEE = 1, 'Tuition Fee'
class PaymentModeType(models.IntegerChoices):
SEPA = 1, 'Prélèvement SEPA'
TRANSFER = 2, 'Virement'
CHECK = 3, 'Chèque'
CASH = 4, 'Espèce'
class Discount(models.Model): class Discount(models.Model):
name = models.CharField(max_length=255, unique=True) name = models.CharField(max_length=255, unique=True)
amount = models.DecimalField(max_digits=10, decimal_places=2, default=0) amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
@ -113,3 +119,11 @@ class PaymentPlan(models.Model):
def __str__(self): def __str__(self):
return f"{self.get_frequency_display()} - {self.get_type_display()}" return f"{self.get_frequency_display()} - {self.get_type_display()}"
class PaymentMode(models.Model):
mode = models.IntegerField(choices=PaymentModeType.choices, default=PaymentModeType.SEPA)
type = models.IntegerField(choices=FeeType.choices, default=FeeType.REGISTRATION_FEE)
is_active = models.BooleanField(default=False)
def __str__(self):
return f"{self.get_mode_display()} - {self.get_type_display()}"

View File

@ -1,5 +1,5 @@
from rest_framework import serializers from rest_framework import serializers
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan, PaymentMode
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
@ -196,3 +196,8 @@ class PaymentPlanSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = PaymentPlan model = PaymentPlan
fields = '__all__' fields = '__all__'
class PaymentModeSerializer(serializers.ModelSerializer):
class Meta:
model = PaymentMode
fields = '__all__'

View File

@ -14,7 +14,9 @@ from School.views import (
DiscountsView, DiscountsView,
DiscountView, DiscountView,
PaymentPlansView, PaymentPlansView,
PaymentPlanView PaymentPlanView,
PaymentModesView,
PaymentModeView
) )
urlpatterns = [ urlpatterns = [
@ -45,4 +47,8 @@ urlpatterns = [
re_path(r'^paymentPlans/(?P<_filter>[a-zA-z]+)$', PaymentPlansView.as_view(), name="paymentPlans"), re_path(r'^paymentPlans/(?P<_filter>[a-zA-z]+)$', PaymentPlansView.as_view(), name="paymentPlans"),
re_path(r'^paymentPlan$', PaymentPlanView.as_view(), name="paymentPlan"), re_path(r'^paymentPlan$', PaymentPlanView.as_view(), name="paymentPlan"),
re_path(r'^paymentPlan/([0-9]+)$', PaymentPlanView.as_view(), name="paymentPlan"), re_path(r'^paymentPlan/([0-9]+)$', PaymentPlanView.as_view(), name="paymentPlan"),
re_path(r'^paymentModes/(?P<_filter>[a-zA-z]+)$', PaymentModesView.as_view(), name="paymentModes"),
re_path(r'^paymentMode$', PaymentModeView.as_view(), name="paymentMode"),
re_path(r'^paymentMode/([0-9]+)$', PaymentModeView.as_view(), name="paymentMode"),
] ]

View File

@ -5,8 +5,8 @@ from rest_framework.parsers import JSONParser
from rest_framework.views import APIView from rest_framework.views import APIView
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from .models import Teacher, Speciality, SchoolClass, Planning, Discount, Fee, PaymentPlan from .models import Teacher, Speciality, SchoolClass, Planning, Discount, Fee, PaymentPlan, PaymentMode
from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer, DiscountSerializer, FeeSerializer, PaymentPlanSerializer from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer, DiscountSerializer, FeeSerializer, PaymentPlanSerializer, PaymentModeSerializer
from N3wtSchool import bdd from N3wtSchool import bdd
from N3wtSchool.bdd import delete_object, getAllObjects, getObject from N3wtSchool.bdd import delete_object, getAllObjects, getObject
@ -356,3 +356,48 @@ class PaymentPlanView(APIView):
def delete(self, request, _id): def delete(self, request, _id):
return delete_object(PaymentPlan, _id) return delete_object(PaymentPlan, _id)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class PaymentModesView(APIView):
def get(self, request, _filter, *args, **kwargs):
if _filter not in ['registration', 'tuition']:
return JsonResponse({"error": "Invalid type parameter. Must be 'registration' or 'tuition'."}, safe=False, status=400)
type_value = 0 if _filter == 'registration' else 1
paymentModes = PaymentMode.objects.filter(type=type_value)
payment_modes_serializer = PaymentModeSerializer(paymentModes, many=True)
return JsonResponse(payment_modes_serializer.data, safe=False, status=200)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class PaymentModeView(APIView):
def get(self, request, _id):
try:
payment_mode = PaymentMode.objects.get(id=_id)
payment_mode_serializer = PaymentModeSerializer(payment_mode)
return JsonResponse(payment_mode_serializer.data, safe=False)
except PaymentMode.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=404)
def post(self, request):
payment_mode_data = JSONParser().parse(request)
payment_mode_serializer = PaymentModeSerializer(data=payment_mode_data)
if payment_mode_serializer.is_valid():
payment_mode_serializer.save()
return JsonResponse(payment_mode_serializer.data, safe=False, status=201)
return JsonResponse(payment_mode_serializer.errors, safe=False, status=400)
def put(self, request, _id):
payment_mode_data = JSONParser().parse(request)
try:
payment_mode = PaymentMode.objects.get(id=_id)
except PaymentMode.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=404)
payment_mode_serializer = PaymentModeSerializer(payment_mode, data=payment_mode_data, partial=True)
if payment_mode_serializer.is_valid():
payment_mode_serializer.save()
return JsonResponse(payment_mode_serializer.data, safe=False)
return JsonResponse(payment_mode_serializer.errors, safe=False, status=400)

View File

@ -19,7 +19,9 @@ commands = [
["python", "manage.py", "makemigrations", "Auth", "--noinput"], ["python", "manage.py", "makemigrations", "Auth", "--noinput"],
["python", "manage.py", "makemigrations", "School", "--noinput"], ["python", "manage.py", "makemigrations", "School", "--noinput"],
["python", "manage.py", "migrate", "--noinput"], ["python", "manage.py", "migrate", "--noinput"],
["python", "manage.py", "init_payment_plans"] ["python", "manage.py", "init_payment_plans"],
["python", "manage.py", "init_payment_modes"],
["python", "manage.py", "init_data"]
] ]
for command in commands: for command in commands:

View File

@ -17,8 +17,10 @@ import { createDatas,
fetchTuitionDiscounts, fetchTuitionDiscounts,
fetchRegistrationFees, fetchRegistrationFees,
fetchTuitionFees, fetchTuitionFees,
fetchRregistrationPaymentPlans, fetchRegistrationPaymentPlans,
fetchTuitionPaymentPlans } from '@/app/lib/schoolAction'; fetchTuitionPaymentPlans,
fetchRegistrationPaymentModes,
fetchTuitionPaymentModes } from '@/app/lib/schoolAction';
import SidebarTabs from '@/components/SidebarTabs'; import SidebarTabs from '@/components/SidebarTabs';
import FilesManagement from '@/components/Structure/Files/FilesManagement'; import FilesManagement from '@/components/Structure/Files/FilesManagement';
@ -38,6 +40,8 @@ export default function Page() {
const [fichiers, setFichiers] = useState([]); const [fichiers, setFichiers] = useState([]);
const [registrationPaymentPlans, setRegistrationPaymentPlans] = useState([]); const [registrationPaymentPlans, setRegistrationPaymentPlans] = useState([]);
const [tuitionPaymentPlans, setTuitionPaymentPlans] = useState([]); const [tuitionPaymentPlans, setTuitionPaymentPlans] = useState([]);
const [registrationPaymentModes, setRegistrationPaymentModes] = useState([]);
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
const csrfToken = useCsrfToken(); const csrfToken = useCsrfToken();
@ -78,6 +82,12 @@ export default function Page() {
// Fetch data for tuition payment plans // Fetch data for tuition payment plans
handleTuitionPaymentPlans(); handleTuitionPaymentPlans();
// Fetch data for registration payment modes
handleRegistrationPaymentModes();
// Fetch data for tuition payment modes
handleTuitionPaymentModes();
}, []); }, []);
const handleSpecialities = () => { const handleSpecialities = () => {
@ -145,7 +155,7 @@ export default function Page() {
}; };
const handleRegistrationPaymentPlans = () => { const handleRegistrationPaymentPlans = () => {
fetchRregistrationPaymentPlans() fetchRegistrationPaymentPlans()
.then(data => { .then(data => {
setRegistrationPaymentPlans(data); setRegistrationPaymentPlans(data);
}) })
@ -160,6 +170,22 @@ export default function Page() {
.catch(error => console.error('Error fetching tuition payment plans:', error)); .catch(error => console.error('Error fetching tuition payment plans:', error));
}; };
const handleRegistrationPaymentModes = () => {
fetchRegistrationPaymentModes()
.then(data => {
setRegistrationPaymentModes(data);
})
.catch(error => console.error('Error fetching registration payment modes:', error));
};
const handleTuitionPaymentModes = () => {
fetchTuitionPaymentModes()
.then(data => {
setTuitionPaymentModes(data);
})
.catch(error => console.error('Error fetching tuition payment modes:', error));
};
const handleCreate = (url, newData, setDatas) => { const handleCreate = (url, newData, setDatas) => {
return createDatas(url, newData, csrfToken) return createDatas(url, newData, csrfToken)
.then(data => { .then(data => {
@ -263,7 +289,10 @@ export default function Page() {
setRegistrationPaymentPlans={setRegistrationPaymentPlans} setRegistrationPaymentPlans={setRegistrationPaymentPlans}
tuitionPaymentPlans={tuitionPaymentPlans} tuitionPaymentPlans={tuitionPaymentPlans}
setTuitionPaymentPlans={setTuitionPaymentPlans} setTuitionPaymentPlans={setTuitionPaymentPlans}
setRegist registrationPaymentModes={registrationPaymentModes}
setRegistrationPaymentModes={setRegistrationPaymentModes}
tuitionPaymentModes={tuitionPaymentModes}
setTuitionPaymentModes={setTuitionPaymentModes}
handleCreate={handleCreate} handleCreate={handleCreate}
handleEdit={handleEdit} handleEdit={handleEdit}
handleDelete={handleDelete} handleDelete={handleDelete}

View File

@ -6,6 +6,7 @@ import {
BE_SCHOOL_FEES_URL, BE_SCHOOL_FEES_URL,
BE_SCHOOL_DISCOUNTS_URL, BE_SCHOOL_DISCOUNTS_URL,
BE_SCHOOL_PAYMENT_PLANS_URL, BE_SCHOOL_PAYMENT_PLANS_URL,
BE_SCHOOL_PAYMENT_MODES_URL
} from '@/utils/Url'; } from '@/utils/Url';
const requestResponseHandler = async (response) => { const requestResponseHandler = async (response) => {
@ -61,7 +62,7 @@ export const fetchTuitionFees = () => {
.then(requestResponseHandler) .then(requestResponseHandler)
}; };
export const fetchRregistrationPaymentPlans = () => { export const fetchRegistrationPaymentPlans = () => {
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}/registration`) return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}/registration`)
.then(requestResponseHandler) .then(requestResponseHandler)
} }
@ -71,6 +72,16 @@ export const fetchTuitionPaymentPlans = () => {
.then(requestResponseHandler) .then(requestResponseHandler)
} }
export const fetchRegistrationPaymentModes = () => {
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}/registration`)
.then(requestResponseHandler)
}
export const fetchTuitionPaymentModes = () => {
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}/tuition`)
.then(requestResponseHandler)
}
export const createDatas = (url, newData, csrfToken) => { export const createDatas = (url, newData, csrfToken) => {
return fetch(url, { return fetch(url, {
method: 'POST', method: 'POST',

View File

@ -1,41 +1,59 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import CheckBoxList from '@/components/CheckBoxList'; import { DollarSign } from 'lucide-react';
import { CreditCard } from 'lucide-react';
const paymentModes = [ const paymentModesOptions = [
{ id: 1, name: 'Prélèvement SEPA' }, { id: 1, name: 'Prélèvement SEPA' },
{ id: 2, name: 'Virement' }, { id: 2, name: 'Virement' },
{ id: 3, name: 'Chèque' }, { id: 3, name: 'Chèque' },
{ id: 4, name: 'Espèce' }, { id: 4, name: 'Espèce' },
]; ];
const PaymentModeSelector = ({ formData, setFormData, fieldName }) => { const PaymentModeSelector = ({ paymentModes, setPaymentModes, handleEdit, type }) => {
const handleCheckboxChange = (event) => { const [activePaymentModes, setActivePaymentModes] = useState([]);
const value = parseInt(event.target.value, 10);
setFormData((prevFormData) => { useEffect(() => {
const selectedModes = prevFormData[fieldName] || []; // Initialiser activePaymentModes avec les modes dont is_active est à true
const newSelectedModes = selectedModes.includes(value) const activeModes = paymentModes.filter(mode => mode.is_active).map(mode => mode.mode);
? selectedModes.filter((mode) => mode !== value) setActivePaymentModes(activeModes);
: [...selectedModes, value]; }, [paymentModes]);
return { ...prevFormData, [fieldName]: newSelectedModes };
const handleModeToggle = (modeId) => {
setActivePaymentModes((prevActiveModes) => {
const newActiveModes = prevActiveModes.includes(modeId)
? prevActiveModes.filter((mode) => mode !== modeId)
: [...prevActiveModes, modeId];
// Mettre à jour le mode de paiement dans le backend
const updatedMode = paymentModes.find(mode => mode.mode === modeId);
if (updatedMode) {
handleEdit(updatedMode.id, { ...updatedMode, is_active: !updatedMode.is_active });
}
return newActiveModes;
}); });
}; };
return ( return (
<div className="mb-4 w-full"> <div className="space-y-4">
<div className="flex justify-center bg-gray p-4 rounded-lg shadow-md w-full"> <div className="flex items-center mb-4">
<CreditCard className="w-6 h-6 text-emerald-500 mr-2" /> <DollarSign className="w-6 h-6 text-emerald-500 mr-2" />
<h2 className="text-xl font-semibold">Mode de paiement</h2> <h2 className="text-xl font-semibold">Modes de paiement</h2>
<CheckBoxList </div>
items={paymentModes} <div className="grid grid-cols-2 gap-4 mt-4">
formData={formData} {paymentModesOptions.map((mode) => (
handleChange={handleCheckboxChange} <button
fieldName={fieldName} key={mode.id}
itemLabelFunc={(item) => ( type="button"
<span className="text-sm font-medium">{item.name}</span> onClick={() => handleModeToggle(mode.id)}
)} className={`p-4 rounded-lg shadow-md text-center text-gray-700' ${
horizontal={true} activePaymentModes.includes(mode.id)
/> ? 'bg-emerald-300'
: 'bg-white'
} hover:bg-emerald-200`}
>
{mode.name}
</button>
))}
</div> </div>
</div> </div>
); );

View File

@ -3,7 +3,7 @@ import FeesSection from '@/components/Structure/Tarification/FeesSection';
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection'; import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
import PaymentPlanSelector from '@/components/PaymentPlanSelector'; import PaymentPlanSelector from '@/components/PaymentPlanSelector';
import PaymentModeSelector from '@/components/PaymentModeSelector'; import PaymentModeSelector from '@/components/PaymentModeSelector';
import { BE_SCHOOL_FEE_URL, BE_SCHOOL_DISCOUNT_URL, BE_SCHOOL_PAYMENT_PLAN_URL } from '@/utils/Url'; import { BE_SCHOOL_FEE_URL, BE_SCHOOL_DISCOUNT_URL, BE_SCHOOL_PAYMENT_PLAN_URL, BE_SCHOOL_PAYMENT_MODE_URL } from '@/utils/Url';
import { set } from 'lodash'; import { set } from 'lodash';
const FeesManagement = ({ registrationDiscounts, const FeesManagement = ({ registrationDiscounts,
@ -18,6 +18,10 @@ const FeesManagement = ({ registrationDiscounts,
setRegistrationPaymentPlans, setRegistrationPaymentPlans,
tuitionPaymentPlans, tuitionPaymentPlans,
setTuitionPaymentPlans, setTuitionPaymentPlans,
registrationPaymentModes,
setRegistrationPaymentModes,
tuitionPaymentModes,
setTuitionPaymentModes,
handleCreate, handleCreate,
handleEdit, handleEdit,
handleDelete }) => { handleDelete }) => {
@ -75,13 +79,14 @@ const FeesManagement = ({ registrationDiscounts,
type={0} type={0}
/> />
</div> </div>
{/* <div className="col-span-1"> <div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
<PaymentModeSelector <PaymentModeSelector
formData={formRegistrationData} paymentModes={registrationPaymentModes}
setFormData={setFormRegistrationData} setPaymentModes={setRegistrationPaymentModes}
fieldName="paymentRegistrationMode" handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_PAYMENT_MODE_URL}`, id, updatedData, setRegistrationPaymentModes)}
type={0}
/> />
</div> */} </div>
</div> </div>
</div> </div>
<div className="bg-white p-2 rounded-lg shadow-md"> <div className="bg-white p-2 rounded-lg shadow-md">
@ -117,6 +122,14 @@ const FeesManagement = ({ registrationDiscounts,
type={1} type={1}
/> />
</div> </div>
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
<PaymentModeSelector
paymentModes={tuitionPaymentModes}
setPaymentModes={setTuitionPaymentModes}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_PAYMENT_MODE_URL}`, id, updatedData, setTuitionPaymentModes)}
type={1}
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -39,6 +39,8 @@ export const BE_SCHOOL_DISCOUNT_URL = `${BASE_URL}/School/discount`;
export const BE_SCHOOL_DISCOUNTS_URL = `${BASE_URL}/School/discounts`; export const BE_SCHOOL_DISCOUNTS_URL = `${BASE_URL}/School/discounts`;
export const BE_SCHOOL_PAYMENT_PLAN_URL = `${BASE_URL}/School/paymentPlan`; export const BE_SCHOOL_PAYMENT_PLAN_URL = `${BASE_URL}/School/paymentPlan`;
export const BE_SCHOOL_PAYMENT_PLANS_URL = `${BASE_URL}/School/paymentPlans`; export const BE_SCHOOL_PAYMENT_PLANS_URL = `${BASE_URL}/School/paymentPlans`;
export const BE_SCHOOL_PAYMENT_MODE_URL = `${BASE_URL}/School/paymentMode`;
export const BE_SCHOOL_PAYMENT_MODES_URL = `${BASE_URL}/School/paymentModes`;
// GESTION MESSAGERIE // GESTION MESSAGERIE
export const BE_GESTIONMESSAGERIE_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messagerie` export const BE_GESTIONMESSAGERIE_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messagerie`