feat : ajout des jours fériés sur l'export PDF des heures

Affiche désormais une ligne dédiée pour chaque jour férié (Lun-Ven) avec la mention "Férié : {nom}" et le total créditant les heures contractuelles, comme sur l'écran Heures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-29 17:21:59 +02:00
parent eacf52425a
commit 78f73ed2e9
5 changed files with 103 additions and 23 deletions

View File

@@ -14,8 +14,10 @@ use App\Enum\TrackingMode;
use App\Repository\AbsenceRepository;
use App\Repository\WorkHourRepository;
use App\Service\Contracts\EmployeeContractResolver;
use App\Service\PublicHolidayServiceInterface;
use DateInterval;
use DateTimeImmutable;
use Throwable;
class YearlyHoursExportBuilder
{
@@ -25,6 +27,8 @@ class YearlyHoursExportBuilder
private EmployeeContractResolver $contractResolver,
private AbsenceSegmentsResolver $absenceSegmentsResolver,
private WorkedHoursCreditPolicy $workedHoursCreditPolicy,
private PublicHolidayServiceInterface $publicHolidayService,
private HolidayVirtualHoursResolver $holidayVirtualHoursResolver,
) {}
/**
@@ -56,6 +60,8 @@ class YearlyHoursExportBuilder
$absences = $this->absenceRepository->findForPrint($from, $to, $employees);
$contractMap = $this->contractResolver->resolveForEmployeesAndDays($employees, $days);
$driverMap = $this->contractResolver->resolveIsDriverForEmployeesAndDays($employees, $days);
$workDaysMap = $this->contractResolver->resolveWorkDaysMinutesForEmployeesAndDays($employees, $days);
$holidayMap = $this->buildHolidayMap($from, $to);
$workHourMap = $this->buildWorkHourMap($workHours);
$absenceMap = $this->buildAbsenceMap($absences, $days);
@@ -71,6 +77,8 @@ class YearlyHoursExportBuilder
$driverMap[$employeeId] ?? [],
$workHourMap[$employeeId] ?? [],
$absenceData,
$workDaysMap[$employeeId] ?? [],
$holidayMap,
);
if ([] === $segments) {
@@ -205,6 +213,9 @@ class YearlyHoursExportBuilder
}
/**
* @param array<string, ?array<int, int>> $workDaysMinutesByDate
* @param array<string, string> $holidayMap
*
* @return list<array{mode: string, contractName: ?string, rows: list<array>}>
*/
private function buildSegments(
@@ -213,6 +224,8 @@ class YearlyHoursExportBuilder
array $driverByDate,
array $workHoursByDate,
array $absenceData,
array $workDaysMinutesByDate,
array $holidayMap,
): array {
$segments = [];
$currentMode = null;
@@ -222,7 +235,8 @@ class YearlyHoursExportBuilder
$firstDataDate = null;
foreach ($days as $date) {
$hasRow = null !== ($workHoursByDate[$date] ?? null)
|| ($absenceData['hasDayAbsence'][$date] ?? false);
|| ($absenceData['hasDayAbsence'][$date] ?? false)
|| isset($holidayMap[$date]);
if ($hasRow) {
$firstDataDate = $date;
@@ -241,14 +255,16 @@ class YearlyHoursExportBuilder
continue;
}
$contract = $contractsByDate[$date] ?? null;
$isDriver = $driverByDate[$date] ?? false;
$wh = $workHoursByDate[$date] ?? null;
$hasData = null !== $wh || ($absenceData['hasDayAbsence'][$date] ?? false);
$isoDay = (int) new DateTimeImmutable($date)->format('N');
$isWeekend = $isoDay >= 6;
$contract = $contractsByDate[$date] ?? null;
$isDriver = $driverByDate[$date] ?? false;
$wh = $workHoursByDate[$date] ?? null;
$hasData = null !== $wh || ($absenceData['hasDayAbsence'][$date] ?? false);
$holidayLabel = $holidayMap[$date] ?? null;
$isHoliday = null !== $holidayLabel;
$isoDay = (int) new DateTimeImmutable($date)->format('N');
$isWeekend = $isoDay >= 6;
if (!$hasData && !$isWeekend) {
if (!$hasData && !$isWeekend && !$isHoliday) {
continue;
}
@@ -275,10 +291,18 @@ class YearlyHoursExportBuilder
$creditedMinutes = $absenceData['credited'][$date] ?? 0;
$absenceLabel = $absenceData['labels'][$date] ?? null;
$hasAbsence = $absenceData['hasDayAbsence'][$date] ?? false;
$virtualMinutes = $this->holidayVirtualHoursResolver->resolveVirtualCredit(
$contract,
new DateTimeImmutable($date),
$hasAbsence,
$workDaysMinutesByDate[$date] ?? null,
);
$row = [
'date' => new DateTimeImmutable($date)->format('d/m/Y'),
'absenceLabel' => $absenceLabel,
'holidayLabel' => $holidayLabel,
'isWeekend' => $isWeekend,
];
@@ -297,6 +321,9 @@ class YearlyHoursExportBuilder
$nightMin = $wh?->getNightHoursMinutes() ?? 0;
$workshopMin = $wh?->getWorkshopHoursMinutes() ?? 0;
$totalMin = $dayMin + $nightMin + $workshopMin + $creditedMinutes;
if ($virtualMinutes > $totalMin) {
$totalMin = $virtualMinutes;
}
$row['dayHours'] = $this->formatMinutes($dayMin);
$row['nightHours'] = $this->formatMinutes($nightMin);
@@ -305,6 +332,10 @@ class YearlyHoursExportBuilder
} else {
$metrics = null !== $wh ? $this->computeMetrics($wh) : new WorkMetrics();
$metrics->addCreditedMinutes($creditedMinutes);
$totalMin = $metrics->totalMinutes;
if ($virtualMinutes > $totalMin) {
$totalMin = $virtualMinutes;
}
$row['morningFrom'] = $wh?->getMorningFrom() ?? '';
$row['morningTo'] = $wh?->getMorningTo() ?? '';
@@ -312,7 +343,7 @@ class YearlyHoursExportBuilder
$row['afternoonTo'] = $wh?->getAfternoonTo() ?? '';
$row['eveningFrom'] = $wh?->getEveningFrom() ?? '';
$row['eveningTo'] = $wh?->getEveningTo() ?? '';
$row['total'] = $this->formatMinutes($metrics->totalMinutes);
$row['total'] = $this->formatMinutes($totalMin);
}
$currentRows[] = $row;
@@ -329,6 +360,29 @@ class YearlyHoursExportBuilder
return $segments;
}
/**
* @return array<string, string> Y-m-d => label
*/
private function buildHolidayMap(DateTimeImmutable $from, DateTimeImmutable $to): array
{
$map = [];
$startYear = (int) $from->format('Y');
$endYear = (int) $to->format('Y');
try {
for ($year = $startYear; $year <= $endYear; ++$year) {
$holidays = $this->publicHolidayService->getHolidaysDayByYears('metropole', (string) $year);
foreach ($holidays as $date => $label) {
$map[(string) $date] = (string) $label;
}
}
} catch (Throwable) {
return [];
}
return $map;
}
private function resolveSegmentMode(string $trackingMode, bool $isDriver): string
{
if ($isDriver) {