Ajout des commentaires dans le code
This commit is contained in:
@@ -9,10 +9,42 @@ use DatePeriod;
|
||||
use DateTimeImmutable;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Construction du calendrier annuel d'un agent, du 1er janvier au 31 décembre.
|
||||
*
|
||||
* Produit une structure entièrement prête à l'affichage : les 365 jours sont
|
||||
* calculés côté serveur, avec leurs libellés, classes CSS et infobulles. Le
|
||||
* JavaScript ne fait que rendre le tableau — c'est ce qui garantit que le
|
||||
* calendrier reste identique à l'écran, au PDF et à l'impression.
|
||||
*
|
||||
* MODÈLE DES QUATRE COLONNES par jour (constante CELL_KEYS) :
|
||||
* AP = Animation Périscolaire PP = Préparation Périscolaire
|
||||
* AE = Animation Extrascolaire PE = Préparation Extrascolaire
|
||||
*
|
||||
* Le classement d'un créneau dans l'une des quatre cases dépend de son motif et,
|
||||
* pour les motifs génériques, du caractère scolaire ou non de la journée
|
||||
* (voir cellForEntry()).
|
||||
*/
|
||||
final class AnnualOverviewModel extends BaseModel
|
||||
{
|
||||
private const CELL_KEYS = ['AP', 'PP', 'AE', 'PE'];
|
||||
|
||||
/**
|
||||
* Calendrier annuel complet et ses totaux.
|
||||
*
|
||||
* Les créneaux sont lus EN UNE FOIS pour toute l'année puis indexés par date
|
||||
* en mémoire : 365 requêtes seraient inenvisageables.
|
||||
*
|
||||
* Trois familles de totaux cumulées en parallèle : par case (AP/PP/AE/PE),
|
||||
* par statut (validé / brouillon) et par motif particulier (congé,
|
||||
* formation, absence…). Elles répondent à trois questions différentes et ne
|
||||
* se déduisent pas les unes des autres.
|
||||
*
|
||||
* @return array<string, mixed> agent, year, quota_periods, calendar_totals,
|
||||
* months (12 entrées) et legend.
|
||||
*
|
||||
* @throws RuntimeException Si l'agent est introuvable ou inactif.
|
||||
*/
|
||||
public function overview(int $agentId, int $year): array
|
||||
{
|
||||
$agentModel = new AgentModel($this->pdo);
|
||||
@@ -100,6 +132,27 @@ final class AnnualOverviewModel extends BaseModel
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit UNE journée du calendrier à partir de ses créneaux.
|
||||
*
|
||||
* Les quatre cases sont toujours présentes, même vides : la grille HTML doit
|
||||
* garder un nombre de colonnes constant.
|
||||
*
|
||||
* Une case peut recevoir plusieurs créneaux ; elle prend alors la classe CSS
|
||||
* du motif le plus « fort » (voir strongestClass()), et son contenu affiche
|
||||
* les codes courts plutôt que la durée dès qu'un motif particulier est
|
||||
* présent — « CA » est plus parlant que « 7h00 » pour un jour de congé.
|
||||
*
|
||||
* `structure_id` retient le lieu du PREMIER créneau du jour, ou à défaut le
|
||||
* rattachement de l'agent : il sert à construire les liens profonds vers
|
||||
* l'écran de saisie.
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $entries Créneaux de la journée.
|
||||
* @param array<string, mixed>|null $vacation Période de vacances, ou null.
|
||||
* @param array<string, mixed> $agent Fiche de l'agent.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildDay(DateTimeImmutable $date, array $entries, ?array $vacation, array $agent): array
|
||||
{
|
||||
$cells = [];
|
||||
@@ -186,6 +239,18 @@ final class AnnualOverviewModel extends BaseModel
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Périodes de quota à afficher pour l'année.
|
||||
*
|
||||
* Trois dates de contexte sont interrogées (1er janvier, 31 décembre, et le
|
||||
* début de contrat s'il tombe dans l'année), puis dédoublonnées par leurs
|
||||
* bornes. Depuis que le quota suit l'année civile, elles donnent toutes la
|
||||
* même période : le résultat ne contient donc en pratique qu'une entrée. La
|
||||
* mécanique est conservée parce qu'elle reste juste si une règle de
|
||||
* proratisation en cours d'année est réintroduite.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function quotaPeriodsForYear(int $agentId, int $year, array $agent): array
|
||||
{
|
||||
$quotaModel = new QuotaModel($this->pdo);
|
||||
@@ -210,6 +275,14 @@ final class AnnualOverviewModel extends BaseModel
|
||||
return array_values($periods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choisit la case (AP/PP/AE/PE) d'un créneau selon son motif.
|
||||
*
|
||||
* Un motif non reconnu bascule sur le contexte de la journée : animation
|
||||
* extrascolaire pendant les vacances, périscolaire sinon. C'est le
|
||||
* comportement le moins surprenant pour un motif ajouté en base après coup,
|
||||
* et il évite qu'un temps disparaisse du calendrier.
|
||||
*/
|
||||
private function cellForEntry(string $code, bool $isVacation): string
|
||||
{
|
||||
return match ($code) {
|
||||
@@ -222,6 +295,12 @@ final class AnnualOverviewModel extends BaseModel
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Abréviation affichée dans la case pour les motifs PARTICULIERS.
|
||||
*
|
||||
* @return string|null null pour les motifs de travail ordinaires, qui
|
||||
* affichent alors leur durée plutôt qu'un code.
|
||||
*/
|
||||
private function shortCode(string $code): ?string
|
||||
{
|
||||
return match ($code) {
|
||||
@@ -236,6 +315,10 @@ final class AnnualOverviewModel extends BaseModel
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe CSS d'un créneau, à partir de son motif et, à défaut, de sa case.
|
||||
* Les classes correspondantes sont définies dans assets/style.css.
|
||||
*/
|
||||
private function classForCode(string $code, string $cellKey): string
|
||||
{
|
||||
return match ($code) {
|
||||
@@ -251,6 +334,17 @@ final class AnnualOverviewModel extends BaseModel
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitre la classe d'une case contenant plusieurs motifs.
|
||||
*
|
||||
* Une seule couleur par case : c'est la plus « forte » qui l'emporte, selon
|
||||
* une priorité croissante allant du travail ordinaire à la journée non
|
||||
* travaillée. Une demi-journée de congé doit rester visible à côté d'une
|
||||
* demi-journée travaillée ; l'inverse ferait disparaître l'information.
|
||||
*
|
||||
* Le `>=` fait gagner le dernier créneau à priorité égale, ce qui est sans
|
||||
* conséquence puisque la classe est alors la même.
|
||||
*/
|
||||
private function strongestClass(string $current, string $candidate): string
|
||||
{
|
||||
$priority = [
|
||||
@@ -268,6 +362,12 @@ final class AnnualOverviewModel extends BaseModel
|
||||
return ($priority[$candidate] ?? 0) >= ($priority[$current] ?? 0) ? $candidate : $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contenu textuel d'une case : rien si vide, les codes courts s'il y en a,
|
||||
* la durée compacte sinon.
|
||||
*
|
||||
* @param array<int, string> $codes
|
||||
*/
|
||||
private function cellDisplay(int $minutes, array $codes): string
|
||||
{
|
||||
if ($minutes <= 0) {
|
||||
@@ -279,6 +379,15 @@ final class AnnualOverviewModel extends BaseModel
|
||||
return $this->formatCompactMinutes($minutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Période de vacances contenant cette date, ou null.
|
||||
* Copie locale de VacationModel::vacationForDate() : les périodes sont déjà
|
||||
* chargées en mémoire pour toute l'année, inutile de repasser par le modèle.
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $vacations
|
||||
*
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function vacationForDate(string $date, array $vacations): ?array
|
||||
{
|
||||
foreach ($vacations as $vacation) {
|
||||
@@ -289,6 +398,7 @@ final class AnnualOverviewModel extends BaseModel
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Durée en minutes entre deux HH:MM, plancher à 0. */
|
||||
private function minutesBetween(string $start, string $end): int
|
||||
{
|
||||
[$startHour, $startMinute] = array_map('intval', explode(':', $start));
|
||||
@@ -296,6 +406,14 @@ final class AnnualOverviewModel extends BaseModel
|
||||
return max(0, ($endHour * 60 + $endMinute) - ($startHour * 60 + $startMinute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Décline chaque total en trois formes : minutes brutes, durée lisible
|
||||
* (« 7 h 30 ») et forme compacte (« 7h30 ») pour les cases étroites.
|
||||
*
|
||||
* @param array<string, int> $totals
|
||||
*
|
||||
* @return array<string, array{minutes: int, duration: string, compact: string}>
|
||||
*/
|
||||
private function formatTotals(array $totals): array
|
||||
{
|
||||
$formatted = [];
|
||||
@@ -309,6 +427,7 @@ final class AnnualOverviewModel extends BaseModel
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/** 450 → « 7 h 30 ». Gère les valeurs négatives (signe extrait d'abord). */
|
||||
private function formatMinutes(int $minutes): string
|
||||
{
|
||||
$sign = $minutes < 0 ? '-' : '';
|
||||
@@ -316,6 +435,7 @@ final class AnnualOverviewModel extends BaseModel
|
||||
return sprintf('%s%d h %02d', $sign, intdiv($minutes, 60), $minutes % 60);
|
||||
}
|
||||
|
||||
/** 450 → « 7h30 », 420 → « 7h », 0 → chaîne vide. Pour les cases étroites. */
|
||||
private function formatCompactMinutes(int $minutes): string
|
||||
{
|
||||
if ($minutes <= 0) {
|
||||
@@ -326,6 +446,7 @@ final class AnnualOverviewModel extends BaseModel
|
||||
return $mins === 0 ? $hours . 'h' : sprintf('%dh%02d', $hours, $mins);
|
||||
}
|
||||
|
||||
/** Nom du mois en français. Table en dur : le conteneur n'a pas de locale fr. */
|
||||
private function monthName(int $month): string
|
||||
{
|
||||
return [
|
||||
@@ -335,6 +456,7 @@ final class AnnualOverviewModel extends BaseModel
|
||||
][$month];
|
||||
}
|
||||
|
||||
/** Initiale du jour (1 = lundi). Mardi et Mercredi partagent « M ». */
|
||||
private function weekdayShort(int $weekday): string
|
||||
{
|
||||
return [1 => 'L', 2 => 'M', 3 => 'M', 4 => 'J', 5 => 'V', 6 => 'S', 7 => 'D'][$weekday];
|
||||
|
||||
Reference in New Issue
Block a user