From 0c5e3aa0988a16b6f9f8c0b411c2c1b443c972a7 Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Wed, 12 Feb 2025 15:13:15 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Ajout=20des=20modes=20de=20paiements=20?= =?UTF-8?q?+=20cr=C3=A9ation=20d'une=20commande=20dans=20le=20back=20perme?= =?UTF-8?q?ttant=20d'initialiser=20des=20donn=C3=A9es=20de=20test=20(pour?= =?UTF-8?q?=20les=20tarifs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../School/management/commands/init_data.py | 85 +++++++++++++++++++ .../management/commands/init_payment_modes.py | 25 ++++++ Back-End/School/models.py | 14 +++ Back-End/School/serializers.py | 7 +- Back-End/School/urls.py | 8 +- Back-End/School/views.py | 51 ++++++++++- Back-End/start.py | 4 +- .../src/app/[locale]/admin/structure/page.js | 37 +++++++- Front-End/src/app/lib/schoolAction.js | 13 ++- .../src/components/PaymentModeSelector.js | 72 ++++++++++------ .../Structure/Tarification/FeesManagement.js | 25 ++++-- Front-End/src/utils/Url.js | 2 + 12 files changed, 299 insertions(+), 44 deletions(-) create mode 100644 Back-End/School/management/commands/init_data.py create mode 100644 Back-End/School/management/commands/init_payment_modes.py diff --git a/Back-End/School/management/commands/init_data.py b/Back-End/School/management/commands/init_data.py new file mode 100644 index 0000000..2dee892 --- /dev/null +++ b/Back-End/School/management/commands/init_data.py @@ -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')) \ No newline at end of file diff --git a/Back-End/School/management/commands/init_payment_modes.py b/Back-End/School/management/commands/init_payment_modes.py new file mode 100644 index 0000000..8d4abf5 --- /dev/null +++ b/Back-End/School/management/commands/init_payment_modes.py @@ -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')) \ No newline at end of file diff --git a/Back-End/School/models.py b/Back-End/School/models.py index 4c8b5d9..345a8e8 100644 --- a/Back-End/School/models.py +++ b/Back-End/School/models.py @@ -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()}" + diff --git a/Back-End/School/serializers.py b/Back-End/School/serializers.py index a3a76c7..44e1509 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, 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__' \ No newline at end of file diff --git a/Back-End/School/urls.py b/Back-End/School/urls.py index 1780b1b..e0938e5 100644 --- a/Back-End/School/urls.py +++ b/Back-End/School/urls.py @@ -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"), ] \ No newline at end of file diff --git a/Back-End/School/views.py b/Back-End/School/views.py index 35d27d6..fdb2657 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, 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) \ No newline at end of file + 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) \ No newline at end of file diff --git a/Back-End/start.py b/Back-End/start.py index 99da02b..c7de8d5 100644 --- a/Back-End/start.py +++ b/Back-End/start.py @@ -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: diff --git a/Front-End/src/app/[locale]/admin/structure/page.js b/Front-End/src/app/[locale]/admin/structure/page.js index 2d4afde..dd1360c 100644 --- a/Front-End/src/app/[locale]/admin/structure/page.js +++ b/Front-End/src/app/[locale]/admin/structure/page.js @@ -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} diff --git a/Front-End/src/app/lib/schoolAction.js b/Front-End/src/app/lib/schoolAction.js index 9d84f91..0d94378 100644 --- a/Front-End/src/app/lib/schoolAction.js +++ b/Front-End/src/app/lib/schoolAction.js @@ -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', diff --git a/Front-End/src/components/PaymentModeSelector.js b/Front-End/src/components/PaymentModeSelector.js index 874cb4b..eb69881 100644 --- a/Front-End/src/components/PaymentModeSelector.js +++ b/Front-End/src/components/PaymentModeSelector.js @@ -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 ( -
-
- -

Mode de paiement

- ( - {item.name} - )} - horizontal={true} - /> +
+
+ +

Modes de paiement

+
+
+ {paymentModesOptions.map((mode) => ( + + ))}
); diff --git a/Front-End/src/components/Structure/Tarification/FeesManagement.js b/Front-End/src/components/Structure/Tarification/FeesManagement.js index e693cb0..4c47f93 100644 --- a/Front-End/src/components/Structure/Tarification/FeesManagement.js +++ b/Front-End/src/components/Structure/Tarification/FeesManagement.js @@ -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} />
- {/*
+
handleEdit(`${BE_SCHOOL_PAYMENT_MODE_URL}`, id, updatedData, setRegistrationPaymentModes)} + type={0} /> -
*/} +
@@ -117,6 +122,14 @@ const FeesManagement = ({ registrationDiscounts, type={1} />
+
+ handleEdit(`${BE_SCHOOL_PAYMENT_MODE_URL}`, id, updatedData, setTuitionPaymentModes)} + type={1} + /> +
diff --git a/Front-End/src/utils/Url.js b/Front-End/src/utils/Url.js index 2bb8805..d12dff4 100644 --- a/Front-End/src/utils/Url.js +++ b/Front-End/src/utils/Url.js @@ -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`