'Lundi', 2 => 'Mardi', 3 => 'Mercredi', 4 => 'Jeudi', 5 => 'Vendredi']; /** * Compatibilité avec l'ancien export d'une seule semaine. */ public function agentWeek(array $agent, array $entries, array $week, array $quota): string { return $this->agentWeeks($agent, $entries, [$week], $quota); } /** * Génère un planning compact de 1 à 7 semaines. Avec 7 semaines, le rendu * tient volontairement sur deux pages A4 paysage maximum (4 + 3 semaines). */ public function agentWeeks(array $agent, array $entries, array $weeks, array $quota): string { $weeks = array_values(array_slice($weeks, 0, 7)); if ($weeks === []) { throw new \InvalidArgumentException('Au moins une semaine est nécessaire pour générer le planning.'); } $pdf = new SimplePdf(); $entriesByDate = $this->entriesByDate($entries); $chunks = array_chunk($weeks, 4); foreach ($chunks as $pageIndex => $pageWeeks) { if ($pageIndex > 0) { $pdf->addPage(); } $this->drawAgentMultiWeekHeader($pdf, $agent, $weeks, $quota, $pageIndex + 1, count($chunks)); $sectionY = 96.0; foreach ($pageWeeks as $week) { $this->drawCompactWeek($pdf, $week, $entriesByDate, $sectionY); $sectionY += 112.0; } $this->drawLegend($pdf, self::MARGIN, 563); $pdf->text( self::MARGIN, 584, sprintf('Document genere depuis PTA - %d semaine%s affichee%s.', count($weeks), count($weeks) > 1 ? 's' : '', count($weeks) > 1 ? 's' : ''), 7 ); } return $pdf->output(); } public function structureWeek(array $structure, array $agents, array $week): string { $pdf = new SimplePdf(); $this->drawDocumentHeader( $pdf, 'Planning hebdomadaire du lieu d\'affectation', (string) $structure['nom'] . ' - ' . $this->typeLabel((string) ($structure['type_affectation'] ?? '')), $this->weekLabel($week) ); $weekDays = $this->weekDays($week); $margin = self::MARGIN; $tableY = 100.0; $agentWidth = 126.0; $quotaWidth = 84.0; $usableWidth = $pdf->width() - ($margin * 2); $dayWidth = ($usableWidth - $agentWidth - $quotaWidth) / 5; $headerHeight = 38.0; $bottomLimit = 548.0; $drawHeader = function () use ($pdf, $weekDays, $margin, $tableY, $agentWidth, $quotaWidth, $dayWidth, $headerHeight): void { $x = $margin; $headers = [['Agent', $agentWidth]]; foreach ($weekDays as $day) { $headers[] = [$day['label'] . ' ' . $day['display'], $dayWidth]; } $headers[] = ['Quota restant', $quotaWidth]; foreach ($headers as [$label, $width]) { $pdf->fillRect($x, $tableY, $width, $headerHeight, 0.92); $pdf->rect($x, $tableY, $width, $headerHeight, 0.72); $lines = $pdf->wrap((string) $label, $width - 10, 8); $lineY = $tableY + 15; foreach (array_slice($lines, 0, 2) as $line) { $pdf->text($x + 5, $lineY, $line, 8, true); $lineY += 10; } $x += $width; } }; $drawHeader(); $y = $tableY + $headerHeight; foreach ($agents as $agent) { $entriesByDate = $this->entriesByDate($agent['entries'] ?? []); $dayBlocks = []; $maxCellHeight = 34.0; foreach ($weekDays as $day) { $blocks = []; foreach ($entriesByDate[$day['date']] ?? [] as $entry) { $labelLines = $this->structureEntryLines($pdf, $entry, $dayWidth - 17); $blockHeight = max(16.0, 6.0 + (count($labelLines) * 8.0)); $blocks[] = [ 'entry' => $entry, 'lines' => $labelLines, 'height' => $blockHeight, ]; } $dayBlocks[] = $blocks; $cellHeight = 8.0; foreach ($blocks as $block) { $cellHeight += $block['height'] + 4.0; } $maxCellHeight = max($maxCellHeight, $cellHeight); } $agentLines = $pdf->wrap( sprintf('%s %s - %s', $agent['prenom'], $agent['nom'], $agent['matricule']), $agentWidth - 10, 7.5 ); $rowHeight = max(44.0, $maxCellHeight, 14.0 + (count($agentLines) * 9.0)); if ($y + $rowHeight > $bottomLimit) { $pdf->addPage(); $this->drawDocumentHeader( $pdf, 'Planning hebdomadaire du lieu d\'affectation - suite', (string) $structure['nom'] . ' - ' . $this->typeLabel((string) ($structure['type_affectation'] ?? '')), $this->weekLabel($week) ); $drawHeader(); $y = $tableY + $headerHeight; } $x = $margin; $widths = [$agentWidth, $dayWidth, $dayWidth, $dayWidth, $dayWidth, $dayWidth, $quotaWidth]; foreach ($widths as $width) { $pdf->rect($x, $y, $width, $rowHeight, 0.80); $x += $width; } $lineY = $y + 13; foreach ($agentLines as $index => $line) { $pdf->text($margin + 5, $lineY, $line, 7.5, $index === 0); $lineY += 9; } foreach ($dayBlocks as $dayIndex => $blocks) { $cellX = $margin + $agentWidth + ($dayIndex * $dayWidth); if ($blocks === []) { $pdf->text($cellX + 6, $y + 18, '-', 7.2); continue; } $blockY = $y + 5; foreach ($blocks as $block) { $entry = $block['entry']; $palette = $this->motifPalette((string) ($entry['motif_code'] ?? 'AUTRE')); $blockHeight = (float) $block['height']; $pdf->fillRectColor($cellX + 4, $blockY, $dayWidth - 8, $blockHeight, $palette['background']); $pdf->fillRectColor($cellX + 4, $blockY, 3.0, $blockHeight, $palette['accent']); $textY = $blockY + 10; foreach ($block['lines'] as $lineIndex => $line) { $isFirst = $lineIndex === 0; $pdf->text($cellX + 10, $textY, $line, 7.0, $isFirst); $textY += 8; } $blockY += $blockHeight + 4; } } $quotaText = (string) ($agent['quota']['restantes']['libelle'] ?? '-'); $pdf->text($margin + $agentWidth + (5 * $dayWidth) + 5, $y + 18, $quotaText, 8, true); $pdf->text($margin + $agentWidth + (5 * $dayWidth) + 5, $y + 31, (($agent['lieu_principal'] ?? false) ? 'Principal' : 'Ponctuel'), 7); $y += $rowHeight; } if ($agents === []) { $pdf->text($margin, $y + 24, 'Aucun agent rattache ou planifie sur ce lieu pour cette semaine.', 10); } $this->drawLegend($pdf, self::MARGIN, 562); $pdf->text(self::MARGIN, 584, 'Document genere depuis PTA - Planning du lundi au vendredi.', 7); return $pdf->output(); } private function drawAgentMultiWeekHeader( SimplePdf $pdf, array $agent, array $weeks, array $quota, int $page, int $pageCount ): void { $first = $weeks[0]; $last = $weeks[count($weeks) - 1]; $periodStart = (new DateTimeImmutable((string) $first['date_debut']))->format('d/m/Y'); $periodEnd = (new DateTimeImmutable((string) $last['date_debut']))->modify('+4 days')->format('d/m/Y'); $principalLocation = (($agent['structure_principale_nom'] ?? '') ?: 'Non renseigne'); $principalType = $this->typeLabel((string) ($agent['structure_principale_type_affectation'] ?? '')); $pdf->text(self::MARGIN, 25, 'PTA', 9, true); $pdf->text(self::MARGIN, 44, 'Planning de l\'agent - vue multi-semaines', 16, true); $pdf->text(self::MARGIN, 60, sprintf('%s %s - %s', $agent['prenom'], $agent['nom'], $agent['matricule']), 9.5, true); $pdf->text(self::MARGIN, 74, 'Lieu principal : ' . $principalLocation . ' - ' . $principalType, 8); $pdf->text($pdf->width() - 260, 44, sprintf('Du %s au %s', $periodStart, $periodEnd), 9.5, true); $pdf->text($pdf->width() - 260, 60, 'Quota restant : ' . ($quota['restantes']['libelle'] ?? '-'), 8.5, true); if ($pageCount > 1) { $pdf->text($pdf->width() - 260, 74, sprintf('Page %d / %d', $page, $pageCount), 8); } $pdf->line(self::MARGIN, 86, $pdf->width() - self::MARGIN, 86, 0.72); } /** @param array> $entriesByDate */ private function drawCompactWeek(SimplePdf $pdf, array $week, array $entriesByDate, float $y): void { $weekDays = $this->weekDays($week); $usableWidth = $pdf->width() - (self::MARGIN * 2); $colWidth = $usableWidth / 5; $weekTitleHeight = 16.0; $dayHeaderHeight = 16.0; $bodyHeight = 77.0; $pdf->fillRect(self::MARGIN, $y, $usableWidth, $weekTitleHeight, 0.965); $pdf->rect(self::MARGIN, $y, $usableWidth, $weekTitleHeight + $dayHeaderHeight + $bodyHeight, 0.78); $pdf->text(self::MARGIN + 6, $y + 11, $this->weekLabel($week), 8.5, true); $dayHeaderY = $y + $weekTitleHeight; $bodyY = $dayHeaderY + $dayHeaderHeight; foreach ($weekDays as $index => $day) { $colX = self::MARGIN + ($index * $colWidth); $pdf->fillRect($colX, $dayHeaderY, $colWidth, $dayHeaderHeight, 0.92); $pdf->rect($colX, $dayHeaderY, $colWidth, $dayHeaderHeight + $bodyHeight, 0.82); $pdf->text($colX + 5, $dayHeaderY + 11, $this->shortDayLabel($day['label']) . ' ' . substr($day['display'], 0, 5), 7.2, true); $dayEntries = $entriesByDate[$day['date']] ?? []; if ($dayEntries === []) { $pdf->text($colX + 6, $bodyY + 17, 'Aucune affectation', 6.5); continue; } $cursorY = $bodyY + 4; $rendered = 0; foreach ($dayEntries as $entryIndex => $entry) { $lines = $this->compactAgentEntryLines($pdf, $entry, $colWidth - 18); $cardHeight = max(18.0, 5.0 + (count($lines) * 7.2)); if ($cursorY + $cardHeight > $bodyY + $bodyHeight - 4) { $remaining = count($dayEntries) - $rendered; if ($remaining > 0) { $pdf->text($colX + 7, $bodyY + $bodyHeight - 7, '+' . $remaining . ' autre(s) affectation(s)', 6.2, true); } break; } $palette = $this->motifPalette((string) ($entry['motif_code'] ?? 'AUTRE')); $pdf->fillRectColor($colX + 4, $cursorY, $colWidth - 8, $cardHeight, $palette['background']); $pdf->fillRectColor($colX + 4, $cursorY, 3.0, $cardHeight, $palette['accent']); $lineY = $cursorY + 9; foreach ($lines as $lineIndex => $line) { $bold = $lineIndex === 0; $pdf->text($colX + 10, $lineY, $line, 6.5, $bold); $lineY += 7.2; } $cursorY += $cardHeight + 3.0; $rendered++; } } } /** @return string[] */ private function compactAgentEntryLines(SimplePdf $pdf, array $entry, float $width): array { $lines = [sprintf('%s-%s', $entry['heure_debut'], $entry['heure_fin'])]; $motifLabel = $this->motifDisplayLabel($entry); if ($motifLabel !== '') { foreach ($pdf->wrap($motifLabel, $width, 6.3) as $line) { $lines[] = $line; } } $location = (string) ($entry['structure_nom'] ?? 'Lieu non renseigne'); foreach ($pdf->wrap('Lieu : ' . $location, $width, 6.1) as $line) { $lines[] = $line; } if (($entry['statut'] ?? 'VALIDE') === 'BROUILLON') { $lines[] = 'Brouillon'; } return $lines; } /** @return string[] */ private function structureEntryLines(SimplePdf $pdf, array $entry, float $width): array { $first = sprintf('%s-%s', $entry['heure_debut'], $entry['heure_fin']); $motif = $this->motifDisplayLabel($entry); if ($motif !== '') { $first .= ' ' . $motif; } return $pdf->wrap($first, $width, 7.0); } private function motifDisplayLabel(array $entry): string { return strtoupper((string) ($entry['motif_code'] ?? '')) === 'TRAVAIL' ? '' : (string) ($entry['motif_libelle'] ?? 'Affectation'); } /** @return array{accent:string,background:string,label:string} */ private function motifPalette(string $code): array { return match (strtoupper($code)) { 'TRAVAIL' => ['accent' => '#2563EB', 'background' => '#EFF6FF', 'label' => 'Travail'], 'FORMATION' => ['accent' => '#7C3AED', 'background' => '#F5F3FF', 'label' => 'Formation'], 'CONGE' => ['accent' => '#059669', 'background' => '#ECFDF5', 'label' => 'Conge'], 'MALADIE' => ['accent' => '#DC2626', 'background' => '#FEF2F2', 'label' => 'Absence'], default => ['accent' => '#64748B', 'background' => '#F8FAFC', 'label' => 'Autre absence'], }; } private function drawLegend(SimplePdf $pdf, float $x, float $y): void { $items = [ ['TRAVAIL', 'Travail'], ['FORMATION', 'Formation'], ['CONGE', 'Conge'], ['MALADIE', 'Absence'], ['AUTRE', 'Autre absence'], ]; foreach ($items as [$code, $label]) { $palette = $this->motifPalette($code); $pdf->fillRectColor($x, $y - 7, 8, 8, $palette['accent']); $pdf->text($x + 12, $y, $label, 6.5); $x += 76; } } /** @return array> */ private function entriesByDate(array $entries): array { $grouped = []; foreach ($entries as $entry) { $grouped[(string) $entry['date_jour']][] = $entry; } foreach ($grouped as &$dayEntries) { usort($dayEntries, static fn (array $a, array $b): int => strcmp((string) $a['heure_debut'], (string) $b['heure_debut'])); } unset($dayEntries); return $grouped; } private function drawDocumentHeader(SimplePdf $pdf, string $title, string $subtitle, string $weekLabel): void { $pdf->text(self::MARGIN, 34, 'PTA', 10, true); $pdf->text(self::MARGIN, 55, $title, 18, true); $pdf->text(self::MARGIN, 72, $subtitle, 10); $pdf->text($pdf->width() - 250, 55, $weekLabel, 10, true); $pdf->line(self::MARGIN, 90, $pdf->width() - self::MARGIN, 90, 0.72); } /** @return array */ private function weekDays(array $week): array { $start = new DateTimeImmutable((string) $week['date_debut']); $days = []; for ($dayNumber = 1; $dayNumber <= 5; $dayNumber++) { $date = $start->modify('+' . ($dayNumber - 1) . ' days'); $days[] = [ 'date' => $date->format('Y-m-d'), 'label' => self::WEEK_DAYS[$dayNumber], 'display' => $date->format('d/m/Y'), ]; } return $days; } private function weekLabel(array $week): string { $start = new DateTimeImmutable((string) $week['date_debut']); $end = $start->modify('+4 days'); return sprintf('Semaine du %s au %s', $start->format('d/m/Y'), $end->format('d/m/Y')); } private function shortDayLabel(string $label): string { return match ($label) { 'Lundi' => 'Lun.', 'Mardi' => 'Mar.', 'Mercredi' => 'Mer.', 'Jeudi' => 'Jeu.', 'Vendredi' => 'Ven.', default => $label, }; } private function typeLabel(string $type): string { return match (strtoupper($type)) { 'EXTRASCOLAIRE' => 'Extrascolaire', 'PERISCOLAIRE' => 'Periscolaire', default => 'Non renseigne', }; } }