437 lines
24 KiB
Markdown
437 lines
24 KiB
Markdown
# Règles Fonctionnelles SIRH
|
||
|
||
Ce document centralise les règles métier actuellement implémentées dans l'application.
|
||
|
||
Documents complementaires:
|
||
- `doc/leave-rollover.md` (rollover conges et checklist de lancement)
|
||
- `doc/rtt-rollover.md` (rollover RTT et checklist de lancement)
|
||
|
||
## 1) Utilisateurs et accès
|
||
|
||
- `ROLE_ADMIN`
|
||
- accès complet aux écrans d'administration
|
||
- vue semaine des heures
|
||
- validation RH des lignes d'heures
|
||
- `ROLE_SELF`
|
||
- accès limité à son périmètre personnel
|
||
- Accès "Sites" (via `user_site_roles` avec rôle `SITE_ACCESS`)
|
||
- accès au périmètre des sites autorisés
|
||
- validation site des lignes d'heures
|
||
|
||
## 2) Contrats
|
||
|
||
- Le profil de temps de travail est porté par `Contract`:
|
||
- `trackingMode`: `TIME` ou `PRESENCE`
|
||
- `weeklyHours` (ex: 35, 39, 4, etc.)
|
||
- La nature RH est portée par période employé:
|
||
- `CDI`, `CDD`, `INTERIM`
|
||
- Historique des contrats employé:
|
||
- table `employee_contract_periods`
|
||
- un employé peut avoir plusieurs périodes
|
||
|
||
### Règles de période
|
||
|
||
- `CDI`:
|
||
- à la création d'une période: `endDate` doit être vide
|
||
- en clôture d'un contrat en cours: `endDate` peut être renseignée
|
||
- `CDD` / `INTERIM`:
|
||
- `endDate` obligatoire
|
||
- `endDate` ne peut pas être antérieure à `startDate`
|
||
|
||
## 3) Heures (vue jour)
|
||
|
||
- Visibilité des employés:
|
||
- vue jour: un employé sans contrat à la date sélectionnée est masqué
|
||
- vue semaine: un employé sans contrat sur aucun jour de la semaine est masqué
|
||
- même règle pour les heures classiques et les heures conducteurs
|
||
- Saisie par salarié et par date:
|
||
- matin / après-midi / soir
|
||
- pour `PRESENCE`: demi-journées matin/après-midi
|
||
- Sélecteur de temps:
|
||
- créneaux de 15 minutes uniquement (00:00, 00:15, ..., 23:45)
|
||
- saisie libre possible mais valeur vidée au blur si hors options
|
||
- Calculs affichés:
|
||
- `Jour`, `Nuit`, `Total`
|
||
- Heures de nuit:
|
||
- fenêtres `00:00-06:00` et `21:00-24:00`
|
||
- Date de modification (`updatedAt`):
|
||
- mise à jour uniquement quand un employé (`ROLE_SELF`) modifie ses propres heures
|
||
- non mise à jour lors de modifications admin ou chef de site
|
||
- affichée sous le nom de l'employé (visible admin uniquement)
|
||
|
||
## 4) Absences
|
||
|
||
- Les absences sont stockées par jour (découpage lors de l'écriture)
|
||
- Une absence peut être:
|
||
- journée complète
|
||
- demi-journée `AM` ou `PM`
|
||
- Colonne absence (vue jour):
|
||
- affiche le libellé
|
||
- fond coloré selon le type d'absence
|
||
- Calendrier congés: fond coloré selon la couleur du type d'absence (`AbsenceType.color`)
|
||
- demi-journée: dégradé diagonal
|
||
- journée complète: fond plein
|
||
|
||
### Effet absence sur les heures
|
||
|
||
- Absence `AM`:
|
||
- efface les heures du matin
|
||
- Absence `PM`:
|
||
- efface les heures d'après-midi et du soir
|
||
- Absence journée:
|
||
- efface toutes les plages horaires
|
||
|
||
### Absences "comptées comme travaillées"
|
||
|
||
- Si `countAsWorkedHours = true`:
|
||
- `TIME`: crédit de minutes selon contrat actif du jour
|
||
- `PRESENCE` (forfait): aucun crédit de présence (seules les checkboxes cochées comptent)
|
||
|
||
## 5) Validations des lignes d'heures
|
||
|
||
- Validation RH (`isValid`)
|
||
- action admin
|
||
- Validation site (`isSiteValid`)
|
||
- action chef de site
|
||
|
||
### Verrouillage
|
||
|
||
- Ligne validée RH:
|
||
- verrouillée pour modifications heures/absences
|
||
- Ligne validée site:
|
||
- verrouillée pour non-admin
|
||
- admin peut corriger
|
||
- Toute vraie modification d'une ligne:
|
||
- remet `isSiteValid = false`
|
||
- remet `isValid = false`
|
||
- Si aucun changement réel à l'enregistrement:
|
||
- les validations existantes ne sont pas altérées
|
||
|
||
## 6) Heures supplémentaires (vue semaine)
|
||
|
||
- Base de calcul:
|
||
- dépend du contrat actif par jour
|
||
- Tranche 25%:
|
||
- contrats <= 35h: de 35h à 43h
|
||
- contrats >= 39h: de 39h à 43h
|
||
- Tranche 50%:
|
||
- au-delà de 43h
|
||
- Date de début RTT (`RTT_START_DATE` dans `.env`):
|
||
- les semaines dont la fin est antérieure à cette date sont ignorées dans le calcul de récupération
|
||
- permet d'éviter les déficits fictifs avant la mise en service du logiciel
|
||
- Semaine en déficit (heures travaillées < heures contrat):
|
||
- le déficit est déduit du cumul RTT : d'abord des heures à 50%, puis des heures à 25%
|
||
- si aucun solde 50% ni 25%, les heures à 25% deviennent négatives
|
||
- Contrats CUSTOM (heures hebdo ≠ 35h et ≠ 39h, hors INTERIM/FORFAIT):
|
||
- référence heures sup = heures contractuelles réelles (ex: 4h → référence 4h)
|
||
- pas de bonus 25% ni 50% : 1 heure sup = 1 heure de récupération
|
||
- le déficit (travail < contrat) ne génère pas de récup mais n'impacte pas le solde
|
||
- Nature `INTERIM`:
|
||
- pas de bonus 25%
|
||
- pas de bonus 50%
|
||
- pas de total récup
|
||
|
||
## 6bis) Heures Conducteurs
|
||
|
||
- Écran dédié `/driver-hours` pour les employés dont le contrat est marqué `isDriver = true`
|
||
- Les conducteurs sont exclus de l'écran `/hours` classique
|
||
- Colonnes spécifiques (vue jour):
|
||
- Heure de jour (durée HH:MM via TimeSelect)
|
||
- Heure de nuit (durée HH:MM via TimeSelect)
|
||
- Heure atelier (durée HH:MM via TimeSelect)
|
||
- Total (somme jour + nuit + atelier, calculé)
|
||
- Petit déjeuner (checkbox)
|
||
- Déjeuner (checkbox)
|
||
- Dîner (checkbox)
|
||
- Nuitée (checkbox)
|
||
- Stockage backend:
|
||
- `dayHoursMinutes`, `nightHoursMinutes` et `workshopHoursMinutes` (entiers, minutes) sur `WorkHour`
|
||
- `hasBreakfast`, `hasLunch`, `hasDinner`, `hasOvernight` (booleans) sur `WorkHour`
|
||
- les champs time classiques (morning/afternoon/evening) sont mis à null pour les chauffeurs
|
||
- Absences `countAsWorkedHours=true`: les minutes créditées sont ajoutées aux heures de jour (vue jour et vue semaine), même logique que les employés classiques
|
||
- Validation: même logique que les heures classiques (`isValid`, `isSiteValid`, bulk)
|
||
- Vue semaine:
|
||
- jour/nuit/atelier par jour + indicateurs repas/dîner/nuitée
|
||
- panier de nuit (PN): affiché par jour si (nightMinutes > dayMinutes) OU (nightMinutes >= 240, soit au moins 4h de travail entre 21h et 6h), et total hebdo dans la colonne Jour/Nuit sem.
|
||
- totaux hebdo: jour, nuit, atelier, total, compteurs petit déj/déjeuner/dîner/nuitée
|
||
- les conducteurs utilisent `dayHoursMinutes + nightHoursMinutes + workshopHoursMinutes` pour le calcul RTT (au lieu des créneaux morning/afternoon/evening)
|
||
- Le flag `isDriver` est sur `EmployeeContractPeriod` (un employé peut changer de statut chauffeur selon la période)
|
||
- Exposé en API via un getter virtuel sur `Employee` (`employee:read`) qui résout depuis la période active
|
||
|
||
## 7) Fériés
|
||
|
||
- Les jours fériés sont identifiés et affichés
|
||
- Onglet congés: jours fériés affichés sur le calendrier avec fond `rgb(179, 229, 252)` et nom au survol
|
||
- Règle courante:
|
||
- absences bloquées sur jour férié
|
||
- saisie d'heures autorisée
|
||
|
||
## 8) Impression absences (PDF)
|
||
|
||
Filtres disponibles:
|
||
- période `from` / `to`
|
||
- sites
|
||
- nature de contrat (`CDI`, `CDD`, `INTERIM`)
|
||
- temps de travail (contrats de type Forfait, 35h, 39h, etc.)
|
||
|
||
Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer.
|
||
|
||
## 9) Employés
|
||
|
||
- Création employé:
|
||
- prénom, nom, site
|
||
- type de contrat (nature RH)
|
||
- temps de travail
|
||
- dates début/fin (selon règles nature)
|
||
- Modification employé:
|
||
- uniquement prénom, nom, site
|
||
- pas de modification de contrat depuis ce drawer
|
||
- Liste employés — filtre par statut de contrat:
|
||
- 3 options: "Avec contrat" (défaut), "Sans contrat", "Tous"
|
||
- "Avec contrat": employés ayant une période de contrat active à la date du jour
|
||
- "Sans contrat": employés sans période de contrat active
|
||
- "Tous": aucun filtrage sur le contrat
|
||
- Détail employé:
|
||
- onglet `Suivi contrat` avec affichage de l'historique des périodes de contrat
|
||
- chaque ligne expose: nature (`CDI`/`CDD`/`INTERIM`), contrat/temps de travail, date de début, date de fin (ou "En cours")
|
||
- action `Modifier` (clôture/solde de tout compte):
|
||
- bouton actif s'il existe un contrat en cours non clôturé, ou si le dernier contrat est terminé (sans contrat actif après)
|
||
- ouvre un drawer en lecture seule (type/temps de travail/date de début)
|
||
- champs saisissables:
|
||
- `contractEndDate` (prérempli à aujourd'hui si contrat en cours, à la date de fin existante si contrat terminé)
|
||
- `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
|
||
- cas du contrat déjà terminé: permet de modifier `paidLeaveSettled` et le commentaire sur le dernier contrat terminé (ex: solde de tout compte CDD)
|
||
- 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)
|
||
- en cas de début/fin en cours de mois, l'acquisition est proratisée au nombre de jours calendaires couverts dans le mois
|
||
- en cas de suspension en cours de mois, l'acquisition est proratisée en jours ouvrés (lun-ven hors fériés) travaillés / 22 (standard mensuel)
|
||
- arrêt maladie long (absences continues de type `M` > 1 mois):
|
||
- premier mois de maladie (date début + 1 mois calendaire): acquisition normale (`2,50`/mois)
|
||
- après le premier mois: acquisition réduite à `2,00`/mois (facteur `0,80` appliqué aux deux taux jours et samedis)
|
||
- en cas de mois partiellement couvert par la période réduite, le prorata est calculé en jours calendaires (jours normaux × taux normal + jours réduits × taux réduit)
|
||
- la détection est automatique à partir des absences MALADIE consécutives en base (tolérance de gap ≤ 3 jours)
|
||
- 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
|
||
- en cas de début/fin en cours de mois, l'acquisition est proratisée au nombre de jours calendaires couverts dans le mois
|
||
- en cas de suspension en cours de mois, l'acquisition est proratisée en jours ouvrés (lun-ven hors fériés) travaillés / 22
|
||
- contrat `FORFAIT`:
|
||
- base annuelle: `jours ouvrés de l'exercice (lundi-vendredi, hors jours fériés métropole) - 218`
|
||
- bonus weekend/férié: chaque jour travaillé un weekend ou jour férié donne 1 jour de congé supplémentaire (journée ≥ 5h = 1.0 jour, demi-journée > 0h et < 5h = 0.5 jour), sans plafond
|
||
- 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é: saisie manuelle par la RH via `PATCH /employees/{id}/fractioned-days`, stocké dans `employee_leave_balances.fractioned_days`. Les jours fractionnés sont ajoutés aux acquis et au reste à prendre.
|
||
- 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)
|
||
- paiement congés N-1: saisie RH via `PATCH /employees/{id}/paid-leave-days` (body: `paidLeaveDays`, `year`). Stocké dans `employee_leave_balances.paid_leave_days`. Les jours payés réduisent le stock N-1 **avant** l'attribution des jours pris : `disponible_N-1 = max(0, acquis_N-1 - payés)`, puis `pris_N-1 = min(disponible_N-1, total_pris)`, surplus pris basculé sur N. Reste à prendre N-1 = `max(0, disponible_N-1 - pris_N-1)`. Uniquement pour les contrats forfait.
|
||
- 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
|
||
- `en cours d'acquisition` est arrêté au dernier jour du mois précédent
|
||
- 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:
|
||
- `reste à prendre` est calculé en prévisionnel jusqu'à la fin de l'exercice
|
||
- les absences futures déjà posées sur l'exercice sont déduites du `reste à prendre`
|
||
- `en cours d'acquisition` reste calculé jusqu'au dernier jour du mois précédent
|
||
- exemple: au `11/03/2026`, l'exercice `2026` déduit les absences posées jusqu'au `31/05/2026`, mais l'acquisition reste arrêtée au `28/02/2026`
|
||
- hors périmètre phase 1: `INTERIM` (retour non supporté)
|
||
- onglet `RTT`:
|
||
- endpoint de synthèse: `GET /api/employees/{id}/rtt-summary?year=YYYY`
|
||
- exercice RTT: du `1er juin (YYYY-1)` au `31 mai (YYYY)` (paramètre `year` = année de fin d'exercice)
|
||
- affichage:
|
||
- détail hebdomadaire (semaine ISO) regroupé par mois
|
||
- total mensuel des minutes de récupération
|
||
- compteur global exercice = `report N-1 + acquis N`
|
||
- attribution mensuelle des semaines:
|
||
- une semaine ISO qui chevauche deux mois est affichée dans **les deux mois**, avec les valeurs réparties proportionnellement aux minutes travaillées de chaque portion
|
||
- le calcul des heures supplémentaires reste hebdomadaire (seuils 35h/39h/43h appliqués sur la semaine entière), seul l'affichage est scindé
|
||
- exemple: S14 lundi-mardi en mars, mercredi-dimanche en avril → la S14 apparaît en mars (avec la part des heures de lun-mar) et en avril (avec la part mer-dim)
|
||
- logique de calcul:
|
||
- base identique aux calculs d'heures supplémentaires de la vue semaine Heures
|
||
- minutes de récupération hebdomadaires = `HS totales + bonus 25% + bonus 50%`
|
||
- contrats `INTERIM` et suivi `PRESENCE`: récupération à `0`
|
||
- date limite de calcul: uniquement les semaines terminées (jusqu'au dernier dimanche), **ou** la semaine en cours si tous les jours existants sont validés RH (`isValid = true`). En cas de fin de contrat en milieu de semaine, seuls les jours jusqu'à la date de fin sont vérifiés.
|
||
- compteur global:
|
||
- affiché en **jours** (1 jour = 7h = 420 minutes)
|
||
- report:
|
||
- le report N-1 correspond à la somme des minutes de récupération calculées sur l'exercice précédent
|
||
- si une ligne existe dans `employee_rtt_balances` pour `(employee, year)`, le champ `opening_minutes` est utilisé en priorité
|
||
- sinon, le calcul dynamique sur l'exercice N-1 est effectué
|
||
- rollover automatique:
|
||
- commande: `php bin/console app:rtt:rollover`
|
||
- s'exécute le `1er juin` (même cron que le rollover congés)
|
||
- calcule le total récup N-1 et le persiste en `opening_minutes` du nouvel exercice
|
||
- idempotent (ne recrée pas si la ligne existe)
|
||
- paiement RTT:
|
||
- saisie RH via `PATCH /employees/{id}/rtt-payments` (body: `month`, `minutes`, `rate`)
|
||
- stocké dans `employee_rtt_payments` (employee, year, month, minutes, rate)
|
||
- `rate`: taux de majoration, valeurs `25` ou `50`
|
||
- les heures payées sont soustraites du disponible RTT (`availableMinutes -= totalPaidMinutes`)
|
||
- affichage: 2 lignes par mois dans le tableau (25% et 50%)
|
||
- colonnes Total 25% et Total 50%: somme base + bonus de chaque tranche
|
||
- ligne Report N-1 (carry rollover): affichée en juin uniquement si carry > 0
|
||
- ligne Report mois précédent: solde cumulé (carry N-1 + semaines antérieures − paiements antérieurs), affichée à partir de juillet (masquée si nul)
|
||
- Reste = Report cumulé + Total du mois − Payé du mois (balance courante en fin de mois)
|
||
- affichage:
|
||
- le compteur global RTT est affiché en **heures** (format `Xh00`)
|
||
|
||
## 10) Export récap. congés & RTT (PDF)
|
||
|
||
- Accessible depuis la page Employés via le bouton "Export récap. congés" (réservé `ROLE_ADMIN`)
|
||
- Clic direct (pas de drawer), génère un PDF A4 portrait à la date du jour
|
||
- Endpoint: `GET /api/leave-recap/print`
|
||
- Seuls les employés avec contrat actif sont inclus
|
||
- Données groupées par site
|
||
|
||
### Colonnes du tableau
|
||
|
||
| Colonne | Logique |
|
||
|---------|---------|
|
||
| Nom | lastName + firstName |
|
||
| Contrat | Contract.name |
|
||
| CP N-1 restant | CDI/CDD: acquis N-1 − pris sur N-1. Forfait: report N-1 restant |
|
||
| Samedi restant | CDI/CDD: samedis acquis N-1 − pris. Forfait: `-` |
|
||
| CP N | Forfait: jours acquis année civile. Non-forfait: en cours d'acquisition |
|
||
| RTT | Minutes disponibles (report N-1 + acquis N - payés). Format `X h Y m`. Forfait et INTERIM: `-` |
|
||
|
||
## 11) Récapitulatif Salaire (PDF mensuel)
|
||
|
||
- Accessible depuis la page Employés via le bouton "Récap. Salaire" (réservé `ROLE_ADMIN`)
|
||
- Sélecteur de mois (défaut = mois courant), génère un PDF A3 paysage
|
||
- Endpoint: `GET /api/salary-recap/print?month=YYYY-MM`
|
||
- Données groupées par site, un en-tête par site
|
||
|
||
### Colonnes du tableau
|
||
|
||
| Colonne | Source | Logique |
|
||
|---------|--------|---------|
|
||
| Nom | Employee | firstName + lastName |
|
||
| Base | Contract.name | Via EmployeeContractResolver pour le mois |
|
||
| Jour de présence Cadre | WorkHour | Uniquement FORFAIT (PRESENCE). Somme isPresentMorning (0.5) + isPresentAfternoon (0.5) |
|
||
| Heures de nuit | WorkHour | Non-chauffeurs: calcul intervalles nuit (00:00-06:00, 21:00-24:00). Chauffeurs: somme nightHoursMinutes |
|
||
| Panier de nuit | WorkHour | Nombre de jours où (nightMinutes > dayMinutes) OU (nightMinutes >= 240, soit 4h entre 21h-6h) |
|
||
| Heures payés | EmployeeRttPayment | Somme base25Minutes + base50Minutes du mois, convertie en heures |
|
||
| Congés - Nombre | Absence code 'C' | Jours (demi-journées = 0.5) |
|
||
| Congés - Date | Absence code 'C' | Dates formatées dd/mm |
|
||
| Maladie - Nombre | Absence code 'M' ou 'AT' | Jours (demi-journées = 0.5) |
|
||
| Maladie - Date | Absence code 'M' ou 'AT' | Dates formatées dd/mm |
|
||
| CHAUFFEUR - PDJ | WorkHour.hasBreakfast | Comptage mois (chauffeurs uniquement) |
|
||
| CHAUFFEUR - REPAS | WorkHour.hasLunch + hasDinner | Comptage mois (chauffeurs uniquement) |
|
||
| CHAUFFEUR - NUITEE | WorkHour.hasOvernight | Comptage mois (chauffeurs uniquement) |
|
||
| CHAUFFEUR - samedi | WorkHour (samedi) | Samedis travaillés (chauffeurs uniquement) |
|
||
| Observations | — | Colonne vide pour saisie manuelle |
|
||
|
||
## 12) Frais
|
||
|
||
- Onglet "Frais" sur la fiche employé (icône `mdi:account-cash-outline`)
|
||
- Entité `MileageAllowance` (table `mileage_allowances`)
|
||
- Champs:
|
||
- `month` (mois, obligatoire)
|
||
- `kilometers` (nombre de km, optionnel)
|
||
- `amount` (montant en €, optionnel)
|
||
- `comment` (commentaire, optionnel)
|
||
- `receiptPath` / `receiptName` (justificatif Km, PDF)
|
||
- `amountReceiptPath` / `amountReceiptName` (justificatif Montant, PDF)
|
||
- Règle de validation:
|
||
- le mois est obligatoire
|
||
- au moins un des deux champs `kilometers` ou `amount` doit être > 0
|
||
- les deux peuvent être remplis simultanément
|
||
- Tableau: colonnes Mois, Nombre de Km, Montant €, Commentaire, Justif. Km, Justif. Montant
|
||
- Deux justificatifs distincts (upload PDF uniquement):
|
||
- Justificatif Km : upload via `/mileage_allowances/{id}/receipt`, téléchargement via GET même URL
|
||
- Justificatif Montant : upload via `/mileage_allowances/{id}/amount-receipt`, téléchargement via GET même URL
|
||
- La suppression d'un frais supprime les deux fichiers justificatifs du disque
|
||
|
||
## 13) Observations
|
||
|
||
- Onglet "Observation" sur la fiche employé (icône `mdi:note-text-outline`)
|
||
- Entité `Observation` (table `observations`)
|
||
- Champs:
|
||
- `month` (mois, obligatoire)
|
||
- `content` (texte d'observation, obligatoire)
|
||
- Contrainte: une seule observation par mois par employé (unique sur `employee_id + month`)
|
||
- Tableau: colonnes Mois | Observation
|
||
- Drawer avec champs mois (`type="month"`) et textarea "Observation"
|
||
- CRUD standard: création, modification, suppression avec confirmation
|
||
|
||
## 14) Verrouillage utilisateur
|
||
|
||
- Champ `isLocked` (boolean, default false) sur l'entité `User`
|
||
- Un admin peut verrouiller/déverrouiller un utilisateur depuis la page Utilisateurs (checkbox dans le drawer)
|
||
- Un utilisateur verrouillé ne peut plus se connecter (vérification via `UserChecker` sur les firewalls `login` et `api`)
|
||
- Colonne "Statut" dans le tableau utilisateurs avec label "Actif" (vert) ou "Verrouillé" (rouge)
|
||
|
||
## 15) Notifications
|
||
|
||
- Icône cloche en topbar:
|
||
- badge = nombre de notifications non lues
|
||
- ouverture panneau = liste des non lues
|
||
- fermeture panneau = marquage "lu" en masse
|
||
|
||
### Règle métier de déclenchement
|
||
|
||
- Les notifications de validation site ne sont pas envoyées ligne par ligne.
|
||
- Une notification est créée uniquement quand un chef de site termine la validation complète:
|
||
- condition: plus aucune ligne `work_hours` du site à la date concernée avec `isSiteValid = false`
|
||
- destinataires: utilisateurs `ROLE_ADMIN`
|
||
|
||
## 16) Export PDF des heures annuelles
|
||
|
||
- Accessible depuis la fiche employé (bouton imprimante à droite du nom)
|
||
- Ouvre un drawer pour choisir l'année (civile, Jan-Déc)
|
||
- Génère un PDF avec le détail jour par jour des heures de l'employé
|
||
- Seuls les jours avec heures saisies ou absence sont affichés
|
||
|
||
### Colonnes selon le mode de suivi
|
||
|
||
- **TIME (non-chauffeur)**: Date | Absence | Début matin | Fin matin | Début après-midi | Fin après-midi | Début soir | Fin soir | Total
|
||
- **PRESENCE (forfait)**: Date | Absence | Présence matin | Présence après-midi | Total
|
||
- **Chauffeur**: Date | Absence | Heures jour | Heures nuit | Heures atelier | Total
|
||
|
||
### Changement de contrat en cours d'année
|
||
|
||
- Si l'employé change de mode de suivi (TIME/PRESENCE) ou de statut chauffeur en cours d'année, le PDF affiche des sections séparées avec les colonnes adaptées à chaque période
|
||
- Le nom du contrat est affiché en sous-titre de chaque section
|
||
|
||
### Calcul du total
|
||
|
||
- TIME non-chauffeur: somme des créneaux matin + après-midi + soir, plus minutes créditées des absences `countAsWorkedHours`
|
||
- Chauffeur: `dayHoursMinutes + nightHoursMinutes + workshopHoursMinutes` + minutes créditées
|
||
- PRESENCE: 0.5 par demi-journée présente (matin/après-midi), max 1.0
|
||
|
||
### Nom du fichier
|
||
|
||
- Format: `{nom}_{prenom}_{annee}.pdf`
|