F1: partner_paid_at wird beim Rechnungskauf (paymentStatusPaidAction mit paid=false) nicht mehr gesetzt, sondern erst bei der Rechnungszahlung; der Backfill nimmt das Aktivierungsdatum nur noch ohne jede Registrierungsbestellung (offene Rechnung bleibt leer). F2: Snapshots speichern je Linie new_partner_ids, die Neupartner-Liste zeigt genau diese; aeltere Snapshots ohne IDs rechnen mit der damaligen Regel (Registrierungsmonat, scopeNewPartnerInByActiveDate), damit Kachel und Liste uebereinstimmen. Keine Payload-Versionsaenderung, kein Neuaufbau noetig. F3: Liefertag-/Termin-Aenderung waehrend einer Pause zieht paused_until mit; eine Ausfuehrung loescht auch den paused_by_*-Grund. F4: expiredCount der Firstlines-Seite (Controller, View, Sprachen) zum bereits committeten Test nachgezogen. F5: Backfills schreiben ohne updated_at (toBase()->update, timestamps=false). Kuendigungsbestaetigung abo_cancel_confirm wird im Selfservice serverseitig verlangt. AboStatusExplainer laedt die Historie je Abo und Aktion nur einmal. Stellungnahme und Deploy-Folgen im Audit-Dokument, Regressionstests je Befund. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
24 KiB
Audit der vier Commits vom 16.09.2026
Prüfdatum: 18.09.2026
Geprüfter Stand: d5ad1ba50b8b4442d6b6ccffebb0226f90c04894
Vergleichsbasis: 203999a (HEAD~4 zum Prüfzeitpunkt)
Umfang: Code, Ticketanforderungen, Migrationen/Backfills, Aufrufer, Views, Übersetzungen und automatisierte Tests.
Bewertung
Die Umsetzung hat eine gute Grundlage: Datumsberechnung und Sperrregeln sind zentralisiert, Monatsenden werden berücksichtigt, und die Statusanzeige verwendet einen gemeinsamen Explainer. Die neuen Tests decken zahlreiche reguläre Abläufe ab. Den Gesamtstand würde ich wegen des falsch gesetzten Zahlungsdatums und der inkonsistenten historischen Statistik noch nicht als abgeschlossen abnehmen.
Es gibt fünf konkrete Befunde: einen mit hoher, drei mit mittlerer und einen mit niedriger Priorität. Hinzu kommen gezielte Verbesserungen bei Bestätigung, Datenqualität, Performance und Testabdeckung. Die vorhandene Testsuite ist schon vor diesen Änderungen nicht vollständig grün; durch die vier Commits kommt genau ein weiterer fehlgeschlagener Test hinzu.
| Commit | Umsetzung | Einschätzung |
|---|---|---|
c0535be |
A4: sichtbarer Bereich für Pause/Kündigung, Folgen-Hinweis und Bestätigungs-Checkbox | UI sinnvoll ergänzt; Mindestlaufzeit und Sperre bleiben serverseitig abgesichert. Checkbox selbst wird nur im Browser geprüft. |
ec3ecad |
A3: Statusgrund, Pausenende, Zahlungsfehler und Backfill | Gemeinsame Darstellung ist sinnvoll; Pausenende kann nach Terminänderungen falsch sein, Backfill verändert entgegen seiner Beschreibung Zeitstempel. |
66c6ae0 |
A1/A2: Liefertage 1–31, Monatsende, Sperre am Ausführungstag | Datums- und Kalendertagslogik im geprüften Umfang schlüssig; Zusammenspiel mit der neuen Pauseninformation muss ergänzt werden. |
d5ad1ba |
Neupartner im Zahlungsmonat | Zentrale Definition sinnvoll, aber Rechnungskauf und bestehende Snapshots sind unvollständig berücksichtigt; ein Test wurde ohne die zugehörige Controller-Änderung committet. |
Prüfverfahren und Grenzen
Im Arbeitsverzeichnis lagen bereits umfangreiche uncommittete Änderungen. Deshalb wurden beide Git-Stände mit git archive in separate temporäre Verzeichnisse extrahiert und mit Kopien der vorhandenen PHP-Abhängigkeiten getestet. Die lokale Anwendungs-Konfiguration mit echten Zugangsdaten wurde nicht übernommen; verwendet wurden SQLite im Speicher und Testkonfiguration. Die bereits vorhandenen Änderungen wurden nicht bearbeitet.
Ticketgrundlage waren insbesondere Entwicklungsplan September, Abschnitte A1–A4 und Antworten R1/R2, sowie Neupartner im Zahlungsmonat. Fundstellen und Zeilennummern beziehen sich auf den geprüften Commitstand; uncommittete Änderungen können die Zeilennummern im Arbeitsverzeichnis verschieben.
Die Prüfung umfasst keine Browserinteraktion, keinen produktiven Datenabgleich und keinen MySQL-Parallelitätstest. Es wurden keine fachlichen Backfills oder Migrationen gegen die bestehende Anwendungsdatenbank ausgeführt. Im Repository wurde ausschließlich dieser Bericht neu angelegt; zusätzliche Reproduktionstests liegen nur in der temporären Prüfkopie.
Konkrete Befunde
F1 – Hohe Priorität: Unbezahlte Rechnung setzt bereits das erste Zahlungsdatum
Commit: d5ad1ba
Fundstellen: Payment.php, Zeilen 224–230; UserUtil.php, Zeilen 285–293. Relevanter Aufrufer: CheckoutController.php, directPaymentStatus(), im Commitstand Zeilen 760–773.
Payment::paymentStatusPaidAction() wird beim Rechnungskauf ausdrücklich mit $paid = false aufgerufen. Der neue Aufruf von markPartnerPaid() prüft dieses Argument nicht. Dadurch wird schon bei einer offenen Rechnung ein Zahlungszeitpunkt gespeichert. Bei der späteren tatsächlichen Zahlung bleibt dieser Wert wegen des gewollten „nur einmal setzen“-Guards bestehen.
Reproduziert: Konto-Produkt auf Rechnung am 31.08.2026 → Bestellung bleibt paid = false, aber partner_paid_at = 31.08.2026. Zahlung am 01.09.2026 → partner_paid_at bleibt August; isNewPartnerIn(9, 2026) liefert false.
Auswirkung: Die zentrale Ticketanforderung „Monat der Zahlung“ ist für diesen Pfad nicht erfüllt. Kachel, Recruiter-Auswertung und Neupartner-Liste können den falschen Monat verwenden. Auch nie bezahlte Rechnungskäufe erhalten bereits einen Zahlungszeitpunkt.
Empfehlung: Im Zahlungsservice das Feld nur bei bestätigter Zahlung setzen. Die ausdrücklich erlaubte manuelle Freischaltung im Admin bleibt ein eigener Fall. Zusätzlich im Backfill unterscheiden, ob ein Konto tatsächlich manuell freigeschaltet wurde oder lediglich eine offene Rechnung vorliegt: payment_account != null und fehlende bezahlte Registrierungsbestellung beweisen keine Admin-Freischaltung. Bereits falsch gesetzte Werte müssen bei einer Korrektur gezielt identifiziert werden; ein normaler erneuter Zahlungslauf repariert sie nicht.
Fehlender Regressionstest: Offene Rechnung Ende August, Zahlung Anfang September; vor Zahlung kein partner_paid_at, danach September. Außerdem offener Rechnungskauf ohne Zahlung im Backfill.
F2 – Mittlere Priorität: Historische Kachel und verlinkte Neupartner-Liste verwenden unterschiedliche Regeln
Commit: d5ad1ba
Fundstellen: User.php, scopeNewPartnerIn(), Zeilen 264–274; BackofficeDrilldownService.php, Zeilen 36–52 und 152–159.
Abgeschlossene Monate zeigen weiterhin die gespeicherten Snapshot-Zahlen nach der bisherigen Definition. Die Detailansicht übernimmt aus dem Snapshot nur die Teammitglieder und filtert diese anschließend mit dem geänderten newPartnerIn() nach partner_paid_at. Die eingefrorene Neupartner-Mitgliedschaft wird nicht übernommen.
Reproduziert: Ein August-Snapshot enthält einen Neupartner nach Registrierungsdatum. Nach Hinterlegung des tatsächlichen Zahlungsdatums im September zeigt die August-Kachel weiterhin 1, die verlinkte August-Liste aber 0 Zeilen. Der CSV-Export verwendet ebenfalls diese Detaildaten.
Die Deploy-Dokumentation beschreibt bereits, dass alte Snapshots bewusst nicht automatisch neu aufgebaut werden. Das erklärt die unveränderte Kachel, löst aber nicht die neu entstehende Abweichung innerhalb derselben Ansicht. Auch die dokumentierten wiederhergestellten Partner mit deutlich früherer Erstzahlung können hiervon betroffen sein; die produktive Anzahl wurde im Audit nicht ermittelt.
Empfehlung: Snapshot und Drilldown müssen dieselbe Definitionsversion verwenden. Für künftige Snapshots auch die Neupartner-IDs speichern. Für bestehende Snapshots eine explizite Strategie vorsehen: alte Definition konsistent weiter anzeigen oder gezielt abgestimmt migrieren. Ein pauschales --force ist keine unbedenkliche Reparatur, weil dadurch laut Projektdokumentation auch historische Teamzuordnungen verändert werden können.
Fehlender Regressionstest: Snapshot vor Definitionswechsel erzeugen, Zahlungsdatum abweichend nachtragen, danach Kachel, Liste und CSV auf gleiche Mitgliedschaft prüfen.
F3 – Mittlere Priorität: Pausenende bleibt nach Änderung des Liefertermins auf dem alten Datum
Commits: Zusammenspiel von ec3ecad und 66c6ae0
Fundstellen: AboRepository.php, Zeilen 99–105 und 243–249; UserAbo.php, Zeilen 135–149 und 171–175.
Eine Pause setzt paused_until auf den neuen Liefertermin. Eine nachfolgende zulässige Änderung des Liefertags schreibt jedoch nur abo_interval und next_date. Der Explainer und der Admin-Filter vertrauen weiterhin auf das alte paused_until.
Reproduziert: Nächste Lieferung 05.10.2026; Pause verschiebt sie auf 05.11.2026. Danach wird der Liefertag auf den 20. geändert. Ergebnis: next_date = 20.11.2026, aber paused_until = 05.11.2026.
Auswirkung: Kunden und Mitarbeitende erhalten einen falschen Wiederaufnahmetermin. Ab dem 06.11. verschwindet das Abo aus dem Pausenfilter, obwohl die verschobene Lieferung erst am 20.11. ansteht. Bei einer Vorverlegung kann die Anzeige umgekehrt eine längere Pause versprechen als tatsächlich vorgesehen.
Empfehlung: Bei Terminänderungen während einer laufenden Pause das Pausenende konsistent mitführen oder die laufende Pause aus einer eindeutigen Quelle ableiten. Die Regel für Selfservice und Admin zentral umsetzen. Beim Abschluss einer Pause auch status_reason bereinigen: Der aktuelle Hook löscht bei geändertem last_date zwar paused_until, aber einen bestehenden paused_by_*-Grund bei unverändertem Status 2 nicht.
Fehlender Regressionstest: Pause → Liefertermin nach hinten/vorn ändern → Erklärung und Admin-Filter prüfen → erfolgreiche Ausführung → Pausenfelder und Pausengrund entfernt.
F4 – Mittlere Priorität: Neuer Test erwartet eine nicht mitcommittete Controller-Erweiterung
Commit: d5ad1ba
Fundstellen: TeamFirstlinesPageTest.php, Zeilen 69–72; TeamController.php, Rückgabedaten in firstlines(), im Commitstand ab Zeile 752.
Der Test erwartet neu expiredCount. Der Controller liefert diesen Schlüssel im Git-Stand nicht. Das Ergebnis ist reproduzierbar Undefined array key "expiredCount".
Besonders relevant: Die passende Controller-Erweiterung existiert im aktuellen Arbeitsverzeichnis bereits als uncommittete Änderung. Damit kann ein Test im gemischten Arbeitsverzeichnis einen vollständigeren Stand prüfen, als später aus den vier Commits ausgeliefert wird.
Empfehlung: Die fachlich zusammengehörende Controller-/View-/Test-Änderung vollständig und fokussiert versionieren oder die sachfremde Test-Erweiterung aus diesem Paket herauslösen. Die Abnahme anschließend gegen einen sauberen Checkout durchführen.
Nachweis: Dies ist der einzige zusätzliche fehlgeschlagene Test im vollständigen Vergleich mit 203999a.
F5 – Niedrige Priorität: Backfills verändern entgegen der Dokumentation updated_at
Commits: ec3ecad, zusätzlich entsprechendes Verhalten in d5ad1ba
Fundstellen: AboBackfillStatusReasons.php, Zeilen 42–43 und 65–69; UserUtil.php, Zeilen 330–331.
Der Abo-Backfill behauptet, ohne Änderung von updated_at zu schreiben. Tatsächlich verwendet er Eloquent-Builder-Updates; diese ergänzen den Aktualisierungszeitstempel. Auch saveQuietly() im Partner-Backfill unterdrückt Events, aber keine Zeitstempel.
Reproduziert: Ein bestehendes Abo mit updated_at = 01.08.2026 12:00:00 erhält beim Status-Backfill den Testzeitpunkt 16.09.2026 10:00:00.
Auswirkung: Die ursprüngliche Änderungszeit geht für betroffene Datensätze verloren. Sortierungen oder spätere Analysen anhand dieses Feldes sehen eine Massenänderung zum Deploy. Eine darüber hinausgehende konkrete Störung wurde nicht nachgewiesen.
Empfehlung: Bewusst ohne automatische Zeitstempel schreiben, beispielsweise mit geeignetem Query-Builder-Update oder withoutTimestamps(). Den Erhalt der Zeitstempel testen und die Deploy-Dokumentation mit der tatsächlichen Implementierung abgleichen.
Weitere Verbesserungen und Optimierungen
Kündigungsbestätigung auch serverseitig prüfen
Das neue Formular sendet abo_cancel_confirm, aber kein PHP-Pfad wertet das Feld aus. Ein Request mit abo_cancel=true ohne Bestätigung kündigt weiterhin erfolgreich; dies wurde im Repository-Test reproduziert. Mindestlaufzeit und Sperrfrist werden dabei weiterhin geprüft – es handelt sich also nicht um deren Umgehung.
Wenn die Checkbox eine verbindliche Bestätigung der angezeigten Folgen sein soll, sollte der Selfservice-Pfad eine accepted-Validierung erhalten. Dazu Tests für fehlende, falsche und gültige Bestätigung ergänzen. Der normale Browserablauf wird aktuell lediglich durch den deaktivierten Button gesteuert.
Zusätzliche Einzelabfragen im Status-Explainer reduzieren
AboStatusExplainer.php, latestHistory() ab Zeile 199, fragt die Historie bei jedem Aufruf erneut ab. Ein deaktiviertes Abo benötigt mindestens eine zusätzliche Abfrage für das Datum; ohne gespeicherten Akteur entstehen zwei Abfragen derselben Historie für Akteur und Datum. Das neue Partial wird auch pro Tabellenzeile gerendert.
Empfehlung: Die jeweils benötigten letzten Historieneinträge gesammelt laden, geladene Beziehungen tatsächlich verwenden und den Eintrag innerhalb einer Erklärung wiederverwenden. Ein Query-Count-Test mit beispielsweise 50 Abos ist hier aussagekräftiger als weitere reine Textvergleiche. Der Admin lädt die Abo-Bestellungen bereits vor; diese vorhandene Optimierung ist positiv.
Herkunft und Genauigkeit rückgefüllter Zahlungsdaten sichtbar halten
firstRegistrationPaidAt() verwendet ohne Punktebuchung shopping_orders.updated_at, ohne passende Bestellung active_date. Beide Ersatzwerte können fachlich vom ersten Zahlungseingang abweichen: updated_at kann durch spätere Bestellbearbeitung verschoben werden, active_date wird unter anderem bei Wiederherstellung neu gesetzt. Diese Fallbacks sind dokumentiert, erscheinen in der Oberfläche aber uneingeschränkt als „Bezahlt am“.
Empfehlung: Eine belastbare Zahlungstransaktion bevorzugen und Ersatzwerte mit Herkunft beziehungsweise Unsicherheit kennzeichnen. Ein Dry-Run mit Zählung je Datenquelle verbessert die Kontrolle vor der Migration. Zudem sollte „erste Zahlung“ bei mehreren bezahlten Registrierungsbestellungen nach Zahlungszeitpunkt bestimmt werden; die kleinste Bestell-ID allein garantiert diese Reihenfolge nicht. Das tatsächliche Ausmaß dieser Randfälle wurde nicht gegen Bestandsdaten geprüft.
Backfill von Schemaänderung entkoppeln und Datenzugriffe bündeln
Die Migration ruft einen veränderlichen Anwendungsservice auf, der pro Partner eine Bestellung, gegebenenfalls eine Punktebuchung und ein Update ausführt. Chunking begrenzt den Speicher, aber nicht die Zahl dieser Einzelabfragen. Ein eigenständiges, wiederaufnehmbares Backfill-Kommando mit Dry-Run, Statistik je Quelle und gebündelten Datenzugriffen wäre besser kontrollierbar. Den erstmaligen Schreibvorgang von partner_paid_at bei parallelen Zahlungsprozessen außerdem atomar absichern; der aktuelle Null-Check schützt nur die einzelne geladene Modelinstanz. Ein tatsächlicher Parallelitätsfehler wurde hier nicht reproduziert.
Änderungen und Tests stärker nach Fachthema trennen
Der Statistik-Commit enthält zusätzlich Änderungen am Löschpräfix von E-Mail-Adressen (buildDeletedEmail() / stripDeletePrefix()). Dazu passende Tests liegen im Arbeitsverzeichnis untracked, sind jedoch nicht Teil des Commits. Diese Änderungen sollten mit eigener Beschreibung und Regressionstests gemeinsam versioniert werden. Dasselbe gilt für die Firstline-Erweiterung aus F4.
Für A1/A2 zusätzlich einen Integrationstest des tatsächlichen Ausführungscommands am 30. eines Monats mit Liefertag 31 ergänzen. Die neuen Tests belegen die Hilfsfunktion, aber nicht allein den gesamten Ablauf Auswahl → Bestellung → nächster Termin. Für A4 fehlt eine Browserprüfung der Checkbox, des Modal-Resets und des Buttons „Lieber pausieren“.
Validierung
Ergebnisse
| Prüfung | Ergebnis |
|---|---|
| PHP-Syntax aller 47 geänderten PHP-Dateien ohne Blade-Templates | Alle erfolgreich |
| Gezielte Tests zu Abo-Tickets, Zahlungsdatum, Prognose und Statistik | 163 Tests: 161 bestanden, 2 fehlgeschlagen, 850 Assertions |
Vollständige Suite auf 203999a |
1.055 bestanden, 96 fehlgeschlagen, 1 übersprungen, 4.019 Assertions |
Vollständige Suite auf d5ad1ba |
1.095 bestanden, 97 fehlgeschlagen, 1 übersprungen, 4.248 Assertions |
| Vergleich der vollständigen JUnit-Ergebnisse | Genau ein neuer Fehlschlag, keine zuvor fehlgeschlagene Prüfung behoben |
| Zusätzliche temporäre Reproduktionstests | 5 bestanden, 15 Assertions; bestätigen bewusst die fehlerhaften Ist-Zustände |
Die zwei Fehlschläge der gezielten Auswahl sind HomeMonthlyStatsTest (payline_is_career_fallback, bereits in der Basis fehlgeschlagen) und TeamFirstlinesPageTest (expiredCount, neu durch d5ad1ba). Die 96 gemeinsamen Fehlschläge wurden nicht als durch diese Tickets verursacht bewertet. Die Zahlen gelten für die identische isolierte Testumgebung; sie sind keine Aussage über die Laufzeitfehlerzahl der Produktion. Zusätzlich meldet PHPUnit das bereits vorhandene veraltete XML-Konfigurationsschema.
Die fünf temporären Reproduktionen prüfen: unbezahlte Rechnung und spätere Zahlung, historische Snapshot-/Listenabweichung, veraltetes Pausenende nach Terminänderung, verändertes updated_at beim Status-Backfill und Kündigung ohne Checkbox-Bestätigung.
Wiederholbare Testbefehle
In einem sauberen Checkout mit gültigem reinem Test-APP_KEY, Test-Zahlungskonfiguration und den vorhandenen Dependencies:
php artisan test --compact --filter='AboDeliveryDayTest|AboLockDaysSettingTest|AboPauseCancelSectionTest|AboPauseTest|AboRepositoryDateChangeTest|AboStatusExplainTest|PartnerPaidAtTest|PaylineForecastServiceTest|TeamFirstlinesPageTest|TopResultsServiceTest|BackofficeStatisticsDrilldownTest|BackofficeStatisticsListsTest|BackofficeSnapshotFreezeTest|HomeMonthlyStatsTest|StatisticsConsistencyTest|BackofficeDashboardServiceTest'
php artisan test --compact --log-junit /tmp/audit-results.xml
Beim Audit wurden für den Artisan-Boot zusätzlich APP_ENV=testing, DB_CONNECTION=sqlite, DB_DATABASE=:memory: und der in phpunit.xml hinterlegte Testwert für PAYONE_KEY_HASH gesetzt. Die vollständige HEAD-Prüfung schloss die separat markierte temporäre Testgruppe audit aus. Es wurden die installierten Dependencies verwendet, keine Paketaktualisierungen vorgenommen.
Empfohlene Reihenfolge
- F1 korrigieren und den Rechnungskauf über einen Monatswechsel absichern.
- F2 mit einer konsistenten Strategie für bestehende Snapshots lösen; keine ungeprüfte historische Neuberechnung.
- F3 korrigieren und die Kombination Pause/Terminänderung/Ausführung testen.
- F4 durch einen vollständigen, fokussierten Commit beheben und den sauberen Checkout erneut testen.
- F5 vor dem Backfill bereinigen; anschließend Bestätigungsprüfung und Performance-Verbesserungen umsetzen.
Die bereits dokumentierten Deploy-Schritte bleiben relevant: Schema-Migrationen, Abo-Status-Backfill zunächst als Dry-Run, beide Sperrfrist-Settings ausdrücklich auf 0 und Blade-Views leeren. Ob diese Schritte auf einer Zielumgebung bereits erfolgt sind, war nicht Gegenstand dieses Audits.
Stellungnahme und Umsetzung (18.09.2026)
Jeder Befund wurde gegen den Code geprüft. Ergebnis: alle fünf Befunde und die Kündigungsbestätigung sind
zutreffend und wurden umgesetzt (Commit „Audit 18.09.“ auf payment/phase-r-payone-refactoring). Die
Zahlen der Validierung wurden nicht nachgerechnet; der eine neue Fehlschlag (F4) ist reproduzierbar und
behoben.
| Befund | Bewertung | Umsetzung |
|---|---|---|
| F1 Rechnungskauf setzt Zahlungsdatum | zutreffend — paymentStatusPaidAction($order, false) beim Rechnungskauf, markPartnerPaid() prüfte $paid nicht |
markPartnerPaid() nur noch bei $paid = true; die spätere Rechnungszahlung (ManualPaymentService, $paid = true) setzt den Wert. Backfill: Aktivierungsdatum nur noch, wenn gar keine Registrierungsbestellung existiert — eine offene Rechnung bleibt leer. Tests: offene Rechnung Ende August, Zahlung Anfang September → September; Backfill mit offener Rechnung → leer. |
| F2 Kachel und Liste mit unterschiedlichen Regeln | zutreffend — Snapshot zählte nach damaliger Regel, Drilldown filterte die eingefrorenen Mitglieder mit der neuen Regel | Neue Snapshots speichern je Linie new_partner_ids; die Liste nimmt exakt diese. Alte Snapshots ohne IDs: Liste rechnet mit der damaligen Regel (scopeNewPartnerInByActiveDate, Registrierungsmonat) über die eingefrorenen Mitglieder — Kachel und Liste stimmen wieder überein, ohne historische Neuberechnung. Kein Bump der Payload-Version (alte Snapshots bleiben gültig). Tests: nachgetragenes Zahlungsdatum, alter Snapshot ohne IDs. |
| F3 Pausenende bleibt bei Terminänderung stehen | zutreffend | Liefertag-/Termin-Änderung (Selfservice und Admin) zieht paused_until auf den neuen Termin, solange eine Pause läuft. Ausführung (last_date ändert sich) löscht jetzt auch den paused_by_*-Grund. Test: Pause → 5.11. auf 20.11. → Admin auf 10.12. → Ausführung → Felder leer. |
| F4 Test ohne Controller-Änderung committet | zutreffend — expiredCount lag nur im Arbeitsverzeichnis |
Controller-Hunk, View und die vier team.php-Keys zusammen versioniert. |
F5 Backfills schreiben updated_at |
zutreffend — Eloquent-Builder-update() ergänzt den Zeitstempel, saveQuietly() ebenfalls |
Abo-Backfill über toBase()->update(), Partner-Backfill mit timestamps = false. Tests prüfen den erhaltenen Zeitstempel. |
| Kündigungsbestätigung nur im Browser | zutreffend | abo_cancel_confirm wird im Selfservice serverseitig verlangt (nach Mindestlaufzeit und Sperre), sonst abo.cancel_confirm_required. Admin unverändert. Tests: fehlend, 0, 1. |
| Explainer-Einzelabfragen | zutreffend | Historie je Abo und Aktion nur einmal geladen, vorgeladene Relation wird genutzt. Kein Query-Count-Test; Listen laden status_histories weiterhin nicht vor (offen, siehe unten). |
Nicht umgesetzt, bewusst:
- Herkunft der rückgefüllten Zahlungsdaten kennzeichnen und Backfill von der Migration entkoppeln: Die
Migration
2026_09_16_090000mit Backfill ist Teil vond5ad1ba; ob sie auf Produktion schon gelaufen ist, war hier nicht feststellbar. Eine Kennzeichnung der Quelle bräuchte eine weitere Spalte — Aufwand ohne fachliche Anfrage. Für die Erstzahlung bei mehreren bezahlten Registrierungsbestellungen gilt weiter die kleinste Bestell-ID. - Atomares Erstsetzen von
partner_paid_at: Der Paid-Pfad läuft in beiden Webhooks bereits in einer Transaktion mitlockForUpdateauf der Bestellung (C2); ein zweiter Zahlungsprozess desselben Users im selben Moment ist nicht reproduziert worden. - Integrationstest des Ausführungscommands am 30. mit Liefertag 31 und Browserprüfung A4: offen.
- Löschpräfix-Änderung (
buildDeletedEmail) im Statistik-Commit: gehört zum DSGVO-Thema, das noch uncommittet im Arbeitsbaum liegt; wird mit diesem Thema versioniert.
Deploy-Folgen:
-
Keine neue Migration.
php artisan view:clear. -
Falls
d5ad1babereits auf Produktion lief: Rechnungskäufe seit diesem Deploy haben ein falschespartner_paid_at(Datum des Rechnungskaufs statt der Zahlung). Prüfen und leeren:-- Kandidaten: Rechnungskauf offen, keine bezahlte Registrierungsbestellung SELECT u.id, u.email, u.partner_paid_at FROM users u WHERE u.partner_paid_at IS NOT NULL AND EXISTS (SELECT 1 FROM shopping_orders o WHERE o.auth_user_id = u.id AND o.payment_for = 1 AND o.paid = 0) AND NOT EXISTS (SELECT 1 FROM shopping_orders o WHERE o.auth_user_id = u.id AND o.payment_for = 1 AND o.paid = 1); -- danach fuer diese IDs: UPDATE users SET partner_paid_at = NULL WHERE id IN (...);Der Wert wird bei der Rechnungszahlung neu gesetzt. Die Backfill-Logik nimmt für diese Fälle nicht mehr das Aktivierungsdatum.
-
Snapshots müssen nicht neu gebaut werden; ab dem nächsten Monatsabschluss enthalten sie die Neupartner-IDs. Ein
--force-Neuaufbau bleibt eine gesonderte Entscheidung (sieheneupartner-zahlungsmonat.md).