reportService = $reportService; $this->inventoryReportService = $inventoryReportService; $this->pdfService = $pdfService; $this->encryptedService = $encryptedService; $this->permissionService = $permissionService; $this->userSession = $userSession; $this->logger = $logger; } /** * List available report types. * * GET /api/v1/reports */ #[NoCSRFRequired] public function index(): JSONResponse { return new JSONResponse([ 'reports' => [ ['id' => 'mitgliederliste', 'name' => 'Mitgliederliste', 'params' => ['status']], ['id' => 'beitragsliste', 'name' => 'Beitragsliste', 'params' => ['year']], ['id' => 'stufenliste', 'name' => 'Stufenliste', 'params' => []], ['id' => 'allergieliste', 'name' => 'Allergieliste', 'params' => []], ['id' => 'geburtstagsliste', 'name' => 'Geburtstagsliste', 'params' => ['months']], ['id' => 'kontaktliste', 'name' => 'Kontaktliste', 'params' => ['stufeId']], ['id' => 'bankverbindungen', 'name' => 'Bankverbindungen', 'params' => [], 'sensitive' => true], ['id' => 'familienliste', 'name' => 'Familienliste', 'params' => []], ['id' => 'lagerhistorie', 'name' => 'Lagerhistorie', 'params' => ['memberId']], ['id' => 'verletzungsprotokoll', 'name' => 'Verletzungsprotokoll', 'params' => ['dateFrom', 'dateTo', 'memberId']], ['id' => 'inventur-verkaeufe', 'name' => 'Inventur-Verkaeufe', 'params' => ['dateFrom', 'dateTo']], ['id' => 'materialzustand', 'name' => 'Materialzustand', 'params' => []], ], ]); } /** * Preview report data as JSON (for frontend table display). * * GET /api/v1/reports/{type}/preview?...params */ #[NoCSRFRequired] public function preview(string $type): JSONResponse { try { $data = $this->generateReportData($type); if ($data === null) { return new JSONResponse( ['error' => "Unbekannter Berichtstyp: {$type}"], Http::STATUS_BAD_REQUEST ); } return new JSONResponse($data); } catch (ValidationException $e) { return new JSONResponse( ['error' => $e->getMessage()], Http::STATUS_FORBIDDEN ); } catch (\Exception $e) { $this->logger->error('Failed to generate report preview', [ 'type' => $type, 'exception' => $e, 'app' => 'mitgliederverwaltung', ]); return new JSONResponse( ['error' => 'Berichtsvorschau fehlgeschlagen'], Http::STATUS_INTERNAL_SERVER_ERROR ); } } /** * Download report as PDF. * * GET /api/v1/reports/{type}/pdf?...params */ #[NoCSRFRequired] public function pdf(string $type): DataDownloadResponse|JSONResponse { try { $data = $this->generateReportData($type); if ($data === null) { return new JSONResponse( ['error' => "Unbekannter Berichtstyp: {$type}"], Http::STATUS_BAD_REQUEST ); } $result = $this->pdfService->generateReport($data); return new DataDownloadResponse( $result['content'], $result['filename'], 'application/pdf' ); } catch (ValidationException $e) { return new JSONResponse( ['error' => $e->getMessage()], Http::STATUS_FORBIDDEN ); } catch (\Exception $e) { $this->logger->error('Failed to generate PDF report', [ 'type' => $type, 'exception' => $e, 'app' => 'mitgliederverwaltung', ]); return new JSONResponse( ['error' => 'PDF-Erzeugung fehlgeschlagen'], Http::STATUS_INTERNAL_SERVER_ERROR ); } } /** * Download report as encrypted PDF (password-protected ZIP). * * POST /api/v1/reports/{type}/encrypted * Body: { "password": "...", ...params } */ public function encrypted(string $type): DataDownloadResponse|JSONResponse { try { $password = $this->request->getParam('password'); if ($password === null || $password === '') { return new JSONResponse( ['error' => 'Passwort ist erforderlich'], Http::STATUS_BAD_REQUEST ); } $data = $this->generateReportData($type); if ($data === null) { return new JSONResponse( ['error' => "Unbekannter Berichtstyp: {$type}"], Http::STATUS_BAD_REQUEST ); } $pdfResult = $this->pdfService->generateReport($data); $zipResult = $this->encryptedService->createEncryptedExport( $pdfResult['content'], $pdfResult['filename'], $password, $data['title'] ); return new DataDownloadResponse( $zipResult['content'], $zipResult['filename'], 'application/zip' ); } catch (ValidationException $e) { return new JSONResponse( ['error' => $e->getMessage()], Http::STATUS_FORBIDDEN ); } catch (\Exception $e) { $this->logger->error('Failed to generate encrypted report', [ 'type' => $type, 'exception' => $e, 'app' => 'mitgliederverwaltung', ]); return new JSONResponse( ['error' => 'Verschluesselter Bericht fehlgeschlagen'], Http::STATUS_INTERNAL_SERVER_ERROR ); } } /** * Route to the appropriate report generator based on type. * * @return array{title: string, headers: string[], rows: array[]}|null */ private function generateReportData(string $type): ?array { return match ($type) { 'mitgliederliste' => $this->reportService->generateMitgliederliste( $this->request->getParam('status') ), 'beitragsliste' => $this->reportService->generateBeitragsliste( (int)($this->request->getParam('year') ?? (new \DateTime())->format('Y')) ), 'stufenliste' => $this->reportService->generateStufenliste(), 'allergieliste' => $this->reportService->generateAllergieliste(), 'geburtstagsliste' => $this->reportService->generateGeburtstagsliste( (int)($this->request->getParam('months') ?? 3) ), 'kontaktliste' => $this->reportService->generateKontaktliste( $this->request->getParam('stufeId') !== null ? (int)$this->request->getParam('stufeId') : null ), 'bankverbindungen' => $this->generateBankverbindungenWithPermCheck(), 'familienliste' => $this->reportService->generateFamilienliste(), 'lagerhistorie' => $this->reportService->generateLagerhistorie( $this->request->getParam('memberId') !== null ? (int)$this->request->getParam('memberId') : null ), 'verletzungsprotokoll' => $this->reportService->generateVerletzungsprotokoll( $this->request->getParam('dateFrom') ?: null, $this->request->getParam('dateTo') ?: null, $this->request->getParam('memberId') !== null ? (int)$this->request->getParam('memberId') : null ), 'inventur-verkaeufe' => $this->inventoryReportService->generateSalesReport( $this->request->getParam('dateFrom') ?: null, $this->request->getParam('dateTo') ?: null ), 'materialzustand' => $this->inventoryReportService->generateConditionReport(), default => null, }; } /** * Generate Bankverbindungen report with permission check. * * @return array{title: string, headers: string[], rows: array[]} * @throws ValidationException */ private function generateBankverbindungenWithPermCheck(): array { $userId = $this->getCurrentUserId(); if (!$this->permissionService->canSeeBanking($userId)) { throw new ValidationException( 'Keine Berechtigung: Bankdaten-Bericht erfordert Bankdaten-Sichtbarkeit.' ); } return $this->reportService->generateBankverbindungen(); } private function getCurrentUserId(): string { $user = $this->userSession->getUser(); return $user ? $user->getUID() : 'system'; } }