Files
SIRH/doc/functional-rules.md
tristan 90e63a463e
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
feat : autoriser la création d'absences sur les jours fériés depuis le calendrier
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 11:04:57 +02:00

26 KiB
Raw Blame History

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
  • Source: API calendrier.api.gouv.fr/jours-feries/ via PublicHolidayService (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ône mdi:calendar-star), distinct du pill absence
  • 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 countAsWorkedHours du 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

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: -

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éfaut false, activé au create/edit user)
  • Le flag s'applique à tous les profils, y compris admin (pas de bypass)
  • Scoping :
    • ROLE_ADMIN : tous les employés
    • ROLE_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)
    • isValid n'entre PAS en compte : cutoff purement temporel
    • Les heures et absences postérieures au cutoff sont ignorées dans les calculs
  • 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 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