Ajout des commentaires dans le code
This commit is contained in:
@@ -13,6 +13,19 @@ use App\Models\StructureModel;
|
||||
use PDO;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Contrôle de couverture : effectif planifié comparé au minimum attendu.
|
||||
*
|
||||
* Chaque lieu peut déclarer des règles de couverture (jour, plage horaire,
|
||||
* nombre minimum d'agents). Ce contrôleur confronte ces règles aux plannings
|
||||
* réels et remonte les « trous ».
|
||||
*
|
||||
* Deux niveaux de droits distincts :
|
||||
* - alerts() : responsable et Service Enfance, un responsable étant forcé sur
|
||||
* son propre lieu quoi qu'il demande ;
|
||||
* - rules() / saveRules() : Service Enfance seul, les règles étant un
|
||||
* référentiel et non une donnée de saisie.
|
||||
*/
|
||||
final class CoverageController
|
||||
{
|
||||
private CoverageModel $coverageModel;
|
||||
@@ -26,6 +39,22 @@ final class CoverageController
|
||||
$this->access = new Access($pdo);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET api/coverage_alerts.php — trous de couverture d'une semaine.
|
||||
*
|
||||
* Sans structure_id, balaie TOUS les lieux actifs. Le structure_id demandé
|
||||
* est ignoré pour un responsable, écrasé par son propre lieu : un simple 403
|
||||
* l'empêcherait de consulter son écran avec une URL partagée.
|
||||
*
|
||||
* Trois familles de retours, à ne pas confondre :
|
||||
* - `alerts` : plages réellement sous-couvertes ;
|
||||
* - `validation_warnings` : couverture assurée par des plannings encore en
|
||||
* brouillon, donc pas encore fiable ;
|
||||
* - `unconfigured_structures` : lieux sans aucune règle. Ils ne peuvent pas
|
||||
* produire d'alerte, et les taire les ferait passer pour conformes.
|
||||
*
|
||||
* Paramètres GET : week, structure_id (facultatif).
|
||||
*/
|
||||
public function alerts(): void
|
||||
{
|
||||
$this->access->requireRoles([Access::ROLE_RESPONSABLE, Access::ROLE_SERVICE]);
|
||||
@@ -100,6 +129,9 @@ final class CoverageController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET api/structure_coverage_rules.php — règles de couverture d'un lieu.
|
||||
*/
|
||||
public function rules(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
@@ -119,6 +151,19 @@ final class CoverageController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST api/save_structure_coverage_rules.php — remplace les règles d'un lieu.
|
||||
*
|
||||
* Remplacement INTÉGRAL : la liste reçue devient l'unique jeu de règles du
|
||||
* lieu, et une liste vide supprime toute règle (le lieu redevient « non
|
||||
* configuré »). Il n'y a pas d'ajout ni de suppression unitaire.
|
||||
*
|
||||
* Les règles sont validées ici — jour ouvré, horaires au quart d'heure, fin
|
||||
* après début, minimum dans [1, 50], puis absence de chevauchement — avant
|
||||
* d'être confiées au modèle, qui n'effectue aucun contrôle métier.
|
||||
*
|
||||
* Charge utile : structure_id, rules[], csrf_token.
|
||||
*/
|
||||
public function saveRules(): void
|
||||
{
|
||||
$this->access->requireService();
|
||||
@@ -182,6 +227,15 @@ final class CoverageController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refuse deux plages de couverture qui se recouvrent le même jour.
|
||||
*
|
||||
* Les règles sont regroupées par jour puis triées : il suffit alors de
|
||||
* comparer chaque début à la fin précédente, en O(n log n) et non en O(n²).
|
||||
* Des plages jointives sont acceptées (une fin égale à un début).
|
||||
*
|
||||
* @param array<int, array{jour_semaine: int, heure_debut: string, heure_fin: string}> $rules
|
||||
*/
|
||||
private function assertNoOverlap(array $rules): void
|
||||
{
|
||||
$byDay = [];
|
||||
@@ -203,6 +257,7 @@ final class CoverageController
|
||||
}
|
||||
}
|
||||
|
||||
/** HH:MM au quart d'heure, entre 00:00 et 23:45. */
|
||||
private function validQuarterHour(string $time): bool
|
||||
{
|
||||
if (!preg_match('/^(?:[01]\d|2[0-3]):(?:00|15|30|45)$/', $time)) {
|
||||
@@ -211,17 +266,24 @@ final class CoverageController
|
||||
return true;
|
||||
}
|
||||
|
||||
/** « 09:30 » → 570. Comparer des minutes évite les pièges de tri de chaînes. */
|
||||
private function timeToMinutes(string $time): int
|
||||
{
|
||||
[$hours, $minutes] = array_map('intval', explode(':', $time));
|
||||
return ($hours * 60) + $minutes;
|
||||
}
|
||||
|
||||
/** Durée en minutes, plancher à 0 : jamais de total négatif dans les cumuls. */
|
||||
private function durationMinutes(string $start, string $end): int
|
||||
{
|
||||
return max(0, $this->timeToMinutes($end) - $this->timeToMinutes($start));
|
||||
}
|
||||
|
||||
/**
|
||||
* Décode « AAAA-Www ». Copie locale, comme dans les autres contrôleurs.
|
||||
*
|
||||
* @return array{0: int, 1: int}|null [année ISO, semaine], ou null.
|
||||
*/
|
||||
private function parseWeek(string $value): ?array
|
||||
{
|
||||
if (!preg_match('/^(\d{4})-W(\d{2})$/', $value, $matches)) {
|
||||
|
||||
Reference in New Issue
Block a user