171 lines
6.5 KiB
PHP
171 lines
6.5 KiB
PHP
<?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 l’API 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 n’a été trouvée pour l’année ' . $calendarYear . '.'
|
||
: sprintf('%d période%s proposée%s pour toute l’année %d. Aucune donnée n’a é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;
|
||
}
|
||
}
|