Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
| Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [x] Pas de régression - [x] TU/TI/TF rédigée - [x] TU/TI/TF OK - [x] CHANGELOG modifié Reviewed-on: #19 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
28 KiB
28 KiB
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_rolesavec rôleSITE_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:TIMEouPRESENCEweeklyHours(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
- table
Règles de période
CDI:- à la création d'une période:
endDatedoit être vide - en clôture d'un contrat en cours:
endDatepeut être renseignée
- à la création d'une période:
CDD/INTERIM:endDateobligatoire
endDatene 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:00et21:00-24:00
- fenêtres
- 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)
- mise à jour uniquement quand un employé (
- Libellé nature de contrat (CDI/CDD/Intérim) affiché sous le nom:
- résolu à la date filtrée (période de contrat couvrant ce jour), pas à aujourd'hui
- masqué si aucun contrat à cette date (cas rarissime en vue jour puisque l'employé est alors déjà filtré)
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
AMouPM
- 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
- Visibilité des employés dans le Calendrier:
- un employé est affiché si au moins une de ses périodes de contrat intersecte le mois affiché
- un employé dont toutes les périodes se terminent avant le 1er du mois (ou commencent après la fin du mois) est masqué
- même logique que l'écran Heures : « pas de contrat sur la période → masqué »
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 jourPRESENCE(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
- remet
- 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_DATEdans.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
- agence d'intérim optionnelle (table
interim_agencies): affichée sur la fiche employé et le détail contrat sous la forme "Intérim (NomAgence)"
6bis) Heures Conducteurs
- Écran dédié
/driver-hourspour les employés dont le contrat est marquéisDriver = true - Les conducteurs sont exclus de l'écran
/hoursclassique - 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,nightHoursMinutesetworkshopHoursMinutes(entiers, minutes) surWorkHourhasBreakfast,hasLunch,hasDinner,hasOvernight(booleans) surWorkHour- 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 + workshopHoursMinutespour le calcul RTT (au lieu des créneaux morning/afternoon/evening)
- Le flag
isDriverest surEmployeeContractPeriod(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
- Source: API
calendrier.api.gouv.fr/jours-feries/viaPublicHolidayService(cache 30j) - Exclusions configurables: variable d'env
EXCLUDED_PUBLIC_HOLIDAYS(liste de libellés séparés par virgules). Par défaut"Lundi de Pentecôte"— journée de solidarité généralement travaillée. Le filtre s'applique à tous les consommateurs (frontend + calculs backend) en amont du retour du service. - Onglet congés: jours fériés affichés sur le calendrier avec fond
rgb(179, 229, 252)et nom au survol - Écran Heures et Heures Conducteurs (vue jour): le nom du férié est affiché dans la colonne Absence sous forme de pill (fond
#b3e5fc, icônemdi:calendar-star), distinct du pill absence - Écran Heures et Heures Conducteurs (vue semaine): la cellule du jour férié prend le fond
#b3e5fcquand l'employé n'a pas d'absence ce jour-là, avec le nom du férié au survol (title). Si une absence est posée, la couleur de l'absence prime ; letitlecumule les deux libellés (Absence — Férié : Nom). - Règle courante:
- absences autorisées sur jour férié (création/édition depuis l'écran Heures et le Calendrier). Quand une absence est posée, le crédit virtuel férié est désactivé — c'est le
countAsWorkedHoursdu type d'absence qui pilote - saisie d'heures ou de jours de présence autorisée — les heures saisies comptent normalement dans le total hebdo et le calcul RTT
- la référence hebdomadaire n'est pas réduite par un férié: un salarié qui ne saisit rien sur un férié est en déficit de la journée correspondante
- absences autorisées sur jour férié (création/édition depuis l'écran Heures et le Calendrier). Quand une absence est posée, le crédit virtuel férié est désactivé — c'est le
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.
Seuls les employés dont au moins une période de contrat intersecte la période imprimée [from, to] apparaissent dans le PDF (même règle que la vue Calendrier). Un salarié parti avant la période (ex. contrat terminé en avril, impression de mai) est exclu. Borne calculée sur la date seule, côté backend (AbsencePrintProvider::hasContractInRange).
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 contratavec 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
contractPaidLeaveSettledest persisté sur la période clôturée - cas du contrat déjà terminé: permet de modifier
paidLeaveSettledet 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/CDDnon forfait +FORFAIT):- exercice CP:
CDI/CDDnon forfait: du1er juin (YYYY-1)au31 mai (YYYY)(paramètreyear= année de fin d'exercice)FORFAIT: du1er janvier (YYYY)au31 décembre (YYYY)(paramètreyear= année civile)
- contrats
39h/35h/25h(et plus largement CDI/CDD non forfait hors4h):- acquis annuel CP:
25 - acquis annuel samedi:
5 - en cours d'acquisition jours:
25/12 = 2,08jours/mois - en cours d'acquisition samedis:
5/12 = 0,42samedi/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 (facteur0,80appliqué 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)
- premier mois de maladie (date début + 1 mois calendaire): acquisition normale (
- samedis acquis affiches: uniquement
opening_saturdays(report N-1)
- acquis annuel CP:
- contrat
4h:- acquis annuel CP:
10 - acquis annuel samedi:
0 - en cours d'acquisition:
0.83jour/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
- acquis annuel CP:
- 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)
- base annuelle:
- fractionné: saisie manuelle par la RH via
PATCH /employees/{id}/fractioned-days, stocké dansemployee_leave_balances.fractioned_days. Les jours fractionnés sont ajoutés aux acquis et au reste à prendre. - pour
CDI/CDDnon forfait:- pris CP: basé sur absences de type code
C(CONGÉ), en tenant compte des demi-journées - samedi pris: absences
Cposées le samedi (demi-journée incluse) - restants = acquis - pris (borné à 0)
- pris CP: basé sur absences de type code
- 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é dansemployee_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), puispris_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/CDDnon 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=truesur l'exercice précédent, le report vers l'exercice suivant est remis à0 - si une clôture
contractPaidLeaveSettled=trueexiste dans l'exercice courant, le calcul est réinitialisé à partir du lendemain de cette clôture (pas de continuité intra-exercice)
- le reliquat (
- 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 UIen cours d'acquisitionest arrêté au dernier jour du mois précédent
- règle de consommation:
- les absences s'imputent d'abord sur
acquis, puis suren cours d'acquisition - la prise sur
en cours d'acquisitionest autorisée (usage anticipé) en cours d'acquisitionpeut 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
- les absences s'imputent d'abord sur
- date d'arret de calcul:
reste à prendreest 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'acquisitionreste calculé jusqu'au dernier jour du mois précédent- exemple: au
11/03/2026, l'exercice2026déduit les absences posées jusqu'au31/05/2026, mais l'acquisition reste arrêtée au28/02/2026
- exercice CP:
- hors périmètre phase 1:
INTERIM(retour non supporté)
- endpoint de synthèse:
- onglet
RTT:- endpoint de synthèse:
GET /api/employees/{id}/rtt-summary?year=YYYY - exercice RTT: du
1er juin (YYYY-1)au31 mai (YYYY)(paramètreyear= 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
INTERIMet suiviPRESENCE: 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_balancespour(employee, year), le champopening_minutesest 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_minutesdu nouvel exercice - idempotent (ne recrée pas si la ligne existe)
- commande:
- 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, valeurs25ou50- les heures payées sont soustraites du disponible RTT (
availableMinutes -= totalPaidMinutes) - affichage: 2 lignes par mois dans le tableau (25% et 50%)
- saisie RH via
- colonnes Total 25% et Total 50%: somme base + bonus de chaque tranche
- colonne Cumul (dernière colonne): solde RTT à la fin de chaque semaine =
report N-1 + somme totalMinutes des semaines jusqu'à celle-ci − paiements RTT des mois antérieurs au mois de la semaine. Le paiement d'un mois M n'est déduit qu'à partir des semaines du mois M+1 (cohérent avec la logique de la ligne "Report mois précédent"). Permet la comparaison ligne à ligne avec un suivi RH externe (Excel) - 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)
- le compteur global RTT est affiché en heures (format
- endpoint de synthèse:
- onglet
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: restant sur quota année civile (acquis − pris depuis N, sans toucher au stock N-1). Non-forfait: en cours d'acquisition |
| RTT | Minutes disponibles (report N-1 + acquis N - payés). Format X h Y m. Forfait et INTERIM: - |
10bis) Écran Récap. congés (tableau)
- Complément de l'export PDF : même logique de calcul, mais accessible aux employés et chefs de site
- Endpoint:
GET /api/leave-recap - Accès conditionné au flag
User.hasLeaveRecapAccess(défautfalse, activé au create/edit user) - Le flag s'applique à tous les profils, y compris admin (pas de bypass)
- Scoping :
ROLE_ADMIN: tous les employésROLE_USER(chef de site) : employés des sites autorisés (UserSiteRole)ROLE_SELF: uniquement son employé lié
- Cutoff temporel : le récap est figé à la fin de la semaine S-2 (dimanche 23:59:59)
- Formule :
cutoffDate = dimanche(lundi_semaine_courante − 14 jours) - Exemple : mardi 14/04/2026 (S16) → dimanche 05/04/2026 (fin S14)
isValidn'entre PAS en compte : cutoff purement temporel- Les heures et absences postérieures au cutoff sont ignorées dans les calculs
- Formule :
- Colonnes identiques au PDF (voir §10)
- Détails techniques : voir
doc/leave-recap-screen.md
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 | Somme sur le mois : +1 par déjeuner coché et +1 par dîner coché (un jour avec les deux compte 2 repas, 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(tablemileage_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
kilometersouamountdoit ê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
- Justificatif Km : upload via
- 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(tableobservations) - 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
UserCheckersur les firewallsloginetapi) - 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_hoursdu site à la date concernée avecisSiteValid = false - destinataires: utilisateurs
ROLE_ADMIN
- condition: plus aucune ligne
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, absence, week-end ou jour férié sont affichés
- Les jours fériés apparaissent toujours sur une ligne dédiée (fond bleu clair) avec la mention "Férié : {nom}" dans la colonne Absence (même si aucune saisie)
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
- Jour férié Lun-Ven (hors Forfait, sans absence) :
total = max(saisie + crédit absence, référence contractuelle)— même règle que l'écran Heures (cf.HolidayVirtualHoursResolver). Pour Forfait : pas de crédit virtuel, la ligne férié affiche juste l'éventuelle présence saisie.
Nom du fichier
- Format:
{nom}_{prenom}_{annee}.pdf