From 4023a0939cb0df689a60de2175b33000d9cb768b Mon Sep 17 00:00:00 2001 From: nexxo Date: Mon, 3 Aug 2026 21:18:26 +0200 Subject: [PATCH] Der Altbestand des Registers wird geradegezogen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was der doppelte Zuhoerer geschrieben hat, steht noch da: zweiundzwanzig Zeilen fuer zehn Vorgaenge. Das Register beantwortet damit genau die eine Frage falsch, fuer die es gebaut wurde — hat der Kunde das bekommen, und wie oft. Die Wanderung liest den Fehler rueckwaerts. Der Zuhoerer verdoppelte jeden Versand exakt, also bleibt von jeder Gruppe gleicher Zeilen (Empfaenger, Betreff, Mailklasse, Zeitpunkt) die aeltere Haelfte stehen. Aus vier werden zwei — das waren zwei echte Anmeldungen in derselben Sekunde, beide verdoppelt —, aus zwei wird eine, Einzelnes bleibt unberuehrt. Ungerade Gruppen kann dieser Fehler nicht erzeugt haben; taucht doch eine auf, wird aufgerundet, damit im Zweifel eine Zeile zu viel stehen bleibt. down() ist leer, mit Begruendung: geloeschte Zeilen kommen nicht zurueck, und eine erfundene Zeile waere schlimmer als die Luecke. Auf dieser Installation gefahren: 22 Zeilen vorher, 11 danach. Co-Authored-By: Claude Opus 5 --- ...s_register_zaehlt_jeden_versand_einmal.php | 68 +++++++++++++++++++ tests/Feature/Admin/MailRegisterTest.php | 32 +++++++++ 2 files changed, 100 insertions(+) create mode 100644 database/migrations/2026_08_04_100000_das_register_zaehlt_jeden_versand_einmal.php diff --git a/database/migrations/2026_08_04_100000_das_register_zaehlt_jeden_versand_einmal.php b/database/migrations/2026_08_04_100000_das_register_zaehlt_jeden_versand_einmal.php new file mode 100644 index 0000000..24da987 --- /dev/null +++ b/database/migrations/2026_08_04_100000_das_register_zaehlt_jeden_versand_einmal.php @@ -0,0 +1,68 @@ +select('to', 'subject', 'mailable', 'sent_at') + ->selectRaw('COUNT(*) as anzahl') + ->groupBy('to', 'subject', 'mailable', 'sent_at') + ->having('anzahl', '>', 1) + ->get(); + + foreach ($gruppen as $gruppe) { + $ids = DB::table('sent_mails') + ->where('to', $gruppe->to) + ->where('subject', $gruppe->subject) + // Beide Spalten duerfen leer sein, und `= NULL` trifft nichts. + ->when($gruppe->mailable === null, + fn ($q) => $q->whereNull('mailable'), + fn ($q) => $q->where('mailable', $gruppe->mailable)) + ->when($gruppe->sent_at === null, + fn ($q) => $q->whereNull('sent_at'), + fn ($q) => $q->where('sent_at', $gruppe->sent_at)) + ->orderBy('id') + ->pluck('id'); + + $behalten = (int) ceil($ids->count() / 2); + $weg = $ids->slice($behalten)->all(); + + if ($weg !== []) { + DB::table('sent_mails')->whereIn('id', $weg)->delete(); + } + } + } + + public function down(): void + { + // Geloeschte Zeilen kommen nicht zurueck, und eine Zeile zu erfinden + // waere schlimmer als die Luecke: das Register darf nichts behaupten. + } +}; diff --git a/tests/Feature/Admin/MailRegisterTest.php b/tests/Feature/Admin/MailRegisterTest.php index bfbf1dd..20480df 100644 --- a/tests/Feature/Admin/MailRegisterTest.php +++ b/tests/Feature/Admin/MailRegisterTest.php @@ -52,6 +52,38 @@ it('writes exactly one row per recipient, however the listener is registered', f expect(SentMail::query()->where('to', $customer->email)->count())->toBe(1); }); +/** + * Die Wanderung, die den Altbestand geradezieht. + * + * Der doppelte Zuhörer hat jeden Versand exakt verdoppelt, also bleibt von + * jeder Gruppe gleicher Zeilen die ältere Hälfte stehen — aus vier werden zwei + * (das waren zwei echte Versande), aus zwei wird eine, und was einzeln steht, + * bleibt unberührt. + */ +it('halves the duplicated rows the doubled listener left behind', function () { + $zeile = fn (string $to, string $subject, string $zeit) => SentMail::create([ + 'to' => $to, 'subject' => $subject, 'mailable' => \App\Mail\OperatorMessageMail::class, + 'from_operator' => false, 'sent_at' => $zeit, + ]); + + // Zwei echte Versande, verdoppelt. + foreach (range(1, 4) as $i) { + $zeile('vier@example.com', 'Vier', '2026-07-30 12:00:00'); + } + // Ein echter Versand, verdoppelt. + foreach (range(1, 2) as $i) { + $zeile('zwei@example.com', 'Zwei', '2026-07-30 12:00:01'); + } + // Nach der Behebung geschrieben — steht schon richtig da. + $zeile('eins@example.com', 'Eins', '2026-07-30 12:00:02'); + + (require database_path('migrations/2026_08_04_100000_das_register_zaehlt_jeden_versand_einmal.php'))->up(); + + expect(SentMail::query()->where('to', 'vier@example.com')->count())->toBe(2) + ->and(SentMail::query()->where('to', 'zwei@example.com')->count())->toBe(1) + ->and(SentMail::query()->where('to', 'eins@example.com')->count())->toBe(1); +}); + it('records the send, not the intention', function () { // On MessageSent, not MessageSending: a row here has to mean the transport // accepted it. Recording intentions would fill the register with mails that