175 lines
6.3 KiB
PHP
175 lines
6.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
/**
|
|
* Référentiel des lieux d'affectation (`structure`).
|
|
*
|
|
* Toutes les lectures filtrent sur `actif = TRUE` : un lieu désactivé disparaît
|
|
* des listes et ne peut plus être choisi, mais ses plannings passés restent
|
|
* intacts. Aucune méthode de désactivation n'est exposée dans cette version, la
|
|
* bascule se fait en SQL.
|
|
*
|
|
* `type_affectation` (PERISCOLAIRE | EXTRASCOLAIRE) donne le contexte par défaut
|
|
* du lieu ; il reste croisé avec le calendrier des vacances (VacationModel).
|
|
*/
|
|
final class StructureModel extends BaseModel
|
|
{
|
|
/**
|
|
* Tous les lieux actifs, triés par nom.
|
|
*
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function allActive(): array
|
|
{
|
|
return $this->pdo->query(
|
|
'SELECT id_structure, code, nom, adresse, type_affectation FROM structure WHERE actif = TRUE ORDER BY nom'
|
|
)->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Lieu actif par identifiant.
|
|
*
|
|
* @return array<string, mixed>|null null si inexistant OU désactivé — les
|
|
* appelants traitent les deux cas de la même façon.
|
|
*/
|
|
public function findActive(int $id): ?array
|
|
{
|
|
$stmt = $this->pdo->prepare(
|
|
'SELECT id_structure, code, nom, adresse, type_affectation FROM structure WHERE id_structure = :id AND actif = TRUE LIMIT 1'
|
|
);
|
|
$stmt->execute(['id' => $id]);
|
|
return $stmt->fetch() ?: null;
|
|
}
|
|
|
|
/**
|
|
* Vrai si le code est déjà pris, DÉSACTIVÉS COMPRIS : le code reste unique
|
|
* en base, réutiliser celui d'un lieu fermé violerait la contrainte.
|
|
*/
|
|
public function codeExists(string $code): bool
|
|
{
|
|
$stmt = $this->pdo->prepare('SELECT 1 FROM structure WHERE code = :code LIMIT 1');
|
|
$stmt->execute(['code' => $code]);
|
|
return $stmt->fetchColumn() !== false;
|
|
}
|
|
|
|
/**
|
|
* Crée un lieu actif et renvoie sa fiche complète.
|
|
*
|
|
* La fiche est reconstituée en PHP plutôt que relue en base : l'appelant en
|
|
* a besoin pour l'insérer dans la liste affichée sans recharger la page.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function create(string $code, string $name, ?string $address, string $typeAffectation): array
|
|
{
|
|
$stmt = $this->pdo->prepare(
|
|
'INSERT INTO structure (code, nom, adresse, type_affectation, actif)
|
|
VALUES (:code, :nom, :adresse, :type_affectation, TRUE)'
|
|
);
|
|
$stmt->execute([
|
|
'code' => $code,
|
|
'nom' => $name,
|
|
'adresse' => $address,
|
|
'type_affectation' => $typeAffectation,
|
|
]);
|
|
|
|
return [
|
|
'id_structure' => (int) $this->pdo->lastInsertId(),
|
|
'code' => $code,
|
|
'nom' => $name,
|
|
'adresse' => $address,
|
|
'type_affectation' => $typeAffectation,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Change le type d'un lieu. La valeur est contrôlée par le contrôleur, ce
|
|
* modèle ne fait qu'écrire.
|
|
*/
|
|
public function updateType(int $structureId, string $typeAffectation): void
|
|
{
|
|
$stmt = $this->pdo->prepare(
|
|
'UPDATE structure SET type_affectation = :type_affectation WHERE id_structure = :id_structure'
|
|
);
|
|
$stmt->execute([
|
|
'type_affectation' => $typeAffectation,
|
|
'id_structure' => $structureId,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Agents à afficher pour un lieu et une semaine.
|
|
*
|
|
* Réunit DEUX populations, d'où les LEFT JOIN et le OR de la clause WHERE :
|
|
* - les agents RATTACHÉS au lieu (a.id_structure), même sans planning ;
|
|
* - les agents PLANIFIÉS sur le lieu cette semaine, quel que soit leur
|
|
* rattachement (sem.id_semaine IS NOT NULL).
|
|
*
|
|
* Omettre la seconde donnerait une vue fausse de l'effectif présent, un
|
|
* agent pouvant travailler sur plusieurs lieux la même semaine.
|
|
* `id_structure_principale` permet à l'interface de les distinguer.
|
|
*
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function agentsForOverview(int $structureId, int $year, int $week): array
|
|
{
|
|
$stmt = $this->pdo->prepare(
|
|
"SELECT DISTINCT a.id_agent, a.matricule, a.nom, a.prenom,
|
|
a.id_structure AS id_structure_principale
|
|
FROM agent a
|
|
LEFT JOIN planning p
|
|
ON p.id_agent = a.id_agent
|
|
AND p.id_structure = :structure_id_planning
|
|
LEFT JOIN semaine sem
|
|
ON sem.id_semaine = p.id_semaine
|
|
AND sem.annee = :annee
|
|
AND sem.numero_semaine = :semaine
|
|
WHERE a.actif = TRUE
|
|
AND (a.id_structure = :structure_id_default OR sem.id_semaine IS NOT NULL)
|
|
ORDER BY a.nom, a.prenom"
|
|
);
|
|
$stmt->execute([
|
|
'structure_id_planning' => $structureId,
|
|
'structure_id_default' => $structureId,
|
|
'annee' => $year,
|
|
'semaine' => $week,
|
|
]);
|
|
return $stmt->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Créneaux d'un lieu sur une semaine, tous agents confondus.
|
|
*
|
|
* Une seule lecture, regroupée ensuite par agent en PHP par l'appelant,
|
|
* plutôt qu'une requête par agent. Heures en HH:MM, format d'affichage.
|
|
*
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function entriesForOverview(int $structureId, int $year, int $week): array
|
|
{
|
|
$stmt = $this->pdo->prepare(
|
|
"SELECT p.id_agent, p.statut, c.date_jour,
|
|
TIME_FORMAT(c.heure_debut, '%H:%i') AS heure_debut,
|
|
TIME_FORMAT(c.heure_fin, '%H:%i') AS heure_fin,
|
|
m.code AS motif_code, m.libelle AS motif_libelle, m.compte_dans_quota
|
|
FROM planning p
|
|
INNER JOIN semaine s ON s.id_semaine = p.id_semaine
|
|
INNER JOIN creneau_horaire c ON c.id_planning = p.id_planning
|
|
INNER JOIN motif_planning m ON m.id_motif = c.id_motif
|
|
WHERE p.id_structure = :structure_id
|
|
AND s.annee = :annee
|
|
AND s.numero_semaine = :semaine
|
|
ORDER BY p.id_agent, c.date_jour, c.heure_debut"
|
|
);
|
|
$stmt->execute([
|
|
'structure_id' => $structureId,
|
|
'annee' => $year,
|
|
'semaine' => $week,
|
|
]);
|
|
return $stmt->fetchAll();
|
|
}
|
|
}
|