diff --git a/.idea/SIRH.iml b/.idea/SIRH.iml
index c7476c0..47ac152 100644
--- a/.idea/SIRH.iml
+++ b/.idea/SIRH.iml
@@ -152,6 +152,8 @@
+
+
diff --git a/.idea/php.xml b/.idea/php.xml
index 6df8250..c4734e1 100644
--- a/.idea/php.xml
+++ b/.idea/php.xml
@@ -153,6 +153,8 @@
+
+
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
new file mode 100644
index 0000000..3fadc3d
--- /dev/null
+++ b/.idea/sqldialects.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/composer.json b/composer.json
index bf8f442..c7ed2ef 100644
--- a/composer.json
+++ b/composer.json
@@ -87,6 +87,7 @@
}
},
"require-dev": {
+ "doctrine/doctrine-fixtures-bundle": "^4.3",
"friendsofphp/php-cs-fixer": "^3.93",
"phpunit/phpunit": "^12.5"
}
diff --git a/composer.lock b/composer.lock
index 58cd62f..cb7c9e7 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "71d28cc0a29fa3f385b067186aa43678",
+ "content-hash": "b540b6cb25ef55c5eebccb57c76da584",
"packages": [
{
"name": "api-platform/doctrine-common",
@@ -8504,6 +8504,175 @@
],
"time": "2024-05-06T16:37:16+00:00"
},
+ {
+ "name": "doctrine/data-fixtures",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/data-fixtures.git",
+ "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/7a615ba135e45d67674bb623d90f34f6c7b6bd97",
+ "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/persistence": "^3.1 || ^4.0",
+ "php": "^8.1",
+ "psr/log": "^1.1 || ^2 || ^3"
+ },
+ "conflict": {
+ "doctrine/dbal": "<3.5 || >=5",
+ "doctrine/orm": "<2.14 || >=4",
+ "doctrine/phpcr-odm": "<1.3.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^14",
+ "doctrine/dbal": "^3.5 || ^4",
+ "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0",
+ "doctrine/orm": "^2.14 || ^3",
+ "ext-sqlite3": "*",
+ "fig/log-test": "^1",
+ "phpstan/phpstan": "2.1.31",
+ "phpunit/phpunit": "10.5.45 || 12.4.0",
+ "symfony/cache": "^6.4 || ^7",
+ "symfony/var-exporter": "^6.4 || ^7"
+ },
+ "suggest": {
+ "alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)",
+ "doctrine/mongodb-odm": "For loading MongoDB ODM fixtures",
+ "doctrine/orm": "For loading ORM fixtures",
+ "doctrine/phpcr-odm": "For loading PHPCR ODM fixtures"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\DataFixtures\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ }
+ ],
+ "description": "Data Fixtures for all Doctrine Object Managers",
+ "homepage": "https://www.doctrine-project.org",
+ "keywords": [
+ "database"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/data-fixtures/issues",
+ "source": "https://github.com/doctrine/data-fixtures/tree/2.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdata-fixtures",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-10-17T20:06:20+00:00"
+ },
+ {
+ "name": "doctrine/doctrine-fixtures-bundle",
+ "version": "4.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/DoctrineFixturesBundle.git",
+ "reference": "9e013ed10d49bf7746b07204d336384a7d9b5a4d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/9e013ed10d49bf7746b07204d336384a7d9b5a4d",
+ "reference": "9e013ed10d49bf7746b07204d336384a7d9b5a4d",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/data-fixtures": "^2.2",
+ "doctrine/doctrine-bundle": "^2.2 || ^3.0",
+ "doctrine/orm": "^2.14.0 || ^3.0",
+ "doctrine/persistence": "^2.4 || ^3.0 || ^4.0",
+ "php": "^8.1",
+ "psr/log": "^2 || ^3",
+ "symfony/config": "^6.4 || ^7.0 || ^8.0",
+ "symfony/console": "^6.4 || ^7.0 || ^8.0",
+ "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0",
+ "symfony/deprecation-contracts": "^2.1 || ^3",
+ "symfony/doctrine-bridge": "^6.4.16 || ^7.1.9 || ^8.0",
+ "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/dbal": "< 3"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "14.0.0",
+ "phpstan/phpstan": "2.1.11",
+ "phpunit/phpunit": "^10.5.38 || 11.4.14"
+ },
+ "type": "symfony-bundle",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Bundle\\FixturesBundle\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Doctrine Project",
+ "homepage": "https://www.doctrine-project.org"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony DoctrineFixturesBundle",
+ "homepage": "https://www.doctrine-project.org",
+ "keywords": [
+ "Fixture",
+ "persistence"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/DoctrineFixturesBundle/issues",
+ "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/4.3.1"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-fixtures-bundle",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-12-03T16:05:42+00:00"
+ },
{
"name": "evenement/evenement",
"version": "v3.0.2",
diff --git a/config/bundles.php b/config/bundles.php
index 746539c..1c5d047 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
+use Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle;
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
use Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle;
use Nelmio\CorsBundle\NelmioCorsBundle;
@@ -22,4 +23,5 @@ return [
ApiPlatformBundle::class => ['all' => true],
LexikJWTAuthenticationBundle::class => ['all' => true],
MonologBundle::class => ['all' => true],
+ DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
];
diff --git a/config/services.yaml b/config/services.yaml
index 9a7408a..f1d8a11 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -23,8 +23,10 @@ services:
resource: '../src/'
App\Repository\Contract\AbsenceReadRepositoryInterface: '@App\Repository\AbsenceRepository'
+ App\Repository\Contract\EmployeeContractPeriodReadRepositoryInterface: '@App\Repository\EmployeeContractPeriodRepository'
App\Repository\Contract\EmployeeScopedRepositoryInterface: '@App\Repository\EmployeeRepository'
App\Repository\Contract\WorkHourReadRepositoryInterface: '@App\Repository\WorkHourRepository'
+ App\Service\Contracts\EmployeeContractPeriodManagerInterface: '@App\Service\Contracts\EmployeeContractPeriodManager'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
diff --git a/doc/functional-rules.md b/doc/functional-rules.md
index e3f5509..c1d6a89 100644
--- a/doc/functional-rules.md
+++ b/doc/functional-rules.md
@@ -2,6 +2,9 @@
Ce document centralise les règles métier actuellement implémentées dans l'application.
+Document complementaire (rollover conges et checklist de lancement):
+- `doc/leave-rollover.md`
+
## 1) Utilisateurs et accès
- `ROLE_ADMIN`
@@ -138,11 +141,61 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer.
- action `Clôturer`:
- bouton actif uniquement s'il existe un contrat en cours non déjà clôturé à la date du jour
- ouvre un drawer en lecture seule (type/temps de travail/date de début)
- - seule la date de fin est saisissable (préremplie à aujourd'hui)
- - backend: en mode clôture, seule `contractEndDate` est acceptée
+ - champs saisissables:
+ - `contractEndDate` (prérempli à aujourd'hui)
+ - `contractPaidLeaveSettled` (checkbox "Soldé dans le solde de tout compte")
+ - backend: en mode clôture, le flag `contractPaidLeaveSettled` est persisté sur la période clôturée
- action `Ajouter`:
- conserve le flux d'ajout d'un nouveau contrat via drawer dédié
- disponible uniquement s'il n'y a pas de contrat en cours, ou si le contrat en cours a déjà une date de fin
+ - onglet `Congé`:
+ - endpoint de synthèse: `GET /api/employees/{id}/leave-summary?year=YYYY`
+ - phase 1 métier (`CDI`/`CDD` non forfait + `FORFAIT`):
+ - exercice CP:
+ - `CDI`/`CDD` non forfait: du `1er juin (YYYY-1)` au `31 mai (YYYY)` (paramètre `year` = année de fin d'exercice)
+ - `FORFAIT`: du `1er janvier (YYYY)` au `31 décembre (YYYY)` (paramètre `year` = année civile)
+ - contrats `39h` / `35h` / `25h` (et plus largement CDI/CDD non forfait hors `4h`):
+ - acquis annuel CP: `25`
+ - acquis annuel samedi: `5`
+ - en cours d'acquisition jours: `25/12 = 2,08` jours/mois
+ - en cours d'acquisition samedis: `5/12 = 0,42` samedi/mois (non detaille en UI)
+ - samedis acquis affiches: uniquement `opening_saturdays` (report N-1)
+ - contrat `4h`:
+ - acquis annuel CP: `10`
+ - acquis annuel samedi: `0`
+ - en cours d'acquisition: `0.83` jour/mois
+ - contrat `FORFAIT`:
+ - base annuelle: `jours ouvrés de l'exercice (lundi-vendredi, hors jours fériés métropole) - 218`
+ - prorata: en cas de démarrage/fin de contrat en cours d'année civile, le calcul ne couvre que l'intervalle actif du contrat dans l'année
+ - reste à prendre: `acquis - absences` (toutes absences, demi-journées incluses)
+ - pas de samedi (`0`)
+ - pas de jours en cours d'acquisition (`0`)
+ - fractionné: `0` (saisie RH ultérieure, non calculée automatiquement)
+ - pour `CDI`/`CDD` non forfait:
+ - pris CP: basé sur absences de type code `C` (CONGÉ), en tenant compte des demi-journées
+ - samedi pris: absences `C` posées le samedi (demi-journée incluse)
+ - restants = acquis - pris (borné à 0)
+ - pour `FORFAIT`:
+ - pris: basé sur toutes les absences (demi-journées incluses)
+ - restants = acquis - pris (borné à 0)
+ - report annuel:
+ - le reliquat (`restants`) de l'exercice précédent est reporté dans les acquis de l'exercice courant
+ - pour `CDI`/`CDD` non forfait: report séparé jours + samedis
+ - pour `FORFAIT`: report uniquement sur les jours
+ - si un solde d'ouverture existe en base (`employee_leave_balances`) pour l'exercice courant, ce solde devient la source prioritaire du report
+ - si une clôture de contrat est marquée `contractPaidLeaveSettled=true` sur l'exercice précédent, le report vers l'exercice suivant est remis à `0`
+ - si une clôture `contractPaidLeaveSettled=true` existe dans l'exercice courant, le calcul est réinitialisé à partir du lendemain de cette clôture (pas de continuité intra-exercice)
+ - lecture des compteurs:
+ - `acquis` = droits reportés de l'exercice N-1 (après application des règles de soldé)
+ - `en cours d'acquisition` = total droits générés sur l'exercice N (jours + samedis en cours), sans detail séparé en UI
+ - règle de consommation:
+ - les absences s'imputent d'abord sur `acquis`, puis sur `en cours d'acquisition`
+ - la prise sur `en cours d'acquisition` est autorisée (usage anticipé)
+ - `en cours d'acquisition` peut devenir négatif si la prise dépasse le généré (ex: `2.08 - 3 = -0.92`), puis se reconstitue avec les acquisitions suivantes
+ - date d'arret de calcul:
+ - les compteurs sont calculés jusqu'au dernier jour du mois précédent (le mois en cours est exclu)
+ - exemple: au `04/03/2026`, l'arret de calcul est le `28/02/2026` (ou `29/02` en année bissextile)
+ - hors périmètre phase 1: `INTERIM` (retour non supporté)
## 10) Notifications
diff --git a/doc/leave-rollover.md b/doc/leave-rollover.md
new file mode 100644
index 0000000..50e953e
--- /dev/null
+++ b/doc/leave-rollover.md
@@ -0,0 +1,226 @@
+# Rollover Conges - Regles et Mise en Production
+
+Document de reference pour expliquer le fonctionnement metier du report N-1 et preparer le lancement en production.
+
+## 1) Objectif
+
+Eviter les recalculs "depuis le debut du contrat" et fiabiliser les soldes.
+
+Principe:
+- le solde est stocké par exercice
+- au changement d'exercice, on ouvre la nouvelle période avec un "solde d'ouverture" (report N-1)
+- un indicateur de cloture (`contractPaidLeaveSettled`) permet de couper la continuité entre 2 contrats
+
+## 2) Exercices metier
+
+- `CDI` / `CDD` non forfait:
+ - exercice: `1er juin` au `31 mai`
+ - `year` = annee de fin d'exercice (ex: `2026` = 01/06/2025 -> 31/05/2026)
+- `FORFAIT`:
+ - exercice: `1er janvier` au `31 decembre`
+ - `year` = annee civile
+- `INTERIM`:
+ - hors perimetre conges
+
+## 3) Logique de compteurs
+
+- `acquis`:
+ - correspond au report N-1 (solde d'ouverture)
+- `en cours d'acquisition`:
+ - correspond aux droits generes sur l'exercice en cours
+- `pris`:
+ - non forfait: absences type `C` (conge)
+ - forfait: toutes absences
+- `restant`:
+ - `acquis + en_cours - pris` (borne a 0 dans l'affichage)
+
+## 4) Effet du "solde de tout compte"
+
+Le champ de cloture `contractPaidLeaveSettled` est saisi lors de la fermeture d'une periode contrat.
+
+- `false`:
+ - continuite des droits entre contrats
+- `true`:
+ - pas de reprise des droits precedents
+ - reset de continuite au lendemain de la date de cloture
+
+## 5) Table cible
+
+Table `employee_leave_balances` (une ligne par employe et exercice):
+- `employee_id`
+- `rule_code` (`CDI_CDD_NON_FORFAIT` ou `FORFAIT_218`)
+- `year`
+- `opening_days`
+- `opening_saturdays`
+- `accrued_days`
+- `accrued_saturdays` (optionnel selon implementation)
+- `taken_days`
+- `taken_saturdays`
+- `closing_days`
+- `closing_saturdays`
+- `is_locked`
+- `created_at`, `updated_at`
+
+Contrainte unique recommandee:
+- `(employee_id, rule_code, year)`
+
+Etat implementation:
+- la table est creee
+- le calcul de synthese conges lit en priorite `opening_days/opening_saturdays` de cette table quand une ligne existe pour `(employee, rule_code, year)`
+- si aucune ligne n'existe, le calcul reste base sur le report dynamique N-1
+- la commande `app:leave:rollover` calcule aussi le report dynamique N-1 si la ligne N-1 n'est pas encore persistée (pas de reset a 0 par defaut)
+
+### Definition des colonnes
+
+- `employee_id`:
+ - identifiant employe (FK vers `employees`)
+ - une ligne de solde par employe / regle / exercice
+- `rule_code`:
+ - code de regle appliquee (`CDI_CDD_NON_FORFAIT`, `FORFAIT_218`)
+ - permet de savoir quelles regles de calcul sont utilisees
+- `year`:
+ - annee d'exercice
+ - non forfait: annee de fin d'exercice (`2026` = 01/06/2025 -> 31/05/2026)
+ - forfait: annee civile (`2026` = 01/01/2026 -> 31/12/2026)
+- `opening_days`:
+ - report N-1 en jours (solde d'ouverture)
+- `opening_saturdays`:
+ - report N-1 "samedis" (0 pour forfait)
+- `accrued_days`:
+ - droits generes sur l'exercice courant (N)
+- `accrued_saturdays`:
+ - droits samedis generes sur N (0 pour forfait)
+- `taken_days`:
+ - jours poses sur l'exercice
+- `taken_saturdays`:
+ - samedis poses sur l'exercice (0 pour forfait)
+- `closing_days`:
+ - solde de cloture jours (`opening_days + accrued_days - taken_days`)
+- `closing_saturdays`:
+ - solde de cloture samedis (`opening_saturdays + accrued_saturdays - taken_saturdays`)
+- `is_locked`:
+ - `false` sur exercice ouvert (recalcul possible)
+ - `true` apres validation RH (exercice fige)
+- `created_at`, `updated_at`:
+ - trace technique creation / mise a jour
+
+## 6) Rollover automatique
+
+Commande quotidienne (cron) idempotente.
+
+- commande Symfony: `php bin/console app:leave:rollover`
+- comportement date metier:
+ - le `01/01`: traite uniquement `FORFAIT_218`
+ - le `01/06`: traite uniquement `CDI_CDD_NON_FORFAIT`
+ - les autres jours: sortie sans action
+- option manuelle: `--force` pour executer hors date metier (reprise/correction)
+
+Date d'effet:
+- forfait: au `1er janvier`
+- non forfait: au `1er juin`
+
+Traitement par employe:
+1. lire l'exercice precedent
+2. determiner le report:
+ - si cloture `paidLeaveSettled=true` sur la periode precedente => report `0`
+ - sinon report = `closing` exercice precedent
+3. creer la ligne du nouvel exercice avec ce report en `opening_*`
+4. initialiser `accrued/taken/closing` pour le nouvel exercice
+
+## 7) Donnees a fournir au go-live
+
+La RH doit fournir un import d'ouverture:
+
+Colonnes minimales:
+- `employee_identifier` (id interne ou matricule)
+- `rule_code`
+- `year`
+- `opening_days`
+- `opening_saturdays` (0 pour forfait)
+- `source_date` (date de reference du relevé RH)
+- `comment` (optionnel)
+
+Format recommande:
+- CSV UTF-8
+- separateur `;`
+- decimales en point (`7.5`)
+
+Exemple:
+```csv
+employee_id;rule_code;year;opening_days;opening_saturdays;source_date;comment
+42;CDI_CDD_NON_FORFAIT;2026;12.5;2;2026-05-31;Reprise fichier RH
+17;FORFAIT_218;2026;8;0;2025-12-31;Reprise fichier RH
+```
+
+## 8) Checklist mise en prod
+
+1. Valider le mapping employe RH -> employe applicatif
+2. Importer les soldes d'ouverture N-1
+3. Verifier 5 cas metier:
+ - CDI simple sans changement de contrat
+ - CDD -> CDI avec `paidLeaveSettled=false`
+ - CDD -> CDI avec `paidLeaveSettled=true`
+ - Forfait sur annee complete
+ - Forfait avec debut en cours d'annee
+4. Activer le cron de rollover
+5. Geler (`is_locked`) les exercices historicises valides
+
+Exemple cron (tous les jours a 02:10):
+Dev
+```cron
+10 2 * * * cd /var/www/html && php bin/console app:leave:rollover --no-interaction >> var/log/leave-rollover.log 2>&1
+```
+Prod
+```cron
+10 2 * * * cd /var/www/sirh && php bin/console app:leave:rollover --no-interaction >> var/log/leave-rollover.log 2>&1
+```
+Explication de la ligne cron:
+- `10 2 * * *`: planification
+ - `10` = minute
+ - `2` = heure
+ - `*` = tous les jours du mois
+ - `*` = tous les mois
+ - `*` = tous les jours de la semaine
+- `cd /var/www/html`: se place dans le dossier de l application Symfony
+- `php bin/console app:leave:rollover --no-interaction`: execute le rollover sans demander de confirmation
+ - hors `01/01` et `01/06`, la commande sort en no-op (normal)
+- `>> var/log/leave-rollover.log`: ajoute la sortie standard dans le fichier de log (sans ecraser l historique)
+- `2>&1`: redirige aussi les erreurs dans le meme fichier de log
+
+Execution manuelle forcee:
+```bash
+php bin/console app:leave:rollover --force --no-interaction
+```
+
+Exemple de verification rapide:
+```bash
+tail -n 50 /var/www/html/var/log/leave-rollover.log
+```
+
+## 9) Points de vigilance
+
+- Ne jamais recalculer les soldes historiques apres validation RH sans procedure explicite
+- Garder une trace de toute correction manuelle (auteur, date, motif)
+- Aligner strictement les regles UI et API sur les memes compteurs (pas de formule differente front/back)
+
+## 10) Regle de consommation des droits
+
+Regle metier:
+- un employe peut poser des conges en cours d'acquisition
+- la consommation se fait par ordre:
+ 1. `acquis` (report N-1)
+ 2. `en cours d'acquisition` (droits N)
+
+Effet attendu:
+- si `acquis = 0` et `en cours = 7.5`, puis prise de `7`, alors:
+ - `acquis` reste `0`
+ - `en cours` devient `0.5`
+- si `acquis = 0` et `en cours = 2.5`, puis prise de `3`, alors:
+ - `acquis` reste `0`
+ - `en cours` devient `-0.5` (dette)
+ - le mois suivant, une acquisition de `2.5` ramené `en cours` a `2.0`
+
+Formule de lecture recommandée:
+- `restant_acquis = max(0, acquis - pris)`
+- `reste_a_imputer_sur_en_cours = max(0, pris - acquis)`
+- `restant_en_cours = en_cours - reste_a_imputer_sur_en_cours` (valeur negative autorisee)
diff --git a/frontend/components/employees/ContractTab.vue b/frontend/components/employees/ContractTab.vue
index 9caf238..b464f26 100644
--- a/frontend/components/employees/ContractTab.vue
+++ b/frontend/components/employees/ContractTab.vue
@@ -86,6 +86,18 @@
La date de fin est obligatoire.
+
+
+
+