diff --git a/CLAUDE.md b/CLAUDE.md index 5759892..bc9673f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -71,6 +71,7 @@ - FORFAIT weekend/holiday bonus: each weekend or public holiday day worked gives bonus leave (full day if morning+afternoon, 0.5 if only one). Added to acquired days, no cap. PRESENCE mode only. - **FORFAIT — jours de présence et N-1** : les congés posés et imputés sur le stock N-1 ne décrémentent **pas** les jours de présence affichés (`presenceDaysByMonth` et `presenceDaysToToday`). Implémenté dans `EmployeeLeaveSummaryProvider::computePresenceDaysByMonth` via un budget N-1 (= `previousYearTakenDays`) consommé chronologiquement avant comptage des absences. Pour les non-forfait, ce budget vaut toujours 0 → comportement inchangé. - **Récap salaire (export PDF mensuel)** : même règle appliquée dans `SalaryRecapPrintProvider` — un congé forfait imputé N-1 n'est ni affiché en colonne congés ni soustrait, et compte comme jour de présence. Le budget N-1 vient de `EmployeeLeaveSummaryProvider::resolvePreviousYearTakenDays()` (méthode publique mutualisée, qui reproduit `provide()` : phase courante + recalcul jours payés, donc **même budget que la fiche employé**). Comme l'export est mensuel, les congés sont chargés depuis le 1er janvier (`findForPrint(yearStart, to)`) et le budget consommé chronologiquement par `splitForfaitCongesByN1()`. Non-forfait ou budget N-1 = 0 → `countAbsencesByCode(['C'])` inchangé. + - **Colonne « Heures payés » scindée 25 %/50 %** : en-tête fusionné (`colspan=2`) + deux sous-colonnes `25%`/`50%` dans le template `salary-recap/print.html.twig`. Données : `paid25Hours` = `base25Minutes`, `paid50Hours` = `base50Minutes` (bases seules, **hors bonus** — total inchangé vs l'ancienne colonne unique). `buildRttPaymentMap` renvoie `['m25','m50']` par employé. Le tableau a désormais 20 colonnes (`colspan` des lignes site/vide ajusté). - **Jours de présence — borne début de contrat** : `presenceDaysByMonth`/`presenceDaysToToday` sont calculés à partir de `resolveEarliestContractStartWithinRange` (début de contrat dans l'exercice), pas du début d'exercice brut. Évite de compter comme « présents » les jours ouvrés antérieurs à l'embauche pour une entrée en cours d'exercice (ex. CDD : Dylan passait de 43,5 à 246 sans la borne). Sans effet pour un employé présent depuis avant l'exercice ni pour le forfait (déjà capé au début de phase). ## Onglet Congés (fiche employé) diff --git a/frontend/data/documentation-content.ts b/frontend/data/documentation-content.ts index 4edcdde..e6b09ae 100644 --- a/frontend/data/documentation-content.ts +++ b/frontend/data/documentation-content.ts @@ -621,7 +621,7 @@ export const documentationSections: DocSection[] = [ requiredLevel: 'admin', blocks: [ { type: 'paragraph', content: 'Génère un PDF A4 paysage avec le détail mensuel pour la paie.' }, - { type: 'list', content: 'Sélecteur de mois (défaut = mois courant)\nDonnées groupées par site\nColonnes : nom, base contrat, jours de présence cadre, heures de nuit, panier de nuit, heures RTT payées, congés (nombre + dates), maladie/AT (nombre + dates), primes conducteur (PDJ, repas, nuitée, samedi), observations\nColonne « Repas » chauffeur : somme déjeuner + dîner sur le mois (un jour avec les deux compte 2 repas)' }, + { type: 'list', content: 'Sélecteur de mois (défaut = mois courant)\nDonnées groupées par site\nColonnes : nom, base contrat, jours de présence cadre, heures de nuit, panier de nuit, heures RTT payées (en-tête fusionné scindé en deux sous-colonnes 25 % et 50 %), congés (nombre + dates), maladie/AT (nombre + dates), primes conducteur (PDJ, repas, nuitée, samedi), observations\nColonne « Repas » chauffeur : somme déjeuner + dîner sur le mois (un jour avec les deux compte 2 repas)' }, { type: 'note', content: 'Forfait : un congé imputé sur le stock de l\'année précédente (N-1) n\'apparaît pas dans la colonne congés et compte comme un jour de présence. Le budget N-1 est consommé dans l\'ordre chronologique depuis janvier, de façon cohérente avec la fiche employé (les jours payés réduisent le stock N-1 d\'abord). Au-delà du budget N-1, les congés s\'affichent normalement.' }, ], }, diff --git a/src/State/SalaryRecapPrintProvider.php b/src/State/SalaryRecapPrintProvider.php index 3a88cc9..80f2e34 100644 --- a/src/State/SalaryRecapPrintProvider.php +++ b/src/State/SalaryRecapPrintProvider.php @@ -174,6 +174,9 @@ class SalaryRecapPrintProvider implements ProviderInterface /** * @return array */ + /** + * @return array + */ private function buildRttPaymentMap(array $rttPayments): array { $map = []; @@ -182,7 +185,9 @@ class SalaryRecapPrintProvider implements ProviderInterface if (!$employeeId) { continue; } - $map[$employeeId] = ($map[$employeeId] ?? 0) + $payment->getBase25Minutes() + $payment->getBase50Minutes(); + $map[$employeeId] ??= ['m25' => 0, 'm50' => 0]; + $map[$employeeId]['m25'] += $payment->getBase25Minutes(); + $map[$employeeId]['m50'] += $payment->getBase50Minutes(); } return $map; @@ -295,7 +300,7 @@ class SalaryRecapPrintProvider implements ProviderInterface $driverMap[$employeeId] ?? [], $workHourMap[$employeeId] ?? [], $absenceMap[$employeeId] ?? [], - $rttPaymentMap[$employeeId] ?? 0, + $rttPaymentMap[$employeeId] ?? ['m25' => 0, 'm50' => 0], $bonusMap[$employeeId] ?? 0.0, $mileageMap[$employeeId] ?? 0.0, $observationMap[$employeeId] ?? '', @@ -328,7 +333,7 @@ class SalaryRecapPrintProvider implements ProviderInterface array $driverByDate, array $workHoursByDate, array $absences, - int $rttPaidMinutes, + array $rttPaid, float $bonusAmount, float $mileageKm, string $observation, @@ -455,7 +460,8 @@ class SalaryRecapPrintProvider implements ProviderInterface $maladie = $this->countAbsencesByCode($absences, ['M', 'AT']); $nightHours = round($nightMinutesTotal / 60, 2); - $paidHours = round($rttPaidMinutes / 60, 2); + $paid25Hours = round(($rttPaid['m25'] ?? 0) / 60, 2); + $paid50Hours = round(($rttPaid['m50'] ?? 0) / 60, 2); $sundayHours = round($sundayMinutesTotal / 60, 2); $holidayHours = round($holidayMinutesTotal / 60, 2); @@ -467,7 +473,8 @@ class SalaryRecapPrintProvider implements ProviderInterface 'mileageKm' => $mileageKm, 'nightHours' => $nightHours, 'nightBasketCount' => $nightBasketCount, - 'paidHours' => $paidHours, + 'paid25Hours' => $paid25Hours, + 'paid50Hours' => $paid50Hours, 'sundayHours' => $sundayHours, 'holidayHours' => $holidayHours, 'bonusAmount' => $bonusAmount, diff --git a/templates/salary-recap/print.html.twig b/templates/salary-recap/print.html.twig index 1aef0da..45ca8fe 100644 --- a/templates/salary-recap/print.html.twig +++ b/templates/salary-recap/print.html.twig @@ -117,7 +117,7 @@ Frais
Kms Heures
de
nuit Panier
de
nuit - Heures
payés + Heures
payés Heures
férié Heures
dim. Prime @@ -127,6 +127,8 @@ Observations + 25% + 50% Nbre Date Nbre @@ -141,7 +143,7 @@ {% for siteId, group in siteGroups %} {% set siteColor = group.color ?? '#B3E5FC' %} - + {{ group.name }} @@ -153,7 +155,8 @@ {{ row.mileageKm > 0 ? row.mileageKm : '' }} {{ row.nightHours > 0 ? row.nightHours : '' }} {{ row.nightBasketCount > 0 ? row.nightBasketCount : '' }} - {{ row.paidHours > 0 ? row.paidHours : '' }} + {{ row.paid25Hours > 0 ? row.paid25Hours : '' }} + {{ row.paid50Hours > 0 ? row.paid50Hours : '' }} {{ row.holidayHours > 0 ? row.holidayHours : '' }} {{ row.sundayHours > 0 ? row.sundayHours : '' }} {{ row.bonusAmount > 0 ? row.bonusAmount ~ ' €' : '' }} @@ -169,7 +172,7 @@ {% else %} - Aucun employé. + Aucun employé. {% endfor %} {% endfor %}