Files
pta/app/Controllers/StructureController.php
Loic Masi 5fbf76868f pour prod
2026-08-07 16:13:43 +02:00

226 lines
8.9 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Controllers;
use App\Core\Access;
use App\Core\Csrf;
use App\Core\JsonResponse;
use App\Core\PdfResponse;
use App\Core\Request;
use App\Models\CoverageModel;
use App\Models\QuotaModel;
use App\Models\StructureModel;
use App\Services\PlanningPdfService;
use DateTimeImmutable;
use PDO;
use Throwable;
final class StructureController
{
private StructureModel $structureModel;
private QuotaModel $quotaModel;
private CoverageModel $coverageModel;
private Access $access;
public function __construct(private PDO $pdo)
{
$this->structureModel = new StructureModel($pdo);
$this->quotaModel = new QuotaModel($pdo);
$this->coverageModel = new CoverageModel($pdo);
$this->access = new Access($pdo);
}
public function overview(): void
{
$structureId = filter_input(INPUT_GET, 'structure_id', FILTER_VALIDATE_INT);
$weekValue = trim((string) ($_GET['week'] ?? ''));
$week = $this->parseWeek($weekValue);
if (!$structureId || $week === null) {
JsonResponse::send(400, ['error' => 'Lieu d’affectation ou semaine invalide.']);
}
$this->access->requireStructureView((int) $structureId);
$structure = $this->structureModel->findActive((int) $structureId);
if ($structure === null) {
JsonResponse::send(404, ['error' => 'Lieu d’affectation introuvable.']);
}
[$year, $weekNumber] = $week;
$weekStart = (new DateTimeImmutable())->setISODate($year, $weekNumber, 1)->format('Y-m-d');
$weekEnd = (new DateTimeImmutable())->setISODate($year, $weekNumber, 7)->format('Y-m-d');
$entries = $this->structureModel->entriesForOverview((int) $structureId, $year, $weekNumber);
$entriesByAgent = [];
foreach ($entries as $entry) {
$entriesByAgent[(int) $entry['id_agent']][] = $entry;
}
$agents = [];
foreach ($this->structureModel->agentsForOverview((int) $structureId, $year, $weekNumber) as $agent) {
$agentId = (int) $agent['id_agent'];
$agents[] = [
'id_agent' => $agentId,
'matricule' => $agent['matricule'],
'nom' => $agent['nom'],
'prenom' => $agent['prenom'],
'lieu_principal' => (int) ($agent['id_structure_principale'] ?? 0) === (int) $structureId,
'quota' => $this->quotaModel->forDateForApi($agentId, $weekStart),
'entries' => $entriesByAgent[$agentId] ?? [],
];
}
JsonResponse::send(200, [
'structure' => $structure,
'week' => ['value' => $weekValue, 'date_debut' => $weekStart, 'date_fin' => $weekEnd],
'agents' => $agents,
'coverage' => $this->coverageModel->reportForStructure((int) $structureId, $year, $weekNumber),
]);
}
public function pdf(): void
{
$structureId = filter_input(INPUT_GET, 'structure_id', FILTER_VALIDATE_INT);
$weekValue = trim((string) ($_GET['week'] ?? ''));
$week = $this->parseWeek($weekValue);
if (!$structureId || $week === null) {
http_response_code(400);
echo 'Lieu d’affectation ou semaine invalide.';
return;
}
$this->access->requireStructureView((int) $structureId);
$structure = $this->structureModel->findActive((int) $structureId);
if ($structure === null) {
http_response_code(404);
echo 'Lieu d’affectation introuvable.';
return;
}
[$year, $weekNumber] = $week;
$weekStart = (new DateTimeImmutable())->setISODate($year, $weekNumber, 1)->format('Y-m-d');
$weekEnd = (new DateTimeImmutable())->setISODate($year, $weekNumber, 7)->format('Y-m-d');
$weekData = ['value' => $weekValue, 'date_debut' => $weekStart, 'date_fin' => $weekEnd];
$entries = $this->structureModel->entriesForOverview((int) $structureId, $year, $weekNumber);
$entriesByAgent = [];
foreach ($entries as $entry) {
$entriesByAgent[(int) $entry['id_agent']][] = $entry;
}
$agents = [];
foreach ($this->structureModel->agentsForOverview((int) $structureId, $year, $weekNumber) as $agent) {
$agentId = (int) $agent['id_agent'];
$agents[] = [
'id_agent' => $agentId,
'matricule' => $agent['matricule'],
'nom' => $agent['nom'],
'prenom' => $agent['prenom'],
'lieu_principal' => (int) ($agent['id_structure_principale'] ?? 0) === (int) $structureId,
'quota' => $this->quotaModel->forDateForApi($agentId, $weekStart),
'entries' => $entriesByAgent[$agentId] ?? [],
];
}
$pdf = (new PlanningPdfService())->structureWeek($structure, $agents, $weekData);
PdfResponse::inline(
$pdf,
sprintf('planning_lieu_%s_%s.pdf', $structure['code'] ?: $structure['id_structure'], str_replace('-W', '_S', $weekValue))
);
}
public function create(): void
{
$this->access->requireService();
Request::requireMethod('POST');
$payload = Request::json();
Csrf::assertPayload($payload);
$rawCode = trim((string) ($payload['code'] ?? ''));
$code = strtoupper((string) preg_replace('/\s+/', '_', $rawCode));
$name = trim((string) ($payload['nom'] ?? ''));
$address = trim((string) ($payload['adresse'] ?? ''));
$typeAffectation = strtoupper(trim((string) ($payload['type_affectation'] ?? '')));
if ($code === '' || $name === '') {
JsonResponse::send(422, ['error' => 'Le code et le nom du lieu d’affectation sont obligatoires.']);
}
if (!in_array($typeAffectation, ['PERISCOLAIRE', 'EXTRASCOLAIRE'], true)) {
JsonResponse::send(422, ['error' => 'Le type du lieu doit être Périscolaire ou Extrascolaire.']);
}
if (!preg_match('/^[A-Z0-9_-]{2,50}$/', $code)) {
JsonResponse::send(422, ['error' => 'Le code doit contenir uniquement des lettres, chiffres, tirets ou underscores.']);
}
if (strlen($name) > 150 || strlen($address) > 255) {
JsonResponse::send(422, ['error' => 'Un des champs dépasse la longueur autorisée.']);
}
if ($this->structureModel->codeExists($code)) {
JsonResponse::send(409, ['error' => 'Un lieu d’affectation utilise déjà ce code.']);
}
try {
$structure = $this->structureModel->create($code, $name, $address !== '' ? $address : null, $typeAffectation);
} catch (Throwable $e) {
JsonResponse::send(500, [
'error' => 'Impossible de créer le lieu d’affectation.',
'details' => (getenv('PTA_DEBUG') ?: '1') !== '0' ? $e->getMessage() : null,
]);
}
JsonResponse::send(201, [
'message' => sprintf('Le lieu d’affectation « %s » a été ajouté.', $name),
'structure' => $structure,
]);
}
public function updateType(): void
{
$this->access->requireService();
Request::requireMethod('POST');
$payload = Request::json();
Csrf::assertPayload($payload);
$structureId = filter_var($payload['structure_id'] ?? null, FILTER_VALIDATE_INT);
$typeAffectation = strtoupper(trim((string) ($payload['type_affectation'] ?? '')));
if (!$structureId) {
JsonResponse::send(422, ['error' => 'Lieu d’affectation invalide.']);
}
if (!in_array($typeAffectation, ['PERISCOLAIRE', 'EXTRASCOLAIRE'], true)) {
JsonResponse::send(422, ['error' => 'Le type du lieu doit être Périscolaire ou Extrascolaire.']);
}
$structure = $this->structureModel->findActive((int) $structureId);
if ($structure === null) {
JsonResponse::send(404, ['error' => 'Lieu d’affectation introuvable.']);
}
try {
$this->structureModel->updateType((int) $structureId, $typeAffectation);
} catch (Throwable $e) {
JsonResponse::send(500, [
'error' => 'Impossible de modifier le type du lieu d’affectation.',
'details' => (getenv('PTA_DEBUG') ?: '1') !== '0' ? $e->getMessage() : null,
]);
}
JsonResponse::send(200, [
'message' => sprintf('Le type du lieu « %s » a été mis à jour.', $structure['nom']),
'structure' => [
'id_structure' => (int) $structureId,
'type_affectation' => $typeAffectation,
],
]);
}
private function parseWeek(string $value): ?array
{
if (!preg_match('/^(\d{4})-W(\d{2})$/', $value, $matches)) {
return null;
}
$week = (int) $matches[2];
return $week >= 1 && $week <= 53 ? [(int) $matches[1], $week] : null;
}
}