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; } }