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,170 @@
<?php
declare(strict_types=1);
namespace App\Controllers;
use App\Core\Access;
use App\Core\Csrf;
use App\Core\JsonResponse;
use App\Core\Request;
use App\Models\VacationModel;
use App\Services\SchoolHolidayApiService;
use DateTimeImmutable;
use PDO;
use Throwable;
final class VacationController
{
private VacationModel $vacationModel;
private Access $access;
public function __construct(private PDO $pdo)
{
$this->vacationModel = new VacationModel($pdo);
$this->access = new Access($pdo);
}
public function index(): void
{
$this->access->requireService();
$schoolYear = trim((string) ($_GET['school_year'] ?? ''));
if ($schoolYear !== '' && !$this->isSchoolYear($schoolYear)) {
JsonResponse::send(422, ['error' => 'Année scolaire invalide. Format attendu : 2026-2027.']);
}
JsonResponse::send(200, [
'periods' => $this->vacationModel->all($schoolYear !== '' ? $schoolYear : null),
]);
}
public function preview(): void
{
$this->access->requireService();
$academy = trim((string) ($_GET['academy'] ?? ''));
$calendarYearRaw = trim((string) ($_GET['year'] ?? ''));
if ($academy === '' || (function_exists('mb_strlen') ? mb_strlen($academy, 'UTF-8') : strlen($academy)) > 100) {
JsonResponse::send(422, ['error' => 'Renseignez une académie valide.']);
}
if (!preg_match('/^\d{4}$/', $calendarYearRaw)) {
JsonResponse::send(422, ['error' => 'Année civile invalide. Format attendu : 2026.']);
}
$calendarYear = (int) $calendarYearRaw;
if ($calendarYear < 2000 || $calendarYear > 2100) {
JsonResponse::send(422, ['error' => 'Année civile invalide.']);
}
try {
$result = (new SchoolHolidayApiService())->previewCalendarYearDetailed($academy, $calendarYear);
$periods = $result['periods'];
$warnings = $result['warnings'];
} catch (Throwable $e) {
JsonResponse::send(502, [
'error' => 'Impossible de charger les propositions depuis lAPI officielle.',
'details' => (getenv('PTA_DEBUG') ?: '1') !== '0' ? $e->getMessage() : null,
]);
}
JsonResponse::send(200, [
'academy' => $academy,
'year' => $calendarYear,
'periods' => $periods,
'warnings' => $warnings ?? [],
'message' => $periods === []
? 'Aucune période na été trouvée pour lannée ' . $calendarYear . '.'
: sprintf('%d période%s proposée%s pour toute lannée %d. Aucune donnée na été enregistrée en base.', count($periods), count($periods) > 1 ? 's' : '', count($periods) > 1 ? 's' : '', $calendarYear),
]);
}
public function save(): void
{
$this->access->requireService();
Request::requireMethod('POST');
$payload = Request::json();
Csrf::assertPayload($payload);
$rawPeriods = $payload['periods'] ?? [];
if (!is_array($rawPeriods) || $rawPeriods === []) {
JsonResponse::send(422, ['error' => 'Sélectionnez au moins une période à enregistrer.']);
}
if (count($rawPeriods) > 50) {
JsonResponse::send(422, ['error' => 'Trop de périodes ont été envoyées en une seule fois.']);
}
$periods = [];
foreach ($rawPeriods as $index => $raw) {
if (!is_array($raw)) {
JsonResponse::send(422, ['error' => 'Période invalide à la ligne ' . ($index + 1) . '.']);
}
$label = trim((string) ($raw['libelle'] ?? ''));
$start = trim((string) ($raw['date_debut'] ?? ''));
$end = trim((string) ($raw['date_fin'] ?? ''));
$schoolYear = trim((string) ($raw['annee_scolaire'] ?? ''));
$academy = trim((string) ($raw['academie'] ?? ''));
$zone = trim((string) ($raw['zone'] ?? ''));
$source = strtoupper(trim((string) ($raw['source'] ?? 'MANUEL')));
if ($label === '' || (function_exists('mb_strlen') ? mb_strlen($label, 'UTF-8') : strlen($label)) > 150 || !$this->isDate($start) || !$this->isDate($end) || $end < $start) {
JsonResponse::send(422, ['error' => 'Dates ou libellé invalides à la ligne ' . ($index + 1) . '.']);
}
if (!$this->isSchoolYear($schoolYear)) {
JsonResponse::send(422, ['error' => 'Année scolaire invalide à la ligne ' . ($index + 1) . '.']);
}
if (!in_array($source, ['MANUEL', 'DATA_GOUV'], true)) {
$source = 'MANUEL';
}
$periods[] = [
'libelle' => $label,
'date_debut' => $start,
'date_fin' => $end,
'annee_scolaire' => $schoolYear,
'academie' => function_exists('mb_substr') ? mb_substr($academy, 0, 100, 'UTF-8') : substr($academy, 0, 100),
'zone' => function_exists('mb_substr') ? mb_substr($zone, 0, 50, 'UTF-8') : substr($zone, 0, 50),
'source' => $source,
];
}
$count = $this->vacationModel->saveMany($periods);
JsonResponse::send(200, [
'success' => true,
'count' => $count,
'message' => sprintf('%d période%s de vacances enregistrée%s après validation manuelle.', $count, $count > 1 ? 's' : '', $count > 1 ? 's' : ''),
]);
}
public function delete(): void
{
$this->access->requireService();
Request::requireMethod('POST');
$payload = Request::json();
Csrf::assertPayload($payload);
$id = filter_var($payload['period_id'] ?? null, FILTER_VALIDATE_INT);
if (!$id) {
JsonResponse::send(422, ['error' => 'Période invalide.']);
}
if (!$this->vacationModel->delete((int) $id)) {
JsonResponse::send(404, ['error' => 'Période introuvable.']);
}
JsonResponse::send(200, ['success' => true, 'message' => 'Période supprimée.']);
}
private function isDate(string $value): bool
{
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $value);
return $date !== false && $date->format('Y-m-d') === $value;
}
private function isSchoolYear(string $value): bool
{
if (!preg_match('/^(\d{4})-(\d{4})$/', $value, $matches)) {
return false;
}
return (int) $matches[2] === (int) $matches[1] + 1;
}
}