- 50 EUR je Neupartner bleiben; die Top 3 (500/300/200 EUR) ermittelt
jetzt der Umsatz der Neupartner (wie Montenegro, ohne Einmalpunkte/Abos)
- Gleichstand bei Umsatzwertung: vorn liegt, wessen letzte gezaehlte
Umsatzbuchung frueher kam
- Challenge-Seite: Praemie und Top-3-Darstellung auch bei Umsatzwertung,
Ranglisten-Hinweis auf Umsatz (DE/EN/ES/FR)
- Seeder: neue Texte und Bedingungen (Rangliste, Steuerpflicht,
vergünstigter Einstieg bis 31.10.), gleicht Wertung beim Einspielen ab
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Beim Umstieg auf Git-Deploy mit dem Live-Server abgeglichen:
- resources/lang/de/website.php (Text Online-Streitbeilegung) und
resources/lang/es.json (DIV): im Admin auf Live geaendert
- web/templates partner, ueber-uns, erreichbarkeit: lagen nur auf Live,
ueber SiteController erreichbar, /partner im Footer verlinkt
- Migrationen create_dhl_shipments, create_user_invoice_credits,
create_users_sales_volumes: auf Live ausgefuehrt, fehlten im Repository
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kunden loeschen (Rueckmeldung "es wird nichts geloescht"):
- DSGVO-geloeschte Eintraege blieben mit Name in den Listen stehen; Kunde
61009 wurde deshalb achtmal geloescht, bei Eintrag 45013 wurde die E-Mail
danach ueber ein offenes Bearbeiten-Formular zurueckgeschrieben
- neue Spalte shopping_users.gdpr_deleted_at (Bestand per Migration markiert)
- Admin-Kundenliste blendet geloeschte Kunden aus (Filter: ausblenden /
mit anzeigen / nur geloeschte), Berater-Kundenliste immer
- Bearbeiten/Speichern im Admin und beim Berater gesperrt; ShoppingUser
verweigert das Zurueckschreiben von E-Mail und Telefon
- Modal und Erfolgsmeldung (de/en/es/fr) sagen klar, dass Name und
Rechnungsadresse wegen der Aufbewahrungspflicht bleiben
- gdpr:repair-customers [--execute] zieht protokollierte Loeschungen nach
- DSGVO-Loeschung prueft Superadmin serverseitig; Berater duerfen eine
Kunden-E-Mail nur beim eigenen Kunden nachtragen
- setFakerMail setzt faker_mail bei geloeschten Eintraegen nicht zurueck
Checkout: "Wie bist du auf uns aufmerksam geworden?" ist kein Pflichtfeld
mehr und wird farbig hervorgehoben.
Deploy: php artisan migrate && php artisan gdpr:repair-customers --execute
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kundenentscheid 25.09.2026 zu den Regeln 4 bis 7:
- Firmenkonto (mivita.company_user_id) steht in keiner Rangliste und
bekommt keine Praemie; getrackt wird weiter, damit ein ueber den
Firmenlink registrierter Partner nach dem Umhaengen mit umzieht
- Bedingungen Punkt 4 (de/en/es/fr): auch Kunden, die Berater werden,
zaehlen; massgeblich ist der aktuell hinterlegte Sponsor
- Test: Anmeldung ueber den Firmenlink, Umhaengen, Wertung beim neuen
Sponsor
- Doku: Regeln 4 bis 7 entschieden
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Kundenentscheid 25.09.2026:
- Storno: Ist jede bezahlte Starterpaket-Bestellung eines Neupartners
storniert (txaction = cancelled), bekommt das Tracking cancelled_at,
eine Storno-Logzeile nimmt die Einmalpunkte zurueck. Nachtlauf und
Neuaufbau markieren; "Meine Berechnung" zeigt den Partner durchgestrichen
mit "Storniert am ..., zaehlt nicht".
- Gezaehlt wird fuer alle, einen Rang bekommen nur bestaetigte Teilnehmer;
Export und Summen nur fuer sie. Unbestaetigte sehen "Deine Praemie nach
Bestaetigung", im Admin "Teilnahme nicht bestaetigt, keine Auszahlung".
- Datenschutz: andere Berater sehen Unbestaetigte in der Rangliste nicht
mehr (auch nicht anonym), VIP-Konten schon, der eigene Eintrag bleibt.
- Teilnahmebedingungen Punkt 2 und 7 und Hinweistexte in de/en/es/fr.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Qualifikationsbeginn 09.10.2026 statt 01.10. (Kundenentscheid 25.09.),
Beschreibung und Teilnahmebedingungen nennen den 9. Oktober
- Name, Untertitel, Beschreibung und Bedingungen als trans_* auf
Englisch, Spanisch und Franzoesisch
- Seeder gleicht bei bestehender Challenge Texte, Uebersetzungen und den
Beginn ab; alle anderen Admin-Werte bleiben
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Die Recruiting Challenge erfasste Eigen- und Kundenabos mit 0 Punkten
(Live-Kopie: 175 Eigenabos). Sie tauchten in Details und Admin auf und
brachten Berater ohne Neupartner in die Rangliste.
- incentives.include_abos (Standard an); Migration schaltet Incentives
ohne jede Abo-Wertung aus (0 Abo-Punkte, 0 Mindestabos, ohne Umsatzpunkte)
- aus: kein Abo-Tracking in Tracker, Neuaufbau und Reparatur;
incentive:calculate raeumt vorhandene Abo-Zeilen ab
- Admin: Checkbox im Formular, Abo-Punkte/Mindestabos werden dann auf 0
gesetzt; Abo-Spalte und -Bereiche in Admin, Details und Teilnahme-Hinweis
entfallen
- Seeder der Recruiting Challenge setzt den Schalter aus
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Entwicklungsplan September, B2 (Protokoll 08.09., Rueckmeldung 14.09., R3):
- Ablauftag + 2 Kalendertage (Nachtlauf): Team, Kunden, Kundenabos und
Portal-Logins gehen umkehrbar an den naechsten aktiven Sponsor, jede
Umhaengung in consultant_suspension_transfers protokolliert. Kurzinfo
an den Sponsor ohne Listen, keine Mail an das Firmenkonto.
- Zahlung bis Tag 27: Berater aktiv, Team und Kundendaten zurueck (nur
was noch beim Sponsor liegt); Umsaetze der Zwischenzeit bleiben dort.
- Tag 28: was noch am Berater haengt geht endgueltig an den Sponsor,
Berater wird dessen Kunde (ohne Duplikat) und geloescht; Listen-Mail
mit Vorname, Nachname, Telefon, E-Mail an jeden haltenden Sponsor.
- Cron, Zahlungseingang und Admin-Ablaufdatum laufen ueber
ConsultantSuspensionService; user:cleanup --dry-run listet beide Stufen.
- Mail 35 und Dashboard nennen Pausentag und Frist; Fristen in
config/mivita.php (cleanup_suspend_days, cleanup_delete_days).
- Nebenbefunde: Kernel referenziert UserCleanUp, user:restore aus der
User-Bereinigung laeuft nur noch einmal.
- Testschema: shopping_users.auth_user_id nullable wie auf Produktion
(Migration aendert nur, wo die Spalte noch NOT NULL ist).
- Uebersicht fuer die Kundin: dev/_tickets/2026-09-23-mitgliedschaft-
ablauf-uebersicht.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Admin-Bereich "Newsletter-Listen" (Plan dev/_tickets/entwicklungsplan-
newsletter-listen.md): Berater und Kunden nach Filtern zusammenstellen,
als Liste speichern und wieder laden, Export als XLSX/CSV. Jeder Export
wird protokolliert (newsletter_list_exports).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- shopping_orders.shipped_at (+ Herkunft live/dhl/geschaetzt): gesetzt im
Model, sobald der Versandstatus auf versendet wechselt, egal an welcher
Stelle der Status gesetzt wird. sales:backfill-shipped-at traegt
Altbestellungen aus dem DHL-Label nach oder schaetzt.
- SalesVolumeService rechnet Absatzmengen je Produkt wahlweise nach
Versand (inkl. externer/API-Bestellungen, Stornos im Monat des
Stornobelegs) oder nach Rechnung. Sets werden fuer die zweite Liste in
Einzelartikel aufgeloest.
- Admin-Seite: Umschalter Versand/Rechnung, Datenqualitaet (Anteil
geschaetzter Versanddaten), Export Produkte und Einzelartikel.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Produkte mit 7,5 Punkten wurden an drei Stellen zu ganzen Zahlen:
user_abo_one_time_items (PHP-Cast int, 7,5 -> 7), homeparty_user_order_items
(MySQL rundete auf 8) und shopping_collect_orders (Summe der Shop-API).
Die Migration zieht die drei Spalten auf DECIMAL(10,2) nach (Nachtrag zu
2026_01_22), Cast und Abo-Snapshot rechnen mit float.
points:report-decimal-diff wertet die Differenzen je Berater und Monat aus
(nur lesend), points:repair-decimal korrigiert den laufenden Monat und
zieht die Monatssummen nach.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Titelbild liegt unter public/img/incentive/recruiting-challenge-2026-27/,
der Seeder traegt den Dateinamen ein. Die Dashboard-Karte verlinkt nur
noch auf die eine Incentive-Seite, der Ranglisten-Button entfaellt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Haken vip_only am Incentive: aktives Incentive erscheint dann nur fuer
VIP-Konten in Sidenav, Dashboard, Teaser, Rangliste und Details, andere
Berater bekommen 404; das Tracking laeuft fuer alle weiter. Seeder setzt den
Haken fuer die Abstimmung der Recruiting Challenge.
Ranglisten-Seite: solange die Teilnahme nicht bestaetigt ist, steht der
Teilnahme-Block mit Praemien-Chips, Checkbox, Button und aufklappbaren
Bedingungen ganz oben, darunter Trennlinie, Titel, Untertitel, Bild,
Beschreibung und die Info-Kacheln. Ueberschriften vergroessert, Abo-Bloecke
nur wenn Abos zaehlen.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Seeder legt die Challenge als Entwurf mit Beschreibung, Bedingungen und den
Werten 50 EUR je Neupartner sowie 500/300/200 EUR Bonus an; ein zweiter Lauf
aktualisiert nur die Texte. Vorlage ohne Gedankenstriche, Bonus entschieden,
offene Regelfragen mit den in den Bedingungen hinterlegten Vorschlaegen.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Neue Felder amount_per_partner, bonus_rank_1..3 und include_sales_points an
incentives. Mit ausgeschalteten Umsatzpunkten zaehlt die Wertung nur die
Einmalpunkte je Neupartner, die Rangliste entspricht damit der Anzahl
Neupartner mit Starterpaket. IncentivePayoutService rechnet Neupartner mal
Betrag plus Rang-Bonus fuer qualifizierte Gewinner; Admin-Detailseite bekommt
Praemienspalte, Summen und XLSX-Export fuer die Auszahlung.
Sidenav und Dashboard zeigen alle aktiven Incentives, damit Montenegro und
die Challenge parallel laufen koennen. Teaser, Rangliste und Details zeigen
bei Geldpraemie die Praemien-Kachel und "Deine Praemie bisher" und blenden
Abo-Bloecke aus, wenn Abos keine Rolle spielen. Montenegro bleibt unveraendert.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Wer Ende August registriert und am 1. September bezahlt hat, tauchte in keiner
Neupartner-Zaehlung auf: Der August-Snapshot wurde um 04:45 eingefroren, da
fehlte noch das Abrechnungskonto; im September lag active_date im falschen
Monat. Betroffen sind 2026 acht Partner, auch im Recruiting ihrer Sponsoren.
Kundenentscheidung 16.09.2026: der Zeitpunkt der Zahlung ist massgeblich.
Neue Spalte users.partner_paid_at, gesetzt bei der ersten Kontozahlung
(Payment::paymentStatusPaidAction) und bei manueller Freischaltung durch den
Admin — nur, solange sie leer ist; Verlaengerung und Wiederherstellung aendern
sie nicht. Bestehende Partner werden per Migration aus der ersten bezahlten
Registrierungsbestellung rueckgefuellt (Zeitstempel der Punktebuchung, ersatzweise
Bestelldatum, ohne Bestellung das Aktivierungsdatum). Bewusst nicht
payment_account minus ein Jahr: das ergaebe bei Verlaengerern den Jahrestag der
Erstzahlung.
User::scopeNewPartnerIn() und isNewPartnerIn() lesen jetzt partner_paid_at —
damit Kachel, Recruiter-Ranking, Top 3 und Neupartner-Liste gleichermassen.
Die Liste und der CSV-Export zeigen "Bezahlt am" neben "Registriert am".
Unveraendert: die Teamstruktur haengt weiter an active_date (auch im
Provisionsbaum). Wer ist neu dazugekommen und wer gehoert zur Linie sind zwei
verschiedene Fragen.
Die eingefrorenen Monate Januar bis August enthalten sieben dieser Partner
nicht; ob sie per --force neu gebaut werden, ist eine Entscheidung — siehe
dev/2026-07-22/neupartner-zahlungsmonat.md.
Mit drin: die handgebaute SQLite-Tabelle in BackofficeDashboardServiceTest
kannte status_reason aus d6efe56 noch nicht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Neue Spalten user_abos.status_reason / paused_until und
user_abo_orders.error_message. Der Grund wird zentral am Model gepflegt
(saving-Event): Zahlungsfehler, Kuendigung/Deaktivierung mit Akteur aus dem
Formular-Kanal, Loeschen beim Zurueckkehren in den Lauf; die Pause setzt
paused_until und paused_by_*. Cron- und Retry-Zahlung schreiben den Fehlertext
des Anbieters an die Abo-Bestellung.
AboStatusExplainer uebersetzt das in einen Satz ("Pausiert bis ... vom Kunden
am ...", "Zahlung am ... fehlgeschlagen - Grund: ...", "Gekuendigt am ... vom
Berater"), Partial _status_explain in Abo-Detail (CRM, Portal, Admin),
Abo-Uebersicht, Kundenabo-Liste, Team-Kundenabos und Admin-Datatable; dort
neuer Filter "Zustand" (pausiert / Zahlung fehlgeschlagen). Backfill-Kommando
abo:backfill-status-reasons fuer Bestandsdaten, Fallback ueber das
Statusprotokoll und die Pausenfelder.
Sprachdateien de/en/es/fr, Tests in AboStatusExplainTest, Plan-Status.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
K11 aus dem September-Protokoll (Tickets C1 und C2 im Entwicklungsplan), live seit 14.09.2026.
C1 – Statistik rechnet wie die Provision
- Standard-Baum ist der Provisionsbaum (Mitglieder wie TreeCalcBot: Mitgliedsebene,
Abrechnungskonto, active_date; ausgefilterte Knoten kappen ihren Unterbaum, max. 21 Linien).
Gesamte Downline bleibt per Umschalter erreichbar, Wahl in der Session.
- Punkte-Definition: Provisionspunkte (TP + Shop), reine Karrierepunkte ausgewiesen statt
eingerechnet, negative Monatssalden je User wie in der Provision auf 0 gekappt.
- Abgeschlossene Monate nehmen die Mitglieder je Linie aus dem gespeicherten Provisionslauf
(user_businesses), Snapshots je Baum-Definition (Payload-Version 3, Migration).
- Struktur-Modal: Gesamt aus den Linien-Punkten mit Dezimalen statt der int-Spalte total_pp.
- Messlauf statistics:compare-trees (Downline / Provisionsbaum / Struktur je VIP, Laufstand-Drift).
C2 – Laufstand-Race (Provision zu niedrig bei zeitgleichen Buchungen)
- Neuberechnung liest die Zeilen des Beraters sperrend (FOR UPDATE), Index (user_id, year, month).
- Paid-Pfad der Payone- und Stripe-Webhooks als DB::transaction mit 3 Anlaeufen; Deadlock und
Lock-Timeout aus der Rechnungserstellung werden nicht mehr geschluckt.
- Sicherheitsnetz sales:repair-running-totals taeglich 02:45 vor dem Provisionslauf;
abgeschlossene Monate werden nicht angefasst (Entscheid 14.09.: keine Nachzahlung fuer 06/08).
Doku: Entwicklungsplan zum Protokoll, Deploy-Notiz 14.09. (inkl. Migrations-Stolperstein:
Migrationen tragen Werte als Literale, nicht als Konstanten aus dem Anwendungscode).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ein Storno einer Shop-Bestellung wurde als Eigenpunkt gebucht und liess damit
die Shop-Provisionsbasis ungemindert. Betroffen sind 7 Faelle aus April bis
August 2026 (178,82 EUR Differenz in der Provisionsbasis, gegen user_businesses
geprueft).
Ursache: `status_turnover` beschreibt nur, wohin der UMSATZ gehoert. Die Punkte
folgen einer anderen Regel — eine Gutschrift (Status 4) bucht ihre Punkte immer
auf KP/TP, auch wenn ihr Umsatz dem Shop zugeordnet ist. Die Gegenbuchung kann
diese Aufteilung ohne die Ursprungsart nicht spiegeln, deshalb haelt
`storno_origin_status` sie jetzt auf der Stornozeile fest. NULL heisst
"Altzeile ohne Angabe" und faellt auf das bisherige Verhalten zurueck —
abgeschlossene Monate aendern sich dadurch nicht.
Stornoregel (Kundenentscheidung): Ein Storno mindert nur den Monat der
Ursprungsbestellung. Ist der Monat abgeschlossen, passiert nichts mehr, denn
Abrechnung und Gutschriften stehen. Ein Minus in den neuen Monat mitzunehmen
waere nicht nachvollziehbar. Fuer den Ausnahmefall gibt es den Settings-Schalter
`storno-points-current-month`, standardmaessig aus.
Ausserdem: Die persoenliche Monatsstatistik summierte Status 0 (nicht
zugeordnet) und 3 (pending) mit, die gemeinsame Punkteauswertung nicht. Beide
nutzen jetzt dieselbe Statusliste, und ein Storno mindert das Segment seiner
Ursprungsbuchung statt pauschal in "Sonstige" zu laufen.
Die Migration fuer `status_turnover` zieht nur das Schema auf den tatsaechlichen
Stand nach (nullable statt NOT NULL) — auf Produktion faktisch wirkungslos,
aber Neuinstallationen und die Testdatenbank haben damit dieselbe Semantik.
Audit-Befunde A02, A06, A15, R01-R03.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rueckmeldung der Buchhaltung und des Stripe-Calls (dev/2026-08-26-stripe/):
Stripe kannte bisher nur die E-Mail des Kunden, Zahlungen waren im Dashboard
weder ueber die Bestell- noch ueber die Rechnungsnummer auffindbar, und ein
Zahlungseingang mit falschem Verwendungszweck zwang die Buchhaltung dazu, die
Rechnung von Hand zu schreiben.
- SE1: Der Checkout haengt einen Stripe-Customer mit Name, E-Mail und
Rechnungsanschrift an die Session (customer statt customer_email); die ID
liegt an shopping_users und wird wiederverwendet. Namensformat "Vorname
Nachname (Firma)", reine Firmenkunden unter der Firma. Faellt die
Customer-Anlage aus, laeuft der Checkout auf dem bisherigen Weg weiter.
DSGVO-Loeschungen entfernen jetzt auch das Customer-Objekt; die Charges
bleiben als Buchungsbeleg bestehen.
- SE2: StripeMetadataBuilder als einzige Quelle fuer Metadaten und die
description (Bestellnummer, Referenz, Kundenname) - damit greift die
Dashboard-Suche, die bisher nichts Lesbares zu fassen bekam.
- SE3: Die Rechnungsnummer entsteht erst nach der Zahlung und wird daher per
Job am PaymentIntent nachgetragen (Vorab-Reservierung wuerde Luecken im
Nummernkreis erzeugen). stripe:backfill-invoice-metadata traegt sie
rueckwirkend ab dem Go-Live 2026-07-10 nach.
- SE4: Neuer Admin-Button "Zahlungseingang erfassen" an jeder unbezahlten
Bestellung - bisher gab es ihn nur bei Rechnungskauf, und er setzte nur
paid, ohne Rechnung, Punkte, Abo-Aktivierung oder Mail. Der neue Weg laeuft
durch paymentStatusPaidAction, verlangt den Verwendungszweck als Beleg und
laesst einen abweichenden Betrag nur mit ausdruecklicher Bestaetigung zu.
- S6.4 (aus dem Juli-Plan vorgezogen): stripe:sync-payment-status gleicht
offene Zahlungen aktiv gegen die API ab. Aufgefallen beim Testlauf - ein nie
zugestellter Webhook war bisher nicht reparierbar, das Geld lag bei Stripe
waehrend die Bestellung auf offen stand. Laeuft stuendlich im Scheduler.
- 110 Tests (Pest) fuer alle Pfade, Stripe-Client durchgehend gemockt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Erinnerungsmails vor Ablauf der Mitgliedschaft beschrieben noch den
abgeschafften SEPA-Einzug und verlinkten auf die Hauptdomain, wo
/user/membership nicht existiert. Das Produkt fuer die Verlaengerung war
zudem dasselbe wie bei der Registrierung und sprach deshalb vom
Neuabschluss.
- Erinnerungen: nur noch Stufen 31/33/34/35, SEPA-Stufen 32/36/37 samt
:price und :pay_date entfallen; Button verlinkt auf my.mivita.care;
fehlt ein Text zum Status, wird die Mail geloggt statt roh versendet.
- Alle Erinnerungstexte (de/en/es/fr) erklaeren jetzt, dass ohne Zahlung
der Account pausiert und offene Provisionen ausgezahlt werden.
- Eigenes Produkt fuer die Verlaengerung (identifier show_renewal) als
Kopie des Mitgliedschafts-Produkts inkl. Preisen, Kategorien und
Uebersetzungen fuer en/es/fr; die Mitgliedschaftsseite faellt ohne
dieses Produkt auf das bisherige zurueck.
- Mitgliedschaftsseite auf ein Paket reduziert: Upgrade-, Wechsel- und
Abo-Optionen entfernt, Texte auf die manuelle Verlaengerung umgestellt.
- Alte Cron-Weboberflaeche entfernt, der Versand laeuft ueber den
Scheduler (payments:check-accounts).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Karten-Update ohne Abbuchung via PAYONE creditcardcheck/pseudocardpan
(AboPaymentMethodService, CardUpdateResult/CreditCardCheckData-DTOs)
- Eingabe nur durch Abo-Inhaber: CRM- und Portal-Formular + zeitlich
begrenzter Berater→Kunde-Link (AboCardUpdateLink + Mail)
- Änderungshistorie in user_abo_card_changes, Anzeige im Admin-Abo-Detail
- Admin: Abo löschen (destroy) inkl. Test
- Portal-Fix: myOrderCreate wirft 404 statt TypeError bei ungültiger ID
- VIP-Sichtbarkeitstests für Einmalartikel-Fenster ergänzt
- Enthält bereits die Freeze-Guards für den Stripe-Notbetrieb (Folge-Commit)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Öffentliches Widerrufsformular /widerruf mit Bestell-Lookup (throttled)
- Bestätigungsmail an Kunde + Weiterleitung an bestellungen@ (config app.withdrawal_mail)
- Admin-Übersicht/-Detail unter /admin/withdrawals inkl. Sidenav-Eintrag
- Widerruf-Button in Portal-Bestellansicht und Footer-Links
- Model OrderWithdrawal + Migration + Factory + Tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Pay-now-Flow: bei fehlgeschlagenem Abo (Status 3) begleicht der Inhaber die
offene Rechnung im Standard-Checkout mit frei waehlbarer, abo-faehiger Zahlart
(nur eigenes Abo: Berater me-View + Kundenportal), payNow-Controller + Routen
- AboRecoveryCheckoutService baut die ShoppingInstance aus den Abo-Artikeln;
neue Spalte shopping_orders.recovery_user_abo_id verknuepft Bestellung<->Abo
- Bei Erfolg: AboHelper::advanceAboAfterRecoveryPayment schreibt das Abo fort
(Status->2, next_date) und uebernimmt die gewaehlte Zahlart als neues Token
fuer kuenftige Abbuchungen
- Neue Pay-now-Fehlermail (AboPaymentFailedMail) ersetzt die generische
Fehlermail im Abo-Cron; Link fuehrt auf die Abo-Detailseite (CRM bzw. Portal)
- Retry-/"extern erledigt"-Buttons nur noch fuer Admins; tote user/portal-
Retry-Routen entfernt
- Checkout: Recovery-Hinweis statt "Abschluss des Abonnements", Lieferadresse
vorbelegt, korrekter Zurueck-Link (Abo statt Shop)
- UserAbo::selfServiceDetailUrl zentralisiert die Abo-URL (Mail, Back-Links)
- Test-Command abo:test-recovery; Tests fuer payNow, Advance-Logik, Fehlermail
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>