Files
SIRH/doc/overtime-contingent.md
T
tristan 0a9b26d31e
Auto Tag Develop / tag (push) Successful in 6s
feat(overtime-contingent) : heures supp structurelles (>35h) ajoutées au contingent
Les heures contractuelles au-delà de 35h (ex. 39h → 17,33h décimales = 17h20/mois)
sont payées chaque mois sans transiter par les paiements RTT (référence 39h). Elles
manquaient au contingent. Ajout via StructuralOvertimeContingentCalculator :
(weeklyHours-35)×260 min/mois, généralisé aux contrats non-forfait/non-intérim >35h,
proratisé aux jours sous contrat. Branché sur l'encart fiche et l'export PDF.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 08:57:26 +02:00

50 lines
2.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Contingent d'heures supplémentaires payées
## Objectif
Suivre, par année civile (JanvDéc), les heures supplémentaires payées de chaque employé
non-forfait (chauffeurs inclus) face au plafond légal annuel.
## Règles
- **Heures payées** = `base25 + base50` (en minutes), hors majoration (bonus), **+ heures
structurelles** (voir ci-dessous).
- **Plafond** : 350 h pour les chauffeurs (contrat courant `isDriver`), 220 h sinon.
- **Périmètre** : non-forfait uniquement (FORFAIT exclus, ni RTT ni heures supp payées).
## Heures supplémentaires structurelles
Les heures contractuelles **au-delà de 35h** (durée légale) sont des heures supplémentaires
payées **chaque mois**, qui ne transitent pas par les paiements RTT (la référence d'un 39h est
39h, pas 35h) mais comptent dans le contingent légal.
- Montant mensuel plein = `(weeklyHours 35) × 52/12` h = `(weeklyHours 35) × 260` min.
Pour un 39h : `4 × 260 = 1040` min = **17,33 h/mois**.
- **Généralisé** à tout contrat non-forfait/non-intérim dont `weeklyHours > 35` (ex. custom
40h → 21,67 h/mois). Contrats ≤ 35h, FORFAIT, INTERIM → 0.
- **Proratisé** au nombre de jours réellement sous contrat dans le mois (entrée/sortie en cours
de mois). Itère les périodes de contrat (`employee.contractPeriods`), pas de requête jour/jour.
- Cœur partagé : `App\Service\WorkHours\StructuralOvertimeContingentCalculator`
(`monthlyStructuralMinutes` / `totalStructuralMinutes`). Ajouté au total des paiements RTT
côté provider (encart fiche) **et** export builder (PDF).
## Mapping exercice → année civile
Les paiements RTT (`EmployeeRttPayment`) sont stockés par **exercice** (`year` = Juin N-1 →
Mai N) + `month` (112). L'année civile d'un paiement :
annéeCivile = month >= 6 ? exerciseYear - 1 : exerciseYear
Donc l'année civile **Y** agrège : exercice `Y` (mois 15) + exercice `Y+1` (mois 612).
## Implémentation
- Cœur partagé : `App\Service\WorkHours\OvertimePaidContingentCalculator` (pur).
- Repo : `EmployeeRttPaymentRepository::findByEmployeesAndYears`.
- Fiche employé : `GET /employees/{id}/overtime-contingent?year=YYYY` → encart header
(`Total H.payés {année} : X h / plafond h`, rouge si dépassement, année civile courante).
- Export PDF : `GET /overtime-contingent/print?year=&siteIds=` (`ROLE_USER`, périmètre
`findScoped`), groupé par site (`displayOrder`), tri `displayOrder → nom → prénom`,
colonnes JanvDéc + colonne `Total payé / payable`. Builder
`OvertimeContingentExportBuilder`, template `overtime-contingent/print.html.twig`.
## Hors périmètre / connu
- Bug latent récap salaire : `SalaryRecapPrintProvider` requête `findByYearAndMonth` avec
l'année civile alors que le stockage est par exercice (mauvais rattachement des paiements
des mois JuinDéc sur le récap mensuel). À corriger séparément.