Files
pta/app/Services/AgentTimeSummaryPdfService.php
Loic Masi 5fbf76868f pour prod
2026-08-07 16:13:43 +02:00

185 lines
7.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Services;
use DateTimeImmutable;
use DateTimeZone;
/**
* Génère un bilan annuel synthétique du temps d'un agent, ventilé par motif.
*/
final class AgentTimeSummaryPdfService
{
private const PAGE_WIDTH = 595.28;
private const PAGE_HEIGHT = 841.89;
private const MARGIN = 42.0;
public function annualSummary(array $agent, array $quota, array $rows): string
{
$pdf = new SimplePdf(self::PAGE_WIDTH, self::PAGE_HEIGHT);
$periodLabel = (string) ($quota['periode_libelle'] ?? ('année ' . ($quota['annee'] ?? date('Y'))));
$generatedAt = new DateTimeImmutable('now', new DateTimeZone('Europe/Paris'));
$totals = $this->normalizeTotals($rows);
$this->drawHeader($pdf, $agent, $periodLabel, $generatedAt);
$this->drawIdentity($pdf, $agent);
$this->drawBreakdown($pdf, $totals);
$this->drawResult($pdf, $quota, $totals);
$this->drawFooter($pdf, $agent, $generatedAt);
return $pdf->output();
}
private function drawHeader(SimplePdf $pdf, array $agent, string $periodLabel, DateTimeImmutable $generatedAt): void
{
$pdf->text(self::MARGIN, 34, 'PTA', 10, true);
$pdf->text(self::MARGIN, 58, 'Compte-rendu du temps de l\'agent', 17, true);
$pdf->text(self::MARGIN, 79, 'Synthèse des heures planifiées et décomptées pour la période ' . $periodLabel, 9.5);
$pdf->text(self::PAGE_WIDTH - 190, 34, 'Édité le ' . $generatedAt->format('d/m/Y'), 8);
$pdf->line(self::MARGIN, 94, self::PAGE_WIDTH - self::MARGIN, 94, 0.72);
}
private function drawIdentity(SimplePdf $pdf, array $agent): void
{
$x = self::MARGIN;
$y = 118.0;
$w = self::PAGE_WIDTH - (self::MARGIN * 2);
$h = 90.0;
$pdf->fillRect($x, $y, $w, 28, 0.93);
$pdf->rect($x, $y, $w, $h, 0.68);
$pdf->text($x + 12, $y + 19, 'Agent', 10.5, true);
$pdf->text($x + 12, $y + 50, 'Nom : ' . (($agent['nom'] ?? '') ?: '-'), 9.2);
$pdf->text($x + 270, $y + 50, 'Prénom : ' . (($agent['prenom'] ?? '') ?: '-'), 9.2);
$pdf->text($x + 12, $y + 73, 'Matricule : ' . (($agent['matricule'] ?? '') ?: '-'), 9.2);
$pdf->text($x + 270, $y + 73, 'Lieu principal : ' . (($agent['structure_principale_nom'] ?? '') ?: 'Non renseigné'), 9.2);
}
private function drawBreakdown(SimplePdf $pdf, array $totals): void
{
$x = self::MARGIN;
$y = 232.0;
$w = self::PAGE_WIDTH - (self::MARGIN * 2);
$headerH = 31.0;
$rowH = 43.0;
$rows = [
['TRAVAIL', 'Heures travaillées', $totals['TRAVAIL']],
['MALADIE', 'Absence', $totals['MALADIE']],
['FORMATION', 'Formation', $totals['FORMATION']],
['CONGE', 'Congé', $totals['CONGE']],
['AUTRE', 'Absence (non décomptée)', $totals['AUTRE']],
];
$pdf->fillRect($x, $y, $w, $headerH, 0.92);
$pdf->rect($x, $y, $w, $headerH + (count($rows) * $rowH), 0.65);
$pdf->text($x + 12, $y + 21, 'Répartition du temps', 11, true);
$cursorY = $y + $headerH;
foreach ($rows as [$code, $label, $minutes]) {
$palette = $this->palette($code);
$pdf->line($x, $cursorY, $x + $w, $cursorY, 0.83);
$pdf->fillRectColor($x + 10, $cursorY + 11, 10, 20, $palette['accent']);
$pdf->text($x + 32, $cursorY + 25, $label, 9.4, true);
$pdf->text($x + $w - 130, $cursorY + 25, $this->minutesLabel($minutes), 10, true);
$cursorY += $rowH;
}
}
private function drawResult(SimplePdf $pdf, array $quota, array $totals): void
{
$counted = $totals['TRAVAIL'] + $totals['MALADIE'] + $totals['FORMATION'] + $totals['CONGE'] + $totals['OTHER_COUNTED'];
$target = (int) ($quota['quota_cible_minutes'] ?? 0);
$remaining = $target - $counted;
$validated = (int) ($quota['minutes_valides'] ?? 0);
$draft = (int) ($quota['minutes_brouillon'] ?? 0);
$x = self::MARGIN;
$y = 507.0;
$w = self::PAGE_WIDTH - (self::MARGIN * 2);
$pdf->line($x, $y, $x + $w, $y, 0.55);
$pdf->text($x, $y + 32, 'Résultat - heures décomptées', 12, true);
$pdf->text($x + $w - 150, $y + 32, $this->minutesLabel($counted), 13, true);
$pdf->text($x, $y + 55, sprintf('Dont %s validées et %s en brouillon.', $this->minutesLabel($validated), $this->minutesLabel($draft)), 8.5);
$boxY = $y + 82;
$boxH = 126.0;
$pdf->fillRect($x, $boxY, $w, $boxH, 0.965);
$pdf->rect($x, $boxY, $w, $boxH, 0.65);
$pdf->text($x + 14, $boxY + 27, 'Quota à effectuer sur la période', 10, true);
$pdf->text($x + $w - 160, $boxY + 27, $this->minutesLabel($target), 11, true);
$pdf->line($x + 12, $boxY + 43, $x + $w - 12, $boxY + 43, 0.85);
$pdf->text($x + 14, $boxY + 72, 'Heures décomptées à ce jour', 10, true);
$pdf->text($x + $w - 160, $boxY + 72, $this->minutesLabel($counted), 11, true);
$pdf->line($x + 12, $boxY + 88, $x + $w - 12, $boxY + 88, 0.85);
$pdf->text($x + 14, $boxY + 114, $remaining >= 0 ? 'Heures restant à effectuer' : 'Dépassement du quota', 10.5, true);
$pdf->textColor($x + $w - 160, $boxY + 114, $this->minutesLabel(abs($remaining)), $remaining >= 0 ? '#1D4ED8' : '#B91C1C', 12, true);
$noteY = $boxY + $boxH + 27;
foreach ($pdf->wrap('Le résultat ci-dessus additionne uniquement les motifs qui décomptent le quota annuel. Les absences non décomptées apparaissent dans le détail mais ne réduisent pas le nombre dheures restant à effectuer.', $w, 8.5) as $line) {
$pdf->text($x, $noteY, $line, 8.5);
$noteY += 12;
}
}
private function drawFooter(SimplePdf $pdf, array $agent, DateTimeImmutable $generatedAt): void
{
$pdf->text(
self::MARGIN,
812,
sprintf('Document généré par PTA le %s - Référence agent : %s', $generatedAt->format('d/m/Y H:i'), (string) ($agent['matricule'] ?? '-')),
7
);
}
private function normalizeTotals(array $rows): array
{
$totals = [
'TRAVAIL' => 0,
'MALADIE' => 0,
'FORMATION' => 0,
'CONGE' => 0,
'AUTRE' => 0,
'OTHER_COUNTED' => 0,
];
foreach ($rows as $row) {
$code = strtoupper((string) ($row['motif_code'] ?? 'AUTRE'));
$minutes = (int) ($row['minutes_total'] ?? 0);
if (array_key_exists($code, $totals) && $code !== 'OTHER_COUNTED') {
$totals[$code] += $minutes;
continue;
}
if ((int) ($row['compte_dans_quota'] ?? 0) === 1) {
$totals['OTHER_COUNTED'] += $minutes;
} else {
$totals['AUTRE'] += $minutes;
}
}
return $totals;
}
/** @return array{accent:string} */
private function palette(string $code): array
{
return match (strtoupper($code)) {
'TRAVAIL' => ['accent' => '#2563EB'],
'FORMATION' => ['accent' => '#7C3AED'],
'CONGE' => ['accent' => '#059669'],
'MALADIE' => ['accent' => '#DC2626'],
default => ['accent' => '#64748B'],
};
}
private function minutesLabel(int $minutes): string
{
$sign = $minutes < 0 ? '-' : '';
$absolute = abs($minutes);
return sprintf('%s%d h %02d', $sign, intdiv($absolute, 60), $absolute % 60);
}
}