111 lines
7.2 KiB
Markdown
111 lines
7.2 KiB
Markdown
# Crédit automatique des heures sur jour férié (Lun-Ven)
|
||
|
||
## Règle
|
||
|
||
Tout jour férié du **lundi au vendredi** crédite automatiquement les **heures contractuelles attendues** pour ce jour, pour tout contrat **autre que Forfait** (`trackingMode` ≠ `PRESENCE`). Les heures ainsi créditées sont dites *virtuelles* : aucune ligne n'est créée dans `work_hours`, elles sont injectées à l'affichage et au calcul.
|
||
|
||
### Référence contractuelle par jour
|
||
|
||
| Contrat | Lun-Jeu | Ven | Sam-Dim |
|
||
|-----------------|---------|-------|---------|
|
||
| 35h | 7h | 7h | 0 |
|
||
| 39h | 8h | 7h | 0 |
|
||
| CUSTOM (avec planning `workDaysHours`) | minutes du jour programmé, 0 sinon | idem | 0 |
|
||
| INTERIM 35h | 7h | 7h | 0 |
|
||
| FORFAIT | — | — | — |
|
||
|
||
La référence par jour est calculée par `App\Service\WorkHours\DailyReferenceMinutesResolver`.
|
||
|
||
### Planning `workDaysHours`
|
||
|
||
Tout contrat TIME **hors 35h/39h/INTERIM** (ex. 4h, 25h, 28h) doit déclarer un planning précis sur sa `EmployeeContractPeriod` : colonne JSON `work_days_hours = {"1": 120, "4": 120}` (iso day → minutes). La somme doit égaler `weeklyHours × 60`.
|
||
|
||
- **Sur un jour du planning** : crédit férié = minutes programmées (ex. Ewa Lun → 120 min).
|
||
- **Sur un jour hors planning** : crédit férié = 0 (elle n'aurait pas travaillé).
|
||
- Même logique appliquée par `WorkedHoursCreditPolicy::resolveContractDayMinutes` pour les crédits d'absence — un 4h en absence mardi (non programmée) = 0 crédit.
|
||
|
||
Validation à l'écriture : `EmployeeContractPeriodValidator::assertWorkDaysHours`. Le frontend expose un bloc « Jours travaillés » (cases Lun-Ven + input `HH:MM`) sur les formulaires de création employé + d'ajout de contrat, visible uniquement quand le contrat le requiert.
|
||
|
||
**Limitation actuelle** : l'édition in-place d'un schedule sur une période active existante n'est **pas exposée** via l'UI. Le drawer « Modifier le contrat » affiche le schedule en lecture seule à titre informatif. Pour corriger un schedule, la démarche est : clôturer le contrat en cours + créer un nouveau contrat avec le schedule corrigé. Si un besoin d'édition directe émerge, ajouter `workDaysHours` dans `EmployeeContractChangeRequest::hasPeriodChangeRequest()` et la logique d'update dans `EmployeeContractPeriodManager`.
|
||
|
||
### Fériés exclus
|
||
|
||
Les fériés listés dans l'env `EXCLUDED_PUBLIC_HOLIDAYS` (par défaut `Lundi de Pentecôte` — journée de solidarité) **ne donnent pas** de crédit virtuel : le `PublicHolidayService` les filtre en amont, donc `HolidayVirtualHoursResolver` ne les voit pas comme fériés.
|
||
|
||
### Interaction avec saisie
|
||
|
||
Quand l'employé saisit des heures ce jour-là :
|
||
|
||
- `heures finales = max(heures saisies + crédit d'absence éventuel, heures contractuelles de référence)`
|
||
|
||
Exemples avec un contrat 39h et un férié un lundi :
|
||
|
||
| Saisie employé | Total affiché | Interprétation |
|
||
|------------------|---------------|----------------|
|
||
| Aucune | 8h | Crédit 100% virtuel |
|
||
| Matin 09:00-13:00 (4h) | 8h | Le minimum contractuel l'emporte |
|
||
| 09:00-12:00 + 13:00-19:00 (9h) | 9h | Les heures saisies l'emportent |
|
||
|
||
### Interaction avec absences
|
||
|
||
La création d'absence sur un férié Lun-Ven est **autorisée** (bouton Modifier visible). Dès qu'une absence est déclarée sur le jour (matin et/ou après-midi), le crédit virtuel férié **est désactivé** pour ce jour : c'est `absence.type.countAsWorkedHours` qui pilote le crédit d'heures, via `WorkedHoursCreditPolicy`.
|
||
|
||
- `countAsWorkedHours = true` (ex. maladie payée) : crédit calculé normalement (7h/8h selon contrat × halfUnits/2). Même quantité que la référence virtuelle si journée complète, donc résultat identique — mais la source du crédit est l'absence, pas le férié.
|
||
- `countAsWorkedHours = false` (ex. congé sans solde) : crédit = 0. Le férié ne compense pas.
|
||
|
||
Cette règle évite le double-crédit (absence + férié virtuel) et respecte le paramétrage fonctionnel du type d'absence.
|
||
|
||
## Impact technique
|
||
|
||
### Affichage
|
||
|
||
- **Écran Heures (vue jour)** : sur un férié Lun-Ven non-Forfait, la colonne Total affiche la valeur effective (référence ou saisie, selon max). Un chip "Férié : Xh comptées" apparaît sous le pill bleu du férié.
|
||
- **Écran Heures Conducteurs (vue jour)** : idem, plus un indicateur `= Xh (férié)` sous l'input "Heures jour" pour signaler que le crédit est imputé au bucket jour.
|
||
- **Vues semaine** : les totaux hebdomadaires intègrent les minutes virtuelles. Un marqueur `F + Xh` apparaît dans la cellule du jour férié.
|
||
- **Onglet RTT** : les semaines contenant un férié Lun-Ven gagnent du temps crédité, ce qui peut générer des heures sup (25% / 50%) là où l'ancienne règle produisait un déficit.
|
||
|
||
### Calcul RTT
|
||
|
||
Le service `App\Service\WorkHours\HolidayVirtualHoursResolver` est injecté dans `RttRecoveryComputationService::computeRecoveryByWeek()`. Pour chaque jour ouvré :
|
||
|
||
```
|
||
effectiveMinutes = resolveEffectiveDailyMinutes(contract, date, metrics.totalMinutes + credited)
|
||
weeklyTotalMinutes += effectiveMinutes
|
||
```
|
||
|
||
Le reste du calcul (tranches +25%, +50%, base 25% à partir de 35h/39h) demeure inchangé ; seul le total hebdo injecté a évolué.
|
||
|
||
### Calcul hebdomadaire d'affichage
|
||
|
||
`WorkHourWeeklySummaryProvider` applique la même substitution sur `weeklyDayMinutes` et `weeklyTotalMinutes`. Le DTO `WeeklyDaySummary` expose désormais un champ `virtualHolidayMinutes` utilisé par les vues semaine.
|
||
|
||
### Contexte jour
|
||
|
||
`WorkHourDayContextProvider` expose `virtualHolidayMinutes` dans `DayContextRow` pour permettre au frontend de calculer le total journalier en temps réel pendant la saisie (sans aller-retour).
|
||
|
||
### Frontend
|
||
|
||
Le composable `frontend/composables/useHolidayVirtualHours.ts` réplique la règle côté client et est consommé par `useHoursPage.ts::getRowMetrics` et `useDriverHoursPage.ts::getRowMetrics`.
|
||
|
||
## Impact historique
|
||
|
||
La règle est appliquée **à chaque lecture** depuis les `WorkHour` — donc l'exercice courant et tout exercice recalculé live bénéficient automatiquement de la nouvelle règle sans migration.
|
||
|
||
Les reports N-1 stockés dans `employee_rtt_balances.opening_*_minutes` ont été saisis manuellement par la RH (valeurs officielles) et ne sont **pas recalculés** : ces snapshots restent la source de vérité pour les soldes d'ouverture.
|
||
|
||
## Services impliqués
|
||
|
||
| Composant | Rôle |
|
||
|-----------|------|
|
||
| `DailyReferenceMinutesResolver` | Résolution "minutes contractuelles par jour" (logique partagée, anciennement dupliquée). |
|
||
| `HolidayVirtualHoursResolver` | Décide si la règle s'applique et renvoie le crédit virtuel ou la valeur effective. |
|
||
| `RttRecoveryComputationService` | Applique la substitution dans le calcul hebdo RTT. |
|
||
| `WorkHourWeeklySummaryProvider` | Applique la substitution dans les totaux hebdo UI. |
|
||
| `WorkHourDayContextProvider` | Expose `virtualHolidayMinutes` par salarié/jour. |
|
||
| `useHolidayVirtualHours.ts` (frontend) | Réplique la règle en live côté client. |
|
||
|
||
## Tests
|
||
|
||
- `tests/Service/WorkHours/HolidayVirtualHoursResolverTest.php` couvre les scénarios par contrat + jours ouvrés/chômés.
|
||
- `make test` (PHPUnit) valide l'intégration RTT / hebdo / contexte jour.
|