chore: commit qui sert à rien

This commit is contained in:
N3WT DE COMPET
2025-02-13 17:13:31 +01:00
parent 9bf9c5f62d
commit cce78355a3
17 changed files with 210 additions and 143 deletions

View File

@ -0,0 +1,24 @@
from django.core.management.base import BaseCommand
from School.models import Establishment, StructureType
class Command(BaseCommand):
help = 'Initialize the establishment'
def handle(self, *args, **kwargs):
establishment_data = {
"name": "N3WT",
"address": "Société n3wt-innov 69 Chez LANA",
"total_capacity": 69,
"establishment_type": [StructureType.MATERNELLE, StructureType.PRIMAIRE],
"licence_code": ""
}
establishment, created = Establishment.objects.update_or_create(
name=establishment_data["name"],
defaults=establishment_data
)
if created:
self.stdout.write(self.style.SUCCESS('Establishment created successfully'))
else:
self.stdout.write(self.style.SUCCESS('Establishment updated successfully'))

View File

@ -1,5 +1,5 @@
from django.core.management.base import BaseCommand
from School.models import PaymentMode, PaymentModeType, FeeType
from School.models import PaymentMode, PaymentModeType, FeeType, Establishment
class Command(BaseCommand):
help = 'Initialize or update Payment Modes'
@ -8,6 +8,7 @@ class Command(BaseCommand):
self.create_or_update_payment_modes()
def create_or_update_payment_modes(self):
establishment = Establishment.objects.get(name="N3WT")
for fee_type in FeeType.choices:
fee_type_value = fee_type[0]
@ -18,7 +19,8 @@ class Command(BaseCommand):
mode=mode_value,
type=fee_type_value,
defaults={
'is_active': False
'is_active': False,
'establishment': establishment
}
)

View File

@ -1,7 +1,7 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from dateutil.relativedelta import relativedelta
from School.models import PaymentPlan, PaymentPlanType, FeeType
from School.models import PaymentPlan, PaymentPlanType, FeeType, Establishment
class Command(BaseCommand):
help = 'Initialize or update Payment Plans'
@ -11,6 +11,7 @@ class Command(BaseCommand):
def create_or_update_payment_plans(self):
current_date = timezone.now().date()
establishment = Establishment.objects.get(name="N3WT")
for fee_type in FeeType.choices:
fee_type_value = fee_type[0]
@ -21,7 +22,8 @@ class Command(BaseCommand):
type=fee_type_value,
defaults={
'due_dates': [current_date + relativedelta(months=1)],
'is_active': True
'is_active': True,
'establishment': establishment
}
)
@ -31,7 +33,8 @@ class Command(BaseCommand):
type=fee_type_value,
defaults={
'due_dates': [current_date + relativedelta(months=1+4*i) for i in range(3)],
'is_active': False
'is_active': False,
'establishment': establishment
}
)
@ -41,7 +44,8 @@ class Command(BaseCommand):
type=fee_type_value,
defaults={
'due_dates': [current_date + relativedelta(months=1+i) for i in range(10)],
'is_active': False
'is_active': False,
'establishment': establishment
}
)
@ -51,7 +55,8 @@ class Command(BaseCommand):
type=fee_type_value,
defaults={
'due_dates': [current_date + relativedelta(months=1+i) for i in range(12)],
'is_active': False
'is_active': False,
'establishment': establishment
}
)

View File

@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand
from Auth.models import Profile
from School.models import Speciality, Teacher, SchoolClass
from School.models import Speciality, Teacher, SchoolClass, Establishment
class Command(BaseCommand):
help = 'Initialize or update Fees and Discounts'
@ -108,6 +108,7 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS('Teachers initialized or updated successfully'))
def create_or_update_schoolClasses(self):
establishment = Establishment.objects.get(name="N3WT")
school_classes_data = [
{
"atmosphere_name": "Classe A",
@ -119,7 +120,8 @@ class Command(BaseCommand):
"type": 1,
"time_range": ["08:30", "17:30"],
"opening_days": [1, 2, 4, 5],
"teachers": [2] # ID of Severus Rogue
"teachers": [2], # ID of Severus Rogue
"establishment": establishment
},
{
"atmosphere_name": "Classe B",
@ -131,7 +133,8 @@ class Command(BaseCommand):
"type": 1,
"time_range": ["08:30", "17:30"],
"opening_days": [1, 2, 4, 5],
"teachers": [3] # ID of Minerva McGonagall
"teachers": [3], # ID of Minerva McGonagall
"establishment": establishment
},
{
"atmosphere_name": "Classe C",
@ -143,7 +146,8 @@ class Command(BaseCommand):
"type": 1,
"time_range": ["08:30", "17:30"],
"opening_days": [1, 2, 4, 5],
"teachers": [4] # ID of Pomona Chourave
"teachers": [4], # ID of Pomona Chourave
"establishment": establishment
}
]

View File

@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand
from Auth.models import Profile
from School.models import Fee, Discount, FeeType, DiscountType
from School.models import Fee, Discount, FeeType, DiscountType, Establishment
class Command(BaseCommand):
help = 'Initialize or update Fees and Discounts'
@ -10,41 +10,47 @@ class Command(BaseCommand):
self.create_or_update_discounts()
def create_or_update_fees(self):
establishment = Establishment.objects.get(name="N3WT")
fees_data = [
{
"name": "Frais d'inscription",
"base_amount": "150.00",
"description": "Montant de base",
"is_active": True,
"type": FeeType.REGISTRATION_FEE
"type": FeeType.REGISTRATION_FEE,
"establishment": establishment
},
{
"name": "Matériel",
"base_amount": "85.00",
"description": "Livres / jouets",
"is_active": True,
"type": FeeType.REGISTRATION_FEE
"type": FeeType.REGISTRATION_FEE,
"establishment": establishment
},
{
"name": "Sorties périscolaires",
"base_amount": "120.00",
"description": "Sorties",
"is_active": True,
"type": FeeType.REGISTRATION_FEE
"type": FeeType.REGISTRATION_FEE,
"establishment": establishment
},
{
"name": "Les colibris",
"base_amount": "4500.00",
"description": "TPS / PS / MS / GS",
"is_active": True,
"type": FeeType.TUITION_FEE
"type": FeeType.TUITION_FEE,
"establishment": establishment
},
{
"name": "Les butterflies",
"base_amount": "5000.00",
"description": "CP / CE1 / CE2 / CM1 / CM2",
"is_active": True,
"type": FeeType.TUITION_FEE
"type": FeeType.TUITION_FEE,
"establishment": establishment
}
]
@ -58,20 +64,23 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS('Fees initialized or updated successfully'))
def create_or_update_discounts(self):
establishment = Establishment.objects.get(name="N3WT")
discounts_data = [
{
"name": "Parrainage",
"amount": "10.00",
"description": "Réduction pour parrainage",
"discount_type": DiscountType.PERCENT,
"type": FeeType.TUITION_FEE
"type": FeeType.TUITION_FEE,
"establishment": establishment
},
{
"name": "Réinscription",
"amount": "100.00",
"description": "Réduction pour Réinscription",
"discount_type": DiscountType.PERCENT,
"type": FeeType.REGISTRATION_FEE
"type": FeeType.REGISTRATION_FEE,
"establishment": establishment
}
]

View File

@ -6,7 +6,6 @@ from django.contrib.postgres.fields import ArrayField
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
LEVEL_CHOICES = [
(1, 'Très Petite Section (TPS)'),
(2, 'Petite Section (PS)'),
@ -19,6 +18,23 @@ LEVEL_CHOICES = [
(9, 'Cours Moyen 2 (CM2)')
]
class StructureType(models.IntegerChoices):
MATERNELLE = 1, _('Maternelle')
PRIMAIRE = 2, _('Primaire')
SECONDAIRE = 3, _('Secondaire')
class Establishment(models.Model):
name = models.CharField(max_length=255, unique=True)
address = models.CharField(max_length=255)
total_capacity = models.IntegerField()
establishment_type = ArrayField(models.IntegerField(choices=StructureType.choices))
licence_code = models.CharField(max_length=100, blank=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Speciality(models.Model):
name = models.CharField(max_length=100)
updated_date = models.DateTimeField(auto_now=True)
@ -56,6 +72,7 @@ class SchoolClass(models.Model):
type = models.IntegerField(choices=PLANNING_TYPE_CHOICES, default=1)
time_range = models.JSONField(default=list)
opening_days = ArrayField(models.IntegerField(), default=list)
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='school_classes')
def __str__(self):
return self.atmosphere_name
@ -95,6 +112,7 @@ class Discount(models.Model):
discount_type = models.IntegerField(choices=DiscountType.choices, default=DiscountType.CURRENCY)
type = models.IntegerField(choices=FeeType.choices, default=FeeType.REGISTRATION_FEE)
updated_at = models.DateTimeField(auto_now=True)
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='discounts')
def __str__(self):
return self.name
@ -106,6 +124,7 @@ class Fee(models.Model):
is_active = models.BooleanField(default=True)
updated_at = models.DateTimeField(auto_now=True)
type = models.IntegerField(choices=FeeType.choices, default=FeeType.REGISTRATION_FEE)
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='fees')
def __str__(self):
return self.name
@ -115,6 +134,7 @@ class PaymentPlan(models.Model):
due_dates = ArrayField(models.DateField(), blank=True)
type = models.IntegerField(choices=FeeType.choices, default=FeeType.REGISTRATION_FEE)
is_active = models.BooleanField(default=False)
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='payment_plans')
def __str__(self):
return f"{self.get_frequency_display()} - {self.get_type_display()}"
@ -123,6 +143,7 @@ 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)
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='payment_modes')
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, PaymentMode
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan, PaymentMode, Establishment
from Auth.models import Profile
from N3wtSchool import settings, bdd
from django.utils import timezone
@ -200,4 +200,9 @@ class PaymentPlanSerializer(serializers.ModelSerializer):
class PaymentModeSerializer(serializers.ModelSerializer):
class Meta:
model = PaymentMode
fields = '__all__'
class EstablishmentSerializer(serializers.ModelSerializer):
class Meta:
model = Establishment
fields = '__all__'

View File

@ -16,7 +16,9 @@ from School.views import (
PaymentPlansView,
PaymentPlanView,
PaymentModesView,
PaymentModeView
PaymentModeView,
EstablishmentsView,
EstablishmentView
)
urlpatterns = [
@ -51,4 +53,9 @@ urlpatterns = [
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"),
re_path(r'^establishments$', EstablishmentsView.as_view(), name="establishments"),
re_path(r'^establishment$', EstablishmentView.as_view(), name='establishment'),
re_path(r'^establishment/([0-9]+)$', EstablishmentView.as_view(), name='establishment')
]

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, PaymentMode
from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer, DiscountSerializer, FeeSerializer, PaymentPlanSerializer, PaymentModeSerializer
from .models import Teacher, Speciality, SchoolClass, Planning, Discount, Fee, PaymentPlan, PaymentMode, Establishment
from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer, DiscountSerializer, FeeSerializer, PaymentPlanSerializer, PaymentModeSerializer, EstablishmentSerializer
from N3wtSchool import bdd
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
@ -400,4 +400,46 @@ class PaymentModeView(APIView):
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)
return JsonResponse(payment_mode_serializer.errors, safe=False, status=400)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class EstablishmentsView(APIView):
def get(self, request):
establishments=getAllObjects(Establishment)
establishments_serializer=EstablishmentSerializer(establishments, many=True)
return JsonResponse(establishments_serializer.data, safe=False, status=200)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class EstablishmentView(APIView):
def get(self, request, _id):
try:
establishment = Establishment.objects.get(id=_id)
establishment_serializer = EstablishmentSerializer(establishment)
return JsonResponse(establishment_serializer.data, safe=False)
except Establishment.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=404)
def post(self, request):
establishment_data = JSONParser().parse(request)
establishment_serializer = EstablishmentSerializer(data=establishment_data)
if establishment_serializer.is_valid():
establishment_serializer.save()
return JsonResponse(establishment_serializer.data, safe=False, status=201)
return JsonResponse(establishment_serializer.errors, safe=False, status=400)
def put(self, request, _id):
establishment_data = JSONParser().parse(request)
try:
establishment = Establishment.objects.get(id=_id)
except Establishment.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=404)
establishment_serializer = EstablishmentSerializer(establishment, data=establishment_data, partial=True)
if establishment_serializer.is_valid():
establishment_serializer.save()
return JsonResponse(establishment_serializer.data, safe=False)
return JsonResponse(establishment_serializer.errors, safe=False, status=400)
def delete(self, request, _id):
return delete_object(Establishment, _id)

View File

@ -4,7 +4,7 @@ from django.conf import settings
from django.utils.translation import gettext_lazy as _
from Auth.models import Profile
from School.models import SchoolClass, Fee, Discount
from School.models import SchoolClass, Fee, Discount, Establishment
from datetime import datetime
@ -209,6 +209,8 @@ class RegistrationForm(models.Model):
null=True,
blank=True)
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='register_forms')
def __str__(self):
return "RF_" + self.student.last_name + "_" + self.student.first_name

View File

@ -20,14 +20,15 @@ commands = [
["python", "manage.py", "makemigrations", "GestionMessagerie", "--noinput"],
["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_modes"]
["python", "manage.py", "migrate", "--noinput"]
]
test_commands = [
["python", "manage.py", "init_establishment"],
["python", "manage.py", "init_school_configuration"],
["python", "manage.py", "init_school_fees"]
["python", "manage.py", "init_school_fees"],
["python", "manage.py", "init_payment_plans"],
["python", "manage.py", "init_payment_modes"]
]
for command in commands:

View File

@ -1,6 +1,6 @@
'use client'
// src/components/Layout.js
import React from 'react';
import React, { useState, useEffect } from 'react';
import Sidebar from '@/components/Sidebar';
import { usePathname } from 'next/navigation';
import {useTranslations} from 'next-intl';
@ -25,6 +25,7 @@ import {
} from '@/utils/Url';
import { disconnect } from '@/app/lib/authAction';
import { fetchEstablishment } from '@/app/lib/schoolAction';
export default function Layout({
children,
@ -40,6 +41,9 @@ export default function Layout({
"settings": { "id": "settings", "name": t('settings'), "url": FE_ADMIN_SETTINGS_URL, "icon": Settings }
};
const [establishment, setEstablishment] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const pathname = usePathname();
const currentPage = pathname.split('/').pop();
@ -57,38 +61,50 @@ export default function Layout({
},
];
useEffect(() => {
setIsLoading(true);
fetchEstablishment()
.then(data => {
setEstablishment(data);
})
.catch(error => console.error('Error fetching establishment : ', error))
.finally(() => setIsLoading(false));
}, []);
return (
<>
<div className="flex min-h-screen bg-gray-50">
<Sidebar currentPage={currentPage} items={Object.values(sidebarItems)} className="h-full" />
<div className="flex flex-col flex-1">
{/* Header - h-16 = 64px */}
<header className="h-16 bg-white border-b border-gray-200 px-8 py-4 flex items-center justify-between z-9">
<div className="text-xl font-semibold">{headerTitle}</div>
<DropdownMenu
buttonContent={<img src="https://i.pravatar.cc/32" alt="Profile" className="w-8 h-8 rounded-full cursor-pointer" />}
items={dropdownItems}
buttonClassName=""
menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded shadow-lg"
/>
</header>
{/* Main Content */}
<div className="flex-1 flex flex-col">
{/* Content avec scroll si nécessaire */}
<div className="flex-1 overflow-auto">
{children}
</div>
{/* Footer - h-16 = 64px */}
<footer className="h-16 bg-white border-t border-gray-200 px-8 py-4 flex items-center justify-between">
<div className="text-sm font-light">
<span>&copy; {new Date().getFullYear()} N3WT-INNOV Tous droits réservés.</span>
<div className="text-sm font-light">{softwareName} - {softwareVersion}</div>
{!isLoading && (
<div className="flex min-h-screen bg-gray-50">
<Sidebar establishment={establishment} currentPage={currentPage} items={Object.values(sidebarItems)} className="h-full" />
<div className="flex flex-col flex-1">
{/* Header - h-16 = 64px */}
<header className="h-16 bg-white border-b border-gray-200 px-8 py-4 flex items-center justify-between z-9">
<div className="text-xl font-semibold">{headerTitle}</div>
<DropdownMenu
buttonContent={<img src="https://i.pravatar.cc/32" alt="Profile" className="w-8 h-8 rounded-full cursor-pointer" />}
items={dropdownItems}
buttonClassName=""
menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded shadow-lg"
/>
</header>
{/* Main Content */}
<div className="flex-1 flex flex-col">
{/* Content avec scroll si nécessaire */}
<div className="flex-1 overflow-auto">
{children}
</div>
<Logo className="w-8 h-8" />
</footer>
{/* Footer - h-16 = 64px */}
<footer className="h-16 bg-white border-t border-gray-200 px-8 py-4 flex items-center justify-between">
<div className="text-sm font-light">
<span>&copy; {new Date().getFullYear()} N3WT-INNOV Tous droits réservés.</span>
<div className="text-sm font-light">{softwareName} - {softwareVersion}</div>
</div>
<Logo className="w-8 h-8" />
</footer>
</div>
</div>
</div>
</div>
)}
</>
);
}

View File

@ -6,7 +6,9 @@ import {
BE_SCHOOL_FEES_URL,
BE_SCHOOL_DISCOUNTS_URL,
BE_SCHOOL_PAYMENT_PLANS_URL,
BE_SCHOOL_PAYMENT_MODES_URL
BE_SCHOOL_PAYMENT_MODES_URL,
BE_SCHOOL_ESTABLISHMENT_URL,
ESTABLISHMENT_ID
} from '@/utils/Url';
const requestResponseHandler = async (response) => {
@ -82,6 +84,11 @@ export const fetchTuitionPaymentModes = () => {
.then(requestResponseHandler)
}
export const fetchEstablishment = () => {
return fetch(`${BE_SCHOOL_ESTABLISHMENT_URL}/${ESTABLISHMENT_ID}`)
.then(requestResponseHandler)
}
export const createDatas = (url, newData, csrfToken) => {
return fetch(url, {
method: 'POST',

View File

@ -14,7 +14,7 @@ const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
</div>
);
function Sidebar({ currentPage, items }) {
function Sidebar({ establishment, currentPage, items }) {
const router = useRouter();
const [selectedItem, setSelectedItem] = useState(currentPage);
@ -31,7 +31,7 @@ function Sidebar({ currentPage, items }) {
{/* Sidebar */}
<div className="w-64 bg-white border-r border-gray-200 py-6 px-4">
<div className="flex items-center mb-8 px-2">
<div className="text-xl font-semibold">Ecole NEWT</div>
<div className="text-xl font-semibold">{establishment?.name}</div>
</div>
<nav className="space-y-1">

View File

@ -41,6 +41,10 @@ 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`;
export const BE_SCHOOL_ESTABLISHMENT_URL = `${BASE_URL}/School/establishment`;
// En attendant la gestion des sessions
export const ESTABLISHMENT_ID = 1;
// GESTION MESSAGERIE
export const BE_GESTIONMESSAGERIE_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messages`

View File

@ -1,62 +0,0 @@
# N3wt School
Logiciel de gestion d'école
## Maquette
Maquette figma : https://www.figma.com/design/1BtWHIQlJDTeue2oYblefV/Maquette-Logiciel-de-gestion-Ecole?node-id=42-296&t=AdaSQYWkLLf1o5OI-0
## Installation
### Installation de docker
Lien de téléchargement : https://www.docker.com/get-started/
# Lancement de monteschool
```sh
docker compose up -d
```
Lancement du front end
```sh
npm run dev
```
se connecter à localhost:8080
# Installation et développement en local
* [Installation Manuelle](./docs/Installation_Manuelle.md)
* [Convention de codage](./docs/CODING_GUIDELINES.md)
# Installer la vérification de commit (dans le projet principal)
```
npm i
npm run prepare
```
# Faire une livraison Mise en Production
```sh
# Faire la première release (1.0.0)
npm run release -- --first-release
# Faire une prerelease (RC,alpha,beta)
npm run release -- --prerelease <name>
# Faire une release
npm run release
# Forcer la release sur un mode particulier (majeur, mineur ou patch)
# npm run script
npm run release -- --release-as minor
# Or
npm run release -- --release-as 1.1.0
# ignorer les hooks de commit lors de la release
npm run release -- --no-verify
```

View File

@ -1,20 +0,0 @@
{
"name": "n3wt-school",
"version": "0.0.1",
"scripts": {
"prepare": "husky",
"release": "standard-version",
"update-version": "node scripts/update-version.js"
},
"standard-version": {
"scripts": {
"postbump": "git add Front-End/package.json Back-End/__version__.py && git commit --amend --no-edit"
}
},
"devDependencies": {
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"husky": "^9.1.6",
"standard-version": "^9.5.0"
}
}