0a9b26d31e
Auto Tag Develop / tag (push) Successful in 6s
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>
50 lines
2.8 KiB
Markdown
50 lines
2.8 KiB
Markdown
# Contingent d'heures supplémentaires payées
|
||
|
||
## Objectif
|
||
Suivre, par année civile (Janv–Dé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` (1–12). 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 1–5) + exercice `Y+1` (mois 6–12).
|
||
|
||
## 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 Janv–Dé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 Juin–Déc sur le récap mensuel). À corriger séparément.
|