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'
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):
name = models.CharField(max_length=255, unique=True)
amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
@ -113,3 +119,11 @@ class PaymentPlan(models.Model):
def __str__(self):
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 .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 N3wtSchool import settings, bdd
from django.utils import timezone
@ -195,4 +195,9 @@ class FeeSerializer(serializers.ModelSerializer):
class PaymentPlanSerializer(serializers.ModelSerializer):
class Meta:
model = PaymentPlan
fields = '__all__'
class PaymentModeSerializer(serializers.ModelSerializer):
class Meta:
model = PaymentMode
fields = '__all__'

View File

@ -14,7 +14,9 @@ from School.views import (
DiscountsView,
DiscountView,
PaymentPlansView,
PaymentPlanView
PaymentPlanView,
PaymentModesView,
PaymentModeView
)
urlpatterns = [
@ -45,4 +47,8 @@ urlpatterns = [
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/([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 django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist
from .models import Teacher, Speciality, SchoolClass, Planning, Discount, Fee, PaymentPlan
from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer, DiscountSerializer, FeeSerializer, PaymentPlanSerializer
from .models import Teacher, Speciality, SchoolClass, Planning, Discount, Fee, PaymentPlan, PaymentMode
from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer, DiscountSerializer, FeeSerializer, PaymentPlanSerializer, PaymentModeSerializer
from N3wtSchool import bdd
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
@ -355,4 +355,49 @@ class PaymentPlanView(APIView):
return JsonResponse(payment_plan_serializer.errors, safe=False, status=400)
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", "School", "--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:

View File

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

View File

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

View File

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

View File

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

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_PAYMENT_PLAN_URL = `${BASE_URL}/School/paymentPlan`;
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
export const BE_GESTIONMESSAGERIE_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messagerie`