226 lines
8.9 KiB
PHP
226 lines
8.9 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\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;
|
||
}
|
||
}
|