Ajout des commentaires dans le code

This commit is contained in:
PTA
2026-08-20 13:22:49 +02:00
parent 4201894e28
commit 5d8c0993a6
154 changed files with 5071 additions and 47 deletions

View File

@@ -6,8 +6,38 @@ namespace App\Models;
use Throwable;
/**
* Équipes des mercredis et des vacances, et leurs contrôles d'encadrement.
*
* Le NOMBRE D'AGENTS REQUIS est calculé en SQL, au plus contraignant des deux
* règles :
* GREATEST(minimum_agents,
* CEIL(enfants_moins_6 / ratio_moins_6)
* + CEIL(enfants_6_plus / ratio_6_plus))
* Chaque tranche d'âge est arrondie au SUPÉRIEUR séparément avant d'être
* additionnée : un demi-agent n'existe pas, et sommer avant d'arrondir
* sous-estimerait l'encadrement.
*
* Les contrôles suivent la forme commune {niveau, code, message}. Aucun n'est
* bloquant : ils signalent, l'arbitrage restant humain.
*
* Cycle de vie propre aux équipes : BROUILLON → A_CONTROLER → VALIDEE, sans
* transition imposée.
*/
final class TeamModel extends BaseModel
{
/**
* Toutes les équipes, avec leurs membres et leurs contrôles.
*
* Le filtre de dates cherche les équipes qui INTERSECTENT l'intervalle
* (fin >= début demandé ET début <= fin demandée) et non celles qui y sont
* incluses : une équipe à cheval sur les bornes doit remonter.
*
* ATTENTION AU COÛT : deux requêtes supplémentaires par équipe (membres puis
* contrôles). Acceptable sur quelques dizaines d'équipes, à revoir au-delà.
*
* @return array<int, array<string, mixed>>
*/
public function all(?string $startDate = null, ?string $endDate = null): array
{
$sql = "SELECT e.id_equipe, e.id_structure, s.nom AS structure_nom,
@@ -56,6 +86,15 @@ final class TeamModel extends BaseModel
return $teams;
}
/**
* Une équipe par identifiant.
*
* Passe par all() plutôt que par une requête dédiée, afin de bénéficier des
* mêmes agrégats et contrôles. Coûteux, mais cette méthode n'est pas sur un
* chemin critique.
*
* @return array<string, mixed>|null
*/
public function find(int $teamId): ?array
{
foreach ($this->all() as $team) {
@@ -66,6 +105,17 @@ final class TeamModel extends BaseModel
return null;
}
/**
* Crée une équipe, toujours en BROUILLON.
*
* $data est passé tel quel à execute() : ses clés doivent correspondre
* exactement aux paramètres nommés. La validation est faite par
* TeamController::index().
*
* @param array<string, mixed> $data
*
* @return int Identifiant créé.
*/
public function create(array $data): int
{
$stmt = $this->pdo->prepare(
@@ -85,6 +135,20 @@ final class TeamModel extends BaseModel
return (int) $this->pdo->lastInsertId();
}
/**
* Ajoute un agent à une équipe, ou met à jour sa fonction s'il y est déjà.
*
* Un seul refus est automatique : une INTERDICTION active de l'agent pour ce
* lieu (agent_structure_souhait). Tout le reste — contraintes médicales,
* temps partiel thérapeutique — est signalé dans les contrôles de l'équipe
* et laissé à l'appréciation humaine, ces situations pouvant être compatibles
* avec certaines affectations.
*
* Les RuntimeException levées ici portent un message destiné à l'utilisateur
* final, que le contrôleur renvoie tel quel en 422.
*
* @throws \RuntimeException Équipe introuvable ou interdiction active.
*/
public function addMember(int $teamId, int $agentId, string $function, ?string $comment): void
{
$team = $this->teamDates($teamId);
@@ -125,6 +189,11 @@ final class TeamModel extends BaseModel
]);
}
/**
* Retire un agent d'une équipe.
*
* @return bool false s'il n'en faisait pas partie.
*/
public function removeMember(int $teamId, int $agentId): bool
{
$stmt = $this->pdo->prepare(
@@ -134,12 +203,27 @@ final class TeamModel extends BaseModel
return $stmt->rowCount() > 0;
}
/**
* Change le statut d'une équipe. La valeur est contrôlée par le contrôleur ;
* aucune transition n'est imposée, y compris un retour en arrière.
*/
public function setStatus(int $teamId, string $status): void
{
$stmt = $this->pdo->prepare('UPDATE equipe_pta SET statut = :statut WHERE id_equipe = :id');
$stmt->execute(['statut' => $status, 'id' => $teamId]);
}
/**
* Membres d'une équipe, enrichis de tout ce dont les contrôles ont besoin :
* diplôme, poste, lieu de rattachement, souhait exprimé pour ce lieu, et
* nombre de contraintes actives sur la période de l'équipe (sous-requête
* corrélée, calculée pour la seule fenêtre de dates de l'équipe).
*
* Le FIELD() ordonne par fonction — responsable, animation, restauration —
* plutôt qu'alphabétiquement : c'est l'ordre de lecture d'un trombinoscope.
*
* @return array<int, array<string, mixed>>
*/
private function members(int $teamId): array
{
$stmt = $this->pdo->prepare(
@@ -171,6 +255,23 @@ final class TeamModel extends BaseModel
return $stmt->fetchAll();
}
/**
* Contrôles d'une équipe.
*
* Deux ERREUR (effectif insuffisant, taux de diplômés sous le seuil), deux
* ALERTE (aucun responsable identifié, membre sous contrainte médicale) et
* une INFO signalant qu'une préférence de l'agent est satisfaite. Un INFO
* « conforme » est ajouté si rien n'a été relevé, pour distinguer l'absence
* d'anomalie d'une absence de contrôle.
*
* Le taux de diplômés est calculé sur les agents AFFECTÉS, pas sur les
* agents requis : c'est la composition réelle de l'équipe qui compte.
*
* @param array<string, mixed> $team
* @param array<int, array<string, mixed>> $members
*
* @return array<int, array<string, string>>
*/
private function controls(array $team, array $members): array
{
$controls = [];
@@ -225,6 +326,11 @@ final class TeamModel extends BaseModel
return $controls;
}
/**
* Lieu et dates d'une équipe — lecture légère, sans les agrégats de all().
*
* @return array<string, mixed>|null
*/
private function teamDates(int $teamId): ?array
{
$stmt = $this->pdo->prepare(