pour prod

This commit is contained in:
Loic Masi
2026-08-07 16:13:43 +02:00
commit 5fbf76868f
157 changed files with 24085 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
<?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 daffectation ou semaine invalide.']);
}
$this->access->requireStructureView((int) $structureId);
$structure = $this->structureModel->findActive((int) $structureId);
if ($structure === null) {
JsonResponse::send(404, ['error' => 'Lieu daffectation 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 daffectation ou semaine invalide.';
return;
}
$this->access->requireStructureView((int) $structureId);
$structure = $this->structureModel->findActive((int) $structureId);
if ($structure === null) {
http_response_code(404);
echo 'Lieu daffectation 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 daffectation 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 daffectation 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 daffectation.',
'details' => (getenv('PTA_DEBUG') ?: '1') !== '0' ? $e->getMessage() : null,
]);
}
JsonResponse::send(201, [
'message' => sprintf('Le lieu daffectation « %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 daffectation 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 daffectation introuvable.']);
}
try {
$this->structureModel->updateType((int) $structureId, $typeAffectation);
} catch (Throwable $e) {
JsonResponse::send(500, [
'error' => 'Impossible de modifier le type du lieu daffectation.',
'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;
}
}