53b3fd945a
Database Portability Tests / Integration (mysql) (push) Has been skipped
Database Portability Tests / Integration (postgres) (push) Has been skipped
Database Portability Tests / Integration (sqlite) (push) Has been skipped
Database Portability Tests / Verify no MySQL-specific SQL (push) Successful in 5s
Database Portability Tests / Unit Tests (PlatformHelper) (push) Failing after 43s
Inventory: - General material, stock items, and sales CRUD - Category management with CategoryPicker component - Inventory reports service - Database migrations and mappers Frontend: - Inventory view with tabs (Allgemeinmaterial, Verkaufsmaterial, Verkäufe) - Forms for general material, stock items, and sales - Search bar fix: flexbox wrapper with inline icon replaces broken NcTextField icon slot Tests: - All 1,491 tests pass (zero errors, warnings, deprecations) - BundleImportServiceTest: fixed ZipArchive empty-file deprecation - BundleImportService: null-coalescing for targetFields - PHPUnit test infra: make test target via container CLAUDE.md: - Added Testing section as PR gating criterion
292 lines
11 KiB
PHP
292 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace OCA\Mitgliederverwaltung\Controller;
|
|
|
|
use OCA\Mitgliederverwaltung\Service\EncryptedExportService;
|
|
use OCA\Mitgliederverwaltung\Service\InventoryReportService;
|
|
use OCA\Mitgliederverwaltung\Service\PdfService;
|
|
use OCA\Mitgliederverwaltung\Service\PermissionService;
|
|
use OCA\Mitgliederverwaltung\Service\ReportService;
|
|
use OCA\Mitgliederverwaltung\Service\ValidationException;
|
|
use OCP\AppFramework\ApiController;
|
|
use OCP\AppFramework\Http;
|
|
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
|
use OCP\AppFramework\Http\DataDownloadResponse;
|
|
use OCP\AppFramework\Http\JSONResponse;
|
|
use OCP\IRequest;
|
|
use OCP\IUserSession;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* REST API controller for report generation and preview.
|
|
*
|
|
* Supports:
|
|
* - Preview: returns structured JSON data for frontend table display
|
|
* - PDF download: generates and returns a PDF file
|
|
* - Encrypted PDF: wraps the PDF in a password-protected ZIP
|
|
*
|
|
* Part of Issue #46.
|
|
*/
|
|
class ReportController extends ApiController {
|
|
|
|
private ReportService $reportService;
|
|
private InventoryReportService $inventoryReportService;
|
|
private PdfService $pdfService;
|
|
private EncryptedExportService $encryptedService;
|
|
private PermissionService $permissionService;
|
|
private IUserSession $userSession;
|
|
private LoggerInterface $logger;
|
|
|
|
public function __construct(
|
|
string $appName,
|
|
IRequest $request,
|
|
ReportService $reportService,
|
|
InventoryReportService $inventoryReportService,
|
|
PdfService $pdfService,
|
|
EncryptedExportService $encryptedService,
|
|
PermissionService $permissionService,
|
|
IUserSession $userSession,
|
|
LoggerInterface $logger
|
|
) {
|
|
parent::__construct($appName, $request);
|
|
$this->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';
|
|
}
|
|
}
|