Ajout des commentaires dans le code
This commit is contained in:
@@ -7,13 +7,35 @@ namespace App\Services;
|
||||
use App\Core\MotifCategories;
|
||||
use DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* Mise en page des plannings au format PDF, par agent ou par lieu.
|
||||
*
|
||||
* Deux documents, tous deux en A4 PAYSAGE (SimplePdf par défaut) :
|
||||
* - agentWeeks() : un agent sur 1 à 7 semaines, en blocs compacts ;
|
||||
* - structureWeek() : un lieu sur une semaine, en tableau agents × jours, avec
|
||||
* saut de page géré à la main.
|
||||
*
|
||||
* CONTRAINTE TRANSVERSE — les libellés sont écrits SANS ACCENT dans tout ce
|
||||
* fichier (« Document genere », « Periscolaire »). Ce n'est pas une négligence :
|
||||
* SimplePdf convertit en Windows-1252 avec translittération, et cette écriture
|
||||
* garantit un rendu identique même quand iconv n'est pas disponible. Les données
|
||||
* venant de la base, elles, gardent leurs accents.
|
||||
*
|
||||
* Toutes les coordonnées sont en POINTS PDF, origine en HAUT à gauche (voir
|
||||
* SimplePdf). Les valeurs numériques éparpillées dans les méthodes de dessin
|
||||
* sont des positions absolues ajustées visuellement : les modifier suppose de
|
||||
* regénérer un document pour vérifier.
|
||||
*/
|
||||
final class PlanningPdfService
|
||||
{
|
||||
/** Marge extérieure commune à tous les documents. */
|
||||
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.
|
||||
* Conservée pour d'éventuels appels externes ; le code actuel passe
|
||||
* directement par agentWeeks().
|
||||
*/
|
||||
public function agentWeek(array $agent, array $entries, array $week, array $quota): string
|
||||
{
|
||||
@@ -23,6 +45,24 @@ final class PlanningPdfService
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* Le découpage en pages est fait par array_chunk(..., 4) : QUATRE SEMAINES
|
||||
* PAR PAGE, chaque bloc occupant 112 points de haut à partir de y = 96. Ce
|
||||
* plafond de quatre est une contrainte physique, SimplePdf ne sachant pas
|
||||
* reporter un tableau sur la page suivante. Passer à cinq exigerait de
|
||||
* réduire la hauteur des blocs.
|
||||
*
|
||||
* Au-delà de sept semaines, la liste est silencieusement TRONQUÉE : le
|
||||
* contrôleur n'accepte de toute façon que 6 ou 7.
|
||||
*
|
||||
* @param array<string, mixed> $agent
|
||||
* @param array<int, array<string, mixed>> $entries Créneaux de toute la période.
|
||||
* @param array<int, array<string, mixed>> $weeks Semaines à rendre.
|
||||
* @param array<string, mixed> $quota
|
||||
*
|
||||
* @return string Octets du PDF.
|
||||
*
|
||||
* @throws \InvalidArgumentException Si aucune semaine n'est fournie.
|
||||
*/
|
||||
public function agentWeeks(array $agent, array $entries, array $weeks, array $quota): string
|
||||
{
|
||||
@@ -60,6 +100,24 @@ final class PlanningPdfService
|
||||
return $pdf->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Planning d'un lieu sur une semaine : un tableau agents × cinq jours.
|
||||
*
|
||||
* Contrairement à agentWeeks(), la hauteur des lignes n'est PAS connue à
|
||||
* l'avance — elle dépend du nombre de créneaux de chaque agent et du
|
||||
* découpage des libellés. La méthode mesure donc chaque ligne avant de la
|
||||
* tracer, et déclenche une nouvelle page dès que $bottomLimit serait
|
||||
* dépassé. La closure $drawHeader existe pour cette raison : l'en-tête du
|
||||
* tableau doit être redessiné en tête de CHAQUE page, sans quoi les colonnes
|
||||
* ne seraient plus identifiables.
|
||||
*
|
||||
* @param array<string, mixed> $structure
|
||||
* @param array<int, array<string, mixed>> $agents Chaque agent porte ses
|
||||
* propres clés `entries` et `quota`.
|
||||
* @param array<string, mixed> $week
|
||||
*
|
||||
* @return string Octets du PDF.
|
||||
*/
|
||||
public function structureWeek(array $structure, array $agents, array $week): string
|
||||
{
|
||||
$pdf = new SimplePdf();
|
||||
@@ -201,6 +259,10 @@ final class PlanningPdfService
|
||||
return $pdf->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* En-tête du planning agent : identité, lieu principal, période couverte,
|
||||
* quota restant, et pagination si le document tient sur plusieurs pages.
|
||||
*/
|
||||
private function drawAgentMultiWeekHeader(
|
||||
SimplePdf $pdf,
|
||||
array $agent,
|
||||
@@ -228,7 +290,19 @@ final class PlanningPdfService
|
||||
$pdf->line(self::MARGIN, 86, $pdf->width() - self::MARGIN, 86, 0.72);
|
||||
}
|
||||
|
||||
/** @param array<string, array<int, array>> $entriesByDate */
|
||||
/**
|
||||
* Dessine UN bloc de semaine : bandeau de titre, en-têtes de jours, puis les
|
||||
* créneaux de chaque journée dans une colonne.
|
||||
*
|
||||
* La hauteur du corps est FIXE (77 points) pour que les blocs s'empilent à
|
||||
* pas régulier. Les créneaux qui n'y tiennent pas ne sont pas rendus — le
|
||||
* compteur $rendered plus bas s'en charge. Une journée très chargée peut
|
||||
* donc apparaître tronquée : c'est le compromis retenu pour garantir sept
|
||||
* semaines sur deux pages.
|
||||
*
|
||||
* @param array<string, array<int, array>> $entriesByDate Créneaux indexés par date.
|
||||
* @param float $y Ordonnée du haut du bloc.
|
||||
*/
|
||||
private function drawCompactWeek(SimplePdf $pdf, array $week, array $entriesByDate, float $y): void
|
||||
{
|
||||
$weekDays = $this->weekDays($week);
|
||||
@@ -287,7 +361,13 @@ final class PlanningPdfService
|
||||
}
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
/**
|
||||
* Lignes d'un créneau dans la vue agent : horaires, motif si informatif,
|
||||
* lieu, et la mention « Brouillon » quand le planning n'est pas validé —
|
||||
* un document imprimé doit dire s'il est définitif.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function compactAgentEntryLines(SimplePdf $pdf, array $entry, float $width): array
|
||||
{
|
||||
$lines = [sprintf('%s-%s', $entry['heure_debut'], $entry['heure_fin'])];
|
||||
@@ -310,7 +390,12 @@ final class PlanningPdfService
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
/**
|
||||
* Lignes d'un créneau dans la vue lieu : plus condensées que dans la vue
|
||||
* agent, le lieu étant déjà celui du document.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function structureEntryLines(SimplePdf $pdf, array $entry, float $width): array
|
||||
{
|
||||
$first = sprintf('%s-%s', $entry['heure_debut'], $entry['heure_fin']);
|
||||
@@ -333,7 +418,11 @@ final class PlanningPdfService
|
||||
: (string) ($entry['motif_libelle'] ?? 'Affectation');
|
||||
}
|
||||
|
||||
/** @return array{accent:string,background:string,label:string} */
|
||||
/**
|
||||
* Palette d'un créneau, déduite de son motif.
|
||||
*
|
||||
* @return array{accent:string,background:string,label:string}
|
||||
*/
|
||||
private function entryPalette(array $entry): array
|
||||
{
|
||||
return $this->motifPalette(
|
||||
@@ -342,7 +431,15 @@ final class PlanningPdfService
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array{accent:string,background:string,label:string} */
|
||||
/**
|
||||
* Couleurs par CATÉGORIE de motif — travail en bleu, formation en violet,
|
||||
* congé en vert, absence en rouge, reste en gris.
|
||||
*
|
||||
* Le classement passe par App\Core\MotifCategories, partagé avec l'écran et
|
||||
* les autres PDF : une même catégorie garde partout la même couleur.
|
||||
*
|
||||
* @return array{accent:string,background:string,label:string}
|
||||
*/
|
||||
private function motifPalette(string $code, bool $countsInQuota = false): array
|
||||
{
|
||||
return match (MotifCategories::category($code, $countsInQuota)) {
|
||||
@@ -354,6 +451,12 @@ final class PlanningPdfService
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Légende des couleurs, en une ligne au pied du document.
|
||||
*
|
||||
* Les entrées sont espacées selon la LARGEUR ESTIMÉE de leur libellé et non
|
||||
* à pas fixe : la première est trop longue pour une colonne régulière.
|
||||
*/
|
||||
private function drawLegend(SimplePdf $pdf, float $x, float $y): void
|
||||
{
|
||||
$items = [
|
||||
@@ -374,7 +477,15 @@ final class PlanningPdfService
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<string, array<int, array>> */
|
||||
/**
|
||||
* Indexe les créneaux par date et les trie par heure de début.
|
||||
*
|
||||
* Le tri est refait ici bien que le SQL trie déjà : les créneaux peuvent
|
||||
* avoir été fusionnés depuis plusieurs sources (plusieurs lieux, plusieurs
|
||||
* plannings) avant d'arriver.
|
||||
*
|
||||
* @return array<string, array<int, array>>
|
||||
*/
|
||||
private function entriesByDate(array $entries): array
|
||||
{
|
||||
$grouped = [];
|
||||
@@ -388,6 +499,7 @@ final class PlanningPdfService
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/** En-tête commun du planning par lieu : titre, sous-titre, semaine, filet. */
|
||||
private function drawDocumentHeader(SimplePdf $pdf, string $title, string $subtitle, string $weekLabel): void
|
||||
{
|
||||
$pdf->text(self::MARGIN, 34, 'PTA', 10, true);
|
||||
@@ -397,7 +509,12 @@ final class PlanningPdfService
|
||||
$pdf->line(self::MARGIN, 90, $pdf->width() - self::MARGIN, 90, 0.72);
|
||||
}
|
||||
|
||||
/** @return array<int, array{date:string,label:string,display:string}> */
|
||||
/**
|
||||
* Les cinq jours ouvrés d'une semaine, à partir de sa date de début.
|
||||
* S'arrête au vendredi, le week-end n'étant pas planifiable.
|
||||
*
|
||||
* @return array<int, array{date:string,label:string,display:string}>
|
||||
*/
|
||||
private function weekDays(array $week): array
|
||||
{
|
||||
$start = new DateTimeImmutable((string) $week['date_debut']);
|
||||
@@ -413,6 +530,7 @@ final class PlanningPdfService
|
||||
return $days;
|
||||
}
|
||||
|
||||
/** « Semaine du 16/02/2026 au 20/02/2026 » — bornée au vendredi. */
|
||||
private function weekLabel(array $week): string
|
||||
{
|
||||
$start = new DateTimeImmutable((string) $week['date_debut']);
|
||||
@@ -420,6 +538,7 @@ final class PlanningPdfService
|
||||
return sprintf('Semaine du %s au %s', $start->format('d/m/Y'), $end->format('d/m/Y'));
|
||||
}
|
||||
|
||||
/** Abrège le nom du jour pour les colonnes étroites : « Lundi » → « Lun. ». */
|
||||
private function shortDayLabel(string $label): string
|
||||
{
|
||||
return match ($label) {
|
||||
@@ -432,6 +551,7 @@ final class PlanningPdfService
|
||||
};
|
||||
}
|
||||
|
||||
/** Libellé du type de lieu, sans accent (contrainte d'encodage du PDF). */
|
||||
private function typeLabel(string $type): string
|
||||
{
|
||||
return match (strtoupper($type)) {
|
||||
|
||||
Reference in New Issue
Block a user