design anpassung

This commit is contained in:
2026-06-23 00:06:31 +02:00
parent 1891ec0a51
commit cbea23083d
30 changed files with 785 additions and 547 deletions
+4 -4
View File
@@ -271,7 +271,7 @@ final class ExportService
private function assertTenantId(int $tenantId): void
{
if ($tenantId <= 0) {
throw new RuntimeException('Die Tenant-ID muss groesser als 0 sein.');
throw new RuntimeException('Die Standort-ID muss größer als 0 sein.');
}
}
@@ -283,7 +283,7 @@ final class ExportService
private function openCsvOutput(string $filename)
{
if (headers_sent($file, $line)) {
throw new RuntimeException(sprintf('CSV-Header koennen nicht mehr gesendet werden (%s:%d).', $file, $line));
throw new RuntimeException(sprintf('Export-Header können nicht mehr gesendet werden (%s:%d).', $file, $line));
}
$safeFilename = $this->sanitizeFilename($filename);
@@ -299,7 +299,7 @@ final class ExportService
$handle = fopen('php://output', 'wb');
if ($handle === false) {
throw new RuntimeException('php://output konnte nicht geoeffnet werden.');
throw new RuntimeException('Die Exportausgabe konnte nicht geöffnet werden.');
}
fwrite($handle, "\xEF\xBB\xBF");
@@ -330,7 +330,7 @@ final class ExportService
}, $row);
if (fputcsv($handle, $values, self::CSV_DELIMITER) === false) {
throw new RuntimeException('Die CSV-Zeile konnte nicht geschrieben werden.');
throw new RuntimeException('Die Exportzeile konnte nicht geschrieben werden.');
}
}
+15 -15
View File
@@ -278,7 +278,7 @@ final class LedgerService
$this->assertTenantMembership($tenantId, $createdByUserId);
if (trim($title) === '') {
throw new RuntimeException('Bitte einen Titel fuer die Papierliste angeben.');
throw new RuntimeException('Bitte einen Titel für die Liste angeben.');
}
$statement = $this->pdo->prepare(
@@ -345,11 +345,11 @@ final class LedgerService
$paperSheet = $sheet->fetch();
if (!$paperSheet) {
throw new RuntimeException('Die Papierliste wurde nicht gefunden.');
throw new RuntimeException('Die Liste wurde nicht gefunden.');
}
if ($paperSheet['status'] !== 'draft') {
throw new RuntimeException('Nur Entwuerfe koennen erweitert werden.');
throw new RuntimeException('Nur Entwürfe können erweitert werden.');
}
$statement = $this->pdo->prepare(
@@ -402,11 +402,11 @@ final class LedgerService
$sheet = $sheetStatement->fetch();
if (!$sheet) {
throw new RuntimeException('Die Papierliste wurde nicht gefunden.');
throw new RuntimeException('Die Liste wurde nicht gefunden.');
}
if ($sheet['status'] !== 'draft') {
throw new RuntimeException('Die Papierliste wurde bereits verbucht.');
throw new RuntimeException('Die Liste wurde bereits verbucht.');
}
$linesStatement = $this->pdo->prepare(
@@ -419,7 +419,7 @@ final class LedgerService
$lines = $linesStatement->fetchAll();
if ($lines === []) {
throw new RuntimeException('Die Papierliste enthaelt noch keine Positionen.');
throw new RuntimeException('Die Liste enthält noch keine Positionen.');
}
foreach ($lines as $line) {
@@ -431,7 +431,7 @@ final class LedgerService
$line['effective_at'] ?: gmdate('Y-m-d H:i:s'),
$actorUserId,
(float) $line['quantity'],
$line['note'] ?: 'Papierliste',
$line['note'] ?: 'Liste',
(int) $line['id'],
'sheet:' . $sheetId
);
@@ -472,7 +472,7 @@ final class LedgerService
$lines = $linesStatement->fetchAll();
if ($lines === []) {
throw new RuntimeException('Es wurden keine Ledger-Zeilen fuer den Vorgang gefunden.');
throw new RuntimeException('Es wurden keine Buchungszeilen für den Vorgang gefunden.');
}
$description = 'Storno: ' . $entry['description'];
@@ -583,7 +583,7 @@ final class LedgerService
$entryIds = $statement->fetchAll(PDO::FETCH_COLUMN);
if ($entryIds === []) {
throw new RuntimeException('Zu dieser Papierliste wurden keine verbuchten Eintraege gefunden.');
throw new RuntimeException('Zu dieser Liste wurden keine verbuchten Einträge gefunden.');
}
$reversed = 0;
@@ -597,13 +597,13 @@ final class LedgerService
$tenantId,
(int) $entryId,
$actorUserId,
$reason !== null && trim($reason) !== '' ? $reason : 'Papierliste #' . $sheetId
$reason !== null && trim($reason) !== '' ? $reason : 'Liste #' . $sheetId
);
$reversed++;
}
if ($reversed === 0) {
throw new RuntimeException('Diese Papierliste wurde bereits vollstaendig storniert.');
throw new RuntimeException('Diese Liste wurde bereits vollständig storniert.');
}
$this->audit->log($tenantId, $actorUserId, 'paper_sheet.reversed', 'paper_sheet', $sheetId, 'success', [
@@ -633,7 +633,7 @@ final class LedgerService
$price = $statement->fetchColumn();
if ($price === false) {
throw new RuntimeException('Fuer das Produkt ist kein aktiver Preis hinterlegt.');
throw new RuntimeException('Für das Produkt ist kein aktiver Preis hinterlegt.');
}
return (int) $price;
@@ -668,7 +668,7 @@ final class LedgerService
]);
if (!$statement->fetchColumn()) {
throw new RuntimeException('Das gewaehlte Mitglied gehoert nicht zu diesem Mandanten.');
throw new RuntimeException('Das gewählte Mitglied gehört nicht zu diesem Standort.');
}
}
@@ -683,7 +683,7 @@ final class LedgerService
]);
if (!$statement->fetchColumn()) {
throw new RuntimeException('Das gewaehlte Produkt gehoert nicht zu diesem Mandanten.');
throw new RuntimeException('Das gewählte Produkt gehört nicht zu dieser Kaffeeliste.');
}
}
@@ -703,7 +703,7 @@ final class LedgerService
]);
if (!$statement->fetchColumn()) {
throw new RuntimeException('Der Benutzer ist fuer diesen Mandanten nicht aktiv.');
throw new RuntimeException('Der Benutzer ist für diesen Standort nicht aktiv.');
}
}
+3 -3
View File
@@ -20,11 +20,11 @@ final class MailerService
$textBody = trim($textBody);
if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
throw new RuntimeException('Die Zieladresse fuer den Mailversand ist ungueltig.');
throw new RuntimeException('Die Zieladresse für den Mailversand ist ungültig.');
}
if ($subject === '' || $textBody === '') {
throw new RuntimeException('Betreff und Inhalt fuer den Mailversand sind erforderlich.');
throw new RuntimeException('Betreff und Inhalt für den Mailversand sind erforderlich.');
}
$subjectLine = $this->encodeSubject($this->sanitizeHeaderValue($this->subjectPrefix() . $subject));
@@ -61,7 +61,7 @@ final class MailerService
'MIME-Version: 1.0',
'Content-Type: text/plain; charset=UTF-8',
'Content-Transfer-Encoding: 8bit',
'X-Mailer: Kaffeeliste-Neustart',
'X-Mailer: Kaffeeliste',
];
$from = trim((string) ($this->config['from'] ?? ''));
+11 -11
View File
@@ -40,13 +40,13 @@ final class PasswordResetService
public function createTokenForUser(int $userId, array $context = []): array
{
if ($userId <= 0) {
throw new RuntimeException('Der Benutzer fuer den Passwort-Reset ist ungueltig.');
throw new RuntimeException('Der Benutzer für den Passwortwechsel ist ungültig.');
}
$user = $this->findUserById($userId);
if (!$user) {
throw new RuntimeException('Der Benutzer fuer den Passwort-Reset wurde nicht gefunden.');
throw new RuntimeException('Der Benutzer für den Passwortwechsel wurde nicht gefunden.');
}
$nowTs = time();
@@ -204,7 +204,7 @@ final class PasswordResetService
$newPassword = (string) $newPassword;
if ($token === '') {
throw new RuntimeException('Der Reset-Link ist ungueltig oder abgelaufen.');
throw new RuntimeException('Der Link ist ungültig oder abgelaufen.');
}
if (mb_strlen($newPassword) < 12) {
@@ -229,7 +229,7 @@ final class PasswordResetService
$resetToken = $statement->fetch();
if (!$resetToken) {
throw new RuntimeException('Der Reset-Link ist ungueltig oder abgelaufen.');
throw new RuntimeException('Der Link ist ungültig oder abgelaufen.');
}
$expiresAtTs = $this->timestampFromDatabase($resetToken['expires_at']);
@@ -239,7 +239,7 @@ final class PasswordResetService
|| $expiresAtTs === null
|| $expiresAtTs < $nowTs
) {
throw new RuntimeException('Der Reset-Link ist ungueltig oder abgelaufen.');
throw new RuntimeException('Der Link ist ungültig oder abgelaufen.');
}
$updateUserStatement = $this->pdo->prepare(
@@ -314,14 +314,14 @@ final class PasswordResetService
private function sendResetMail(array $token): void
{
$name = trim((string) ($token['full_name'] ?? ''));
$greeting = $name !== '' ? 'Hallo ' . $name . ',' : 'Hallo,';
$greeting = $name !== '' ? 'Guten Tag ' . $name . ',' : 'Guten Tag,';
$body = implode("\n\n", [
$greeting,
'fuer dein Konto wurde ein Passwort-Reset angefragt.',
'Bitte nutze diesen Link, um ein neues Passwort zu setzen:',
'für Ihr Konto wurde ein Passwortwechsel angefragt.',
'Bitte nutzen Sie diesen Link, um ein neues Passwort zu setzen:',
(string) $token['reset_url'],
'Der Link ist gueltig bis ' . (string) $token['expires_at'] . ' UTC.',
'Falls du den Reset nicht angefragt hast, kannst du diese Nachricht ignorieren.',
'Der Link ist gültig bis ' . (string) $token['expires_at'] . '.',
'Falls Sie den Passwortwechsel nicht angefragt haben, können Sie diese Nachricht ignorieren.',
]);
$this->mailer->send(
@@ -410,7 +410,7 @@ final class PasswordResetService
{
$subject = trim((string) ($this->config['subject'] ?? ''));
return $subject !== '' ? $subject : 'Passwort zuruecksetzen';
return $subject !== '' ? $subject : 'Passwort zurücksetzen';
}
private function ttlMinutes(): int
+4 -4
View File
@@ -16,7 +16,7 @@ final class RfidService
public function createDevice(int $tenantId, string $name, ?string $location, int $actorUserId): string
{
if (trim($name) === '') {
throw new RuntimeException('Bitte einen Geraetenamen angeben.');
throw new RuntimeException('Bitte einen Namen für den Kartenleser angeben.');
}
$token = bin2hex(random_bytes(20));
@@ -52,7 +52,7 @@ final class RfidService
]);
if (!$memberStatement->fetchColumn()) {
throw new RuntimeException('Das ausgewaehlte Mitglied ist fuer RFID nicht verfuegbar.');
throw new RuntimeException('Das ausgewählte Mitglied ist für Karten nicht verfügbar.');
}
$statement = $this->pdo->prepare(
@@ -89,11 +89,11 @@ final class RfidService
$device = $deviceStatement->fetch();
if (!$device) {
throw new RuntimeException('Das RFID-Geraet ist unbekannt.');
throw new RuntimeException('Der Kartenleser ist unbekannt.');
}
if (($device['status'] ?? '') !== 'active') {
throw new RuntimeException('Das RFID-Geraet ist nicht aktiv.');
throw new RuntimeException('Der Kartenleser ist nicht aktiv.');
}
$status = 'received';
+6 -6
View File
@@ -31,12 +31,12 @@ final class SetupService
foreach ($required as $field) {
if (trim((string) ($input[$field] ?? '')) === '') {
throw new RuntimeException('Bitte alle Pflichtfelder ausfuellen.');
throw new RuntimeException('Bitte alle Pflichtfelder ausfüllen.');
}
}
if (!filter_var($input['admin_email'], FILTER_VALIDATE_EMAIL)) {
throw new RuntimeException('Die Admin-E-Mail ist ungueltig.');
throw new RuntimeException('Die Admin-E-Mail ist ungültig.');
}
if (mb_strlen($input['admin_password']) < 12) {
@@ -171,7 +171,7 @@ final class SetupService
$rfidSecret = bin2hex(random_bytes(24));
$lines = [
'APP_NAME="Kaffeekasse SaaS"',
'APP_NAME="Kaffeeliste"',
'APP_ENV=production',
'APP_DEBUG=0',
'APP_URL=' . $appUrl,
@@ -206,14 +206,14 @@ final class SetupService
'',
'MAIL_FROM=' . $mailFrom,
'MAIL_REPLY_TO=' . $mailFrom,
'MAIL_SUBJECT_PREFIX="[Kaffeekasse] "',
'MAIL_SUBJECT_PREFIX="[Kaffeeliste] "',
'MAIL_LOG_FALLBACK=1',
'MAIL_LOG_FILE=storage/logs/mail.log',
'',
'PASSWORD_RESET_URL=' . $appUrl . '/reset-password?token={{token}}',
'PASSWORD_RESET_TTL_MINUTES=60',
'PASSWORD_RESET_MAX_ACTIVE_TOKENS=3',
'PASSWORD_RESET_SUBJECT="Passwort zuruecksetzen"',
'PASSWORD_RESET_SUBJECT="Passwort zurücksetzen"',
'',
'RATE_LIMIT_LOGIN_MAX_ATTEMPTS=5',
'RATE_LIMIT_LOGIN_WINDOW_SECONDS=900',
@@ -242,7 +242,7 @@ final class SetupService
}
if (!filter_var($mailFrom, FILTER_VALIDATE_EMAIL)) {
throw new RuntimeException('Die Mail-Absenderadresse ist ungueltig.');
throw new RuntimeException('Die Mail-Absenderadresse ist ungültig.');
}
return $mailFrom;
+6 -6
View File
@@ -28,7 +28,7 @@ final class TenantRegistrationService
}
if (!filter_var($ownerEmail, FILTER_VALIDATE_EMAIL)) {
throw new RuntimeException('Die E-Mail-Adresse ist ungueltig.');
throw new RuntimeException('Die E-Mail-Adresse ist ungültig.');
}
if (mb_strlen($ownerPassword) < 12) {
@@ -52,7 +52,7 @@ final class TenantRegistrationService
$existingUser->execute(['email' => $ownerEmail]);
if ($existingUser->fetchColumn()) {
throw new RuntimeException('Die E-Mail-Adresse ist bereits vorhanden. Fuer weitere Tenants bitte spaeter Einladungen nutzen.');
throw new RuntimeException('Die E-Mail-Adresse ist bereits vorhanden. Bitte verwenden Sie eine andere Adresse.');
}
$now = gmdate('Y-m-d H:i:s');
@@ -131,10 +131,10 @@ final class TenantRegistrationService
);
foreach ([
['digital_self', 'Digitale Selbstbuchung', 'digital'],
['paper_sheet', 'Papierliste / Nacherfassung', 'paper'],
['admin_backoffice', 'Backoffice-Buchung', 'admin'],
['rfid_reader', 'RFID-Geraet', 'rfid'],
['digital_self', 'Selbst gebucht', 'digital'],
['paper_sheet', 'Papierliste / Nachtrag', 'paper'],
['admin_backoffice', 'Nachgetragen durch Verwaltung', 'admin'],
['rfid_reader', 'Kartenleser', 'rfid'],
] as [$code, $sourceName, $channel]) {
$sourceInsert->execute([
'tenant_id' => $tenantId,