diff --git a/app/Livewire/Admin/MailPace.php b/app/Livewire/Admin/MailPace.php index 9e7db3d..7b12deb 100644 --- a/app/Livewire/Admin/MailPace.php +++ b/app/Livewire/Admin/MailPace.php @@ -4,6 +4,7 @@ namespace App\Livewire\Admin; use App\Services\Mail\MailLane; use App\Support\Settings; +use Illuminate\Support\Facades\Lang; use Illuminate\Support\Facades\Queue; use Livewire\Attributes\Layout; use Livewire\Component; @@ -162,7 +163,45 @@ class MailPace extends Component return view('livewire.admin.mail-pace', [ 'lanes' => $lanes, 'laneOptions' => [MailLane::DIRECT, MailLane::URGENT, MailLane::CALM], - 'assignments' => MailLane::all(), + 'assignments' => $this->assignmentRows(), ]); } + + /** + * Die Zuordnung mit lesbaren Namen statt Klassennamen. + * + * Die Seite zeigte `class_basename()` — „DormantAccountWarningMail" steht + * dort, wo der Betreiber „Konto ohne Paket wird gelöscht" sucht. Ein + * Klassenname ist eine Auskunft über den Bauplan, keine über die Mail. + * + * Die Namen liegen in der Sprachdatei und NICHT in MailCatalogue, obwohl + * der sehr ähnlich klingt: der Katalog zählt Mail*arten* (vier + * Mahnstufen, vier Einträge, vier Absenderwege), diese Seite verteilt + * Mail*klassen* auf Spuren — und alle vier Mahnstufen sind EINE Klasse, + * die als eine Zeile eine Spur bekommt. Zwei Listen, weil es zwei + * verschiedene Dinge sind; MailPaceLabelsTest hält fest, dass keine + * Klasse ohne Namen bleibt. + * + * Fehlt einer doch, steht der Klassenname da wie bisher — unschön, aber + * lesbar, statt einer leeren Zeile. + * + * @return array + */ + private function assignmentRows(): array + { + return collect(MailLane::all()) + ->map(function (string $lane, string $class) { + $kurz = class_basename($class); + $schluessel = 'mail_pace.class.'.$kurz; + + return [ + 'class' => $class, + 'lane' => $lane, + 'label' => Lang::has($schluessel) ? __($schluessel) : $kurz, + 'locked' => MailLane::isLocked($class), + ]; + }) + ->values() + ->all(); + } } diff --git a/lang/de/mail_pace.php b/lang/de/mail_pace.php index 1388034..bad489d 100644 --- a/lang/de/mail_pace.php +++ b/lang/de/mail_pace.php @@ -32,6 +32,27 @@ return [ 'assignments_title' => 'Zuordnung', 'assignments_sub' => 'Welche Mailklasse in welcher Spur fährt. Gesperrte Klassen fahren immer direkt — darauf wartet gerade jemand, drosseln würde dort nur schaden.', + 'class' => [ + // Die Namen, unter denen der Betreiber diese Mails kennt — nicht die + // Klassennamen, unter denen sie im Code stehen. Sie decken sich mit + // den Bezeichnungen in MailCatalogue, wo es dieselbe Sache ist; die + // Mahnungen sind eine Klasse fuer vier Mahnstufen und bekommen hier + // deshalb EINEN Namen fuer alle vier. + 'ResetPasswordMail' => 'Passwort zurücksetzen', + 'VerifyEmailMail' => 'E-Mail bestätigen (Registrierung)', + 'NewDeviceSignInMail' => 'Anmeldung von einem neuen Gerät', + 'SecurityBlockMail' => 'Adresse wegen Anmeldeversuchen gesperrt', + 'ContactRequestMail' => 'Kontaktanfrage von der Website', + 'OrderConfirmationMail' => 'Bestellbestätigung', + 'OperatorMessageMail' => 'Nachricht aus der Konsole', + 'MaintenanceAnnouncementMail' => 'Wartungsfenster angekündigt', + 'MaintenanceCancelledMail' => 'Wartungsfenster abgesagt', + 'CloudSuspendedMail' => 'Cloud abgeschaltet', + 'CloudResumedMail' => 'Cloud läuft wieder', + 'InvoiceMail' => 'Rechnung', + 'DunningNoticeMail' => 'Zahlungserinnerungen und Mahnungen', + 'DormantAccountWarningMail' => 'Konto ohne Paket wird gelöscht', + ], 'locked_hint' => 'Immer direkt (gesperrt)', 'locked_notice' => 'Diese Mail fährt immer direkt: darauf wartet gerade jemand.', 'moved' => 'Verschoben.', diff --git a/lang/en/mail_pace.php b/lang/en/mail_pace.php index 3d70756..d95dbbd 100644 --- a/lang/en/mail_pace.php +++ b/lang/en/mail_pace.php @@ -31,6 +31,25 @@ return [ 'assignments_title' => 'Assignment', 'assignments_sub' => 'Which mail class rides in which lane. Locked classes always ride direct — someone is waiting on them right now, and throttling would only hurt.', + 'class' => [ + // The names an operator knows these mails by — not the class names + // they carry in the code. The dunning mail is one class covering four + // levels, so it gets ONE name here for all four. + 'ResetPasswordMail' => 'Password reset', + 'VerifyEmailMail' => 'Confirm email address (sign-up)', + 'NewDeviceSignInMail' => 'Sign-in from a new device', + 'SecurityBlockMail' => 'Address blocked after failed sign-ins', + 'ContactRequestMail' => 'Contact request from the website', + 'OrderConfirmationMail' => 'Order confirmation', + 'OperatorMessageMail' => 'Message from the console', + 'MaintenanceAnnouncementMail' => 'Maintenance window announced', + 'MaintenanceCancelledMail' => 'Maintenance window cancelled', + 'CloudSuspendedMail' => 'Cloud suspended', + 'CloudResumedMail' => 'Cloud running again', + 'InvoiceMail' => 'Invoice', + 'DunningNoticeMail' => 'Payment reminders and dunning notices', + 'DormantAccountWarningMail' => 'Account without a plan will be deleted', + ], 'locked_hint' => 'Always direct (locked)', 'locked_notice' => 'This mail always rides direct: someone is waiting on it right now.', 'moved' => 'Moved.', diff --git a/resources/views/livewire/admin/mail-pace.blade.php b/resources/views/livewire/admin/mail-pace.blade.php index 17b208e..87367e1 100644 --- a/resources/views/livewire/admin/mail-pace.blade.php +++ b/resources/views/livewire/admin/mail-pace.blade.php @@ -30,10 +30,15 @@ @foreach ($lanes as $lane) - + {{-- Der Warteschlangenname steht als Unterzeile bei der Spur, + nicht in der Wertspalte: dort stand er zwischen + Beschriftung und Eingabefeldern und las sich wie ein + weiterer Wert. Er bleibt aber stehen — es ist der Name, + den der Arbeiter in docker-compose.yml abhört, und wer + dort nachsieht, braucht genau diese Zeichenkette. --}} +
- {{ $lane['key'] }} - @if ($lane['throttled'])
{{ __('mail_pace.assignments_sub') }}

- @foreach ($assignments as $class => $lane) - - @if (\App\Services\Mail\MailLane::isLocked($class)) + @foreach ($assignments as $eintrag) + + @if ($eintrag['locked']) {{ __('mail_pace.locked_hint') }} @else - @endif diff --git a/tests/Feature/Admin/MailPacePageTest.php b/tests/Feature/Admin/MailPacePageTest.php index 54e3abb..3474d24 100644 --- a/tests/Feature/Admin/MailPacePageTest.php +++ b/tests/Feature/Admin/MailPacePageTest.php @@ -5,6 +5,7 @@ use App\Mail\InvoiceMail; use App\Mail\ResetPasswordMail; use App\Services\Mail\MailLane; use App\Support\Settings; +use Illuminate\Support\Facades\Lang; use Livewire\Livewire; /** @@ -45,7 +46,7 @@ it('zeigt eine gesperrte Klasse als gesperrt statt mit einem Auswahlfeld', funct // wäre die Einladung zu einem Klick, der ohnehin nichts bewirkt. $html = Livewire::actingAs(operator('Owner'), 'operator') ->test(MailPace::class) - ->assertSee(class_basename(ResetPasswordMail::class)) + ->assertSee(__('mail_pace.class.ResetPasswordMail')) ->html(); expect($html)->not->toContain("move('".ResetPasswordMail::class."'"); @@ -105,3 +106,47 @@ it('speichert einen gueltigen Takt fuer beide gedrosselten Spuren', function () ->and(Settings::get('mail.pace.calm.count'))->toBe(7) ->and(Settings::get('mail.pace.calm.minutes'))->toBe(9); }); + +/** + * ── Namen statt Klassennamen ────────────────────────────────────────────── + * + * Die Seite zeigte `class_basename()`: „DormantAccountWarningMail" stand + * dort, wo der Betreiber „Konto ohne Paket wird gelöscht" sucht. Ein + * Klassenname ist eine Auskunft über den Bauplan, keine über die Mail. + */ +it('nennt jede Mailklasse bei ihrem Namen, nicht bei ihrer Klasse', function () { + $html = Livewire::actingAs(operator('Owner'), 'operator')->test(MailPace::class)->html(); + + foreach (array_keys(MailLane::all()) as $class) { + $kurz = class_basename($class); + + expect(Lang::has('mail_pace.class.'.$kurz)) + ->toBeTrue("Für {$kurz} fehlt ein Name in lang/de/mail_pace.php."); + + // Der Klassenname darf auch nicht mehr durchscheinen — er steht nur + // noch im wire:key und im Wert des Auswahlfelds, beides mit vollem + // Namensraum davor. + expect($html)->not->toContain('>'.$kurz.'<'); + } +}); + +it('führt für jede Mailklasse denselben Namen in beiden Sprachen', function () { + // Eine Sprache ohne den Eintrag zeigte still den Schlüssel selbst — + // „mail_pace.class.InvoiceMail" mitten in einer Tabelle. + $de = require lang_path('de/mail_pace.php'); + $en = require lang_path('en/mail_pace.php'); + + foreach (array_keys(MailLane::all()) as $class) { + expect($de['class'])->toHaveKey(class_basename($class)) + ->and($en['class'])->toHaveKey(class_basename($class)); + } +}); + +it('stellt den technischen Spurnamen unter die Spur, nicht zwischen die Felder', function () { + // Er bleibt sichtbar — der Arbeiter in docker-compose.yml hört genau + // diese Zeichenkette ab —, aber als Unterzeile der Beschriftung. + $html = Livewire::actingAs(operator('Owner'), 'operator')->test(MailPace::class)->html(); + + expect($html)->toContain(MailLane::CALM) + ->and($html)->not->toContain('font-mono text-xs text-muted">'.MailLane::CALM); +});