427 lines
17 KiB
PHP
427 lines
17 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use DateTimeImmutable;
|
|
|
|
final class PlanningPdfService
|
|
{
|
|
private const MARGIN = 28.0;
|
|
private const WEEK_DAYS = [1 => '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<string, array<int, 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<string, array<int, 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<int, array{date:string,label:string,display:string}> */
|
|
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',
|
|
};
|
|
}
|
|
}
|