Commit graph

123 commits

Author SHA1 Message Date
ff0860fb81 Doku: Deploy-Notiz zum Statistik-Paket vom 10.09.2026
Haelt fest, was live gegangen ist, welche Daten der Snapshot-Neuaufbau
tatsaechlich angefasst hat (nur backoffice_statistics_snapshots, 8 Monate,
7 VIP-Konten) und welche nicht (Provisionen, Gutschriften, Marketingplan).

Wichtig fuer spaetere Rueckfragen: Die historischen Monatswerte koennen sich
verschoben haben, weil der neue Snapshot die Struktur des Ausfuehrungstags
einfriert und Shop-Stornos jetzt zu den Kundenpunkten zaehlen. Ein
Vorher/Nachher-Vergleich wurde nicht erstellt, die alten Snapshot-Staende sind
ueberschrieben.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 11:43:15 +00:00
233cd01bb5 Doku: Statistik-Audit 09.09., Bewertung und Umsetzungsstand
Der Ordner dev/2026-07-22 ist ueber Monate gewachsen; ohne Einstieg findet man
darin nichts mehr. Deshalb ein README als Wegweiser, mit dem Hinweis, dass bei
Widerspruechen immer die juengere Entscheidung gilt (Beispiel: F7 wurde am 10.09.
abgeloest).

Die Bewertung traegt oben einen Stand-Block mit erledigten und offenen Befunden
samt Sprungmarken. Ueberholte Aussagen der ersten Fassung sind markiert statt
geloescht, damit der Weg nachvollziehbar bleibt.

Der Monatsabschluss steht als eigenes Dokument, weil es eine dauerhafte
Fachregel ist und keine Audit-Antwort: was eingefroren wird, was die Regel nicht
leisten kann, und die Betriebsbefehle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 11:19:50 +00:00
5a38f4ac3e Statistik: uebrige Audit-Korrekturen (Abo-Regeln, Prognose, Sortierung)
A01 — Abo-Regeln waren ueber das Aktiv-Feld umgehbar. Das
Selbstbedienungsformular hat gar keinen Aktiv-Schalter, es spiegelt den Zustand
nur in einem versteckten Feld zurueck; `updateStatus()` hat den Wert trotzdem
uebernommen. Wer den Request manipulierte, konnte sein eigenes Abo dauerhaft
deaktivieren und so die Mindestlaufzeit umgehen, am Pausenpfad mit seinen
Sperren vorbei. Das Feld wirkt jetzt nur noch im Admin-Pfad.

A16 — Statuswechsel wurden nur aus dem Formular protokolliert. Recovery-Zahlung,
Retry, externe Erledigung und die DSGVO-Anonymisierung schreiben `active` direkt
auf das Modell. Das ist heikel, weil die historische Bestandsrueckrechnung ein
Abo verwirft, solange eine protokollierte Deaktivierung nicht durch eine
spaetere protokollierte Reaktivierung aufgehoben ist — ein stiller
`active = true` nimmt es dauerhaft aus allen Folgemonaten.

Der Eintrag liegt deshalb jetzt am Model (`UserAbo::logStatusTransition()`) und
nicht in den einzelnen Diensten: so kann ihn ein kuenftiger Schreibpfad nicht
vergessen, und er entsteht in derselben Transaktion wie die Statusaenderung.
Nebeneffekt: Die DSGVO-Anonymisierung setzt `active = false` ohne `cancel_date`
— bisher fiel ein so abgeschaltetes Abo rueckwirkend aus allen vergangenen
Monaten, jetzt bleibt die Historie korrekt.

A13 — Ein heute faelliges Abo galt ab Mitternacht als erledigt, der
Ausfuehrungsjob laeuft aber erst um 04:00. In diesem Fenster fehlten die Punkte
in der Prognose UND im Ist. Der neue Scope `UserAbo::scopeOpenExecutionUntil()`
zaehlt den heutigen Liefertag mit, solange keine Ausfuehrung angelegt ist —
genau diese Bedingung verhindert die Doppelzaehlung. Alle drei Prognosen nutzen
jetzt denselben Scope statt je einer eigenen Kopie der Datumsregel.

A11 — Der Namens-Tiebreak der Top 3 lief nach dem Abschneiden auf 25
Kandidaten, bei Gleichstand entschied also die Reihenfolge der Abfrage. Die
Auswahlgrenze wird jetzt um alle Gleichstaende an dieser Grenze erweitert, und
Kandidaten ohne Account fallen vorher weg statt die Plaetze zu blockieren. Als
letzte Regel entscheidet die user_id, damit eine gecachte Rangliste
reproduzierbar bleibt.

A14 — Sortiert wurde ueber den angezeigten Zellentext. Unter `en` liefert die
Anwendung `Y-m-d` und `1,000.00`, damit ergab jedes Datum 0 und `1,000.00` wurde
zur 1. Die Zellen tragen jetzt `data-sort-value` mit dem unformatierten Wert;
ohne Attribut gilt weiter der Textweg. Dabei mitgenommen: Umlaute landeten hinter
dem Z (jetzt localeCompare), leere Werte lagen als 0 zwischen den negativen
Stornowerten, und `next_date` wurde doppelt formatiert.

Ausserdem A08 (Prognose auch ohne erreichte Qualifikationsstufe), A09
(Summenzeile im Neupartner-CSV), A10 (Lieferungszahl vorgeladen), A17
(Faelligkeitsgruppen ohne Monatsueberlauf), A18 (Formelinhalte im CSV als Text)
und A19 (fehlgeschlagene Provisionsberechnung ist als solche erkennbar statt
0,00 EUR).

Audit-Befunde A01, A08, A09, A10, A11, A13, A14, A16, A17, A18, A19.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 11:19:38 +00:00
92344f7080 Statistik: abgeschlossene Monate werden vollstaendig eingefroren
Am ersten des Folgemonats wird der Monat abgerechnet, die Provision berechnet
und Gutschriften werden ausgestellt. Deswegen fliessen auch Stornos nicht mehr
rueckwirkend ein. Genau dieser Stand muss eingefroren werden — und zwar
einschliesslich Teamstruktur und Detaillisten, nicht nur der Kennzahlen.

Bisher hat `overlayCurrentAboStock()` beim Lesen eines gespeicherten Monats die
gesamte Kundenabo-Topliste live neu gerechnet, also auch abgerechnete
Monatswerte. Und die Detaillisten hinter den Kacheln lasen Monatsbuchungen und
Teamzuordnung grundsaetzlich neu — nach einem Sponsorwechsel oder einer
Loeschung konnte derselbe historische Klick andere Ergebnisse liefern als die
Kachel darueber.

Das Overlay entfaellt; der Snapshot wird unveraendert zurueckgegeben. Die
Detaillisten lesen die eingefrorene Mitgliedermenge aus dem Snapshot
(`user_ids` lag dort bereits, wurde nur nie benutzt) und die eingefrorenen
Abo-Ids aus dem neuen Feld `abo_ids`. Payload-Version 2.

Grenze, die bleibt: Ein Rebuild vergangener Monate friert die Struktur des
AUSFUEHRUNGSTAGS ein, nicht die des damaligen Monatsendes — frueheres
Sponsoring wird nirgends historisiert. Der `--force`-Lauf ist deshalb nur
richtig, wenn man bewusst einen einheitlichen Stand herstellt. Das ist hier der
Fall (Schemawechsel des Payloads), fuer den laufenden Betrieb genuegt der
naechtliche Lauf ohne `--force`.

Loest die August-Entscheidung F7 ("Abo-Bestaende tagesaktuell") ab. Zwei Tests
hielten die alte Regel fest und beschreiben jetzt die neue.

Mit drin, weil dieselben Dateien betroffen sind:
- Negative Monatssalden verschwanden aus dem Drilldown (`HAVING > 0`), obwohl
  ihre Betraege in der Uebersicht steckten. Nur die 1000-Punkte-Liste behaelt
  ihren Schwellenfilter (A03).
- Zusatzpunkte einer stornierten Bestellung werden anteilig verrechnet statt
  voll abgezogen (A06).
- Unformatierte Sortierwerte fuer die Detailliste (A14, siehe Folgecommit).

Audit-Befunde A03, A04, A06, A12.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 11:19:06 +00:00
1900860c74 Statistik: Stornos mindern die richtige Punktekategorie
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>
2026-09-10 11:18:40 +00:00
75c2790bd2 Warenkorb: nicht-numerische Mengen brechen die Gewichtsberechnung nicht mehr
Yard::weight() multiplizierte Gewicht und Menge ohne Cast. Beide Werte stammen
aus Session- bzw. Request-Daten; ein fuehrend-numerischer String wie "2,5" loeste
unter PHP 8 die ErrorException "A non-numeric value encountered" aus. Danach war
die Position dauerhaft defekt, da reCalculateShippingPrice() bei jedem Aufruf
erneut fehlschlug.

Cart::update() setzt die Menge ohne Validierung (anders als setQuantity()).
Eintrittspunkte waren das quantity-Feld im Shop-POST sowie das Textfeld fuer die
Menge im Backoffice-Warenkorb.

- Yard: neue cartItemWeight() mit explizitem int-Cast fuer Gewicht und Menge
- CardController: nicht-numerische Mengen werden uebersprungen, Schutz gegen ein
  quantity-Feld, das kein Array ist
- OrderController: nutzt das bereits vorhandene $qty (int) statt $data['qty'] roh

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 07:16:30 +00:00
68d230d4e5 Stripe: Kundendaten, Rechnungsnummer, manueller Zahlungseingang
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>
2026-08-28 09:04:28 +00:00
30fb031d12 Mitgliedschaft: Erinnerungen und eigenes Verlaengerungs-Produkt
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>
2026-08-26 08:24:32 +00:00
0bdb6eabc6 Slim: sanitizeFileName kürzt hinten statt vorne und behält die Dateiendung
- Lange Namen werden am Ende gekürzt (vorher substr(-33): "Bildschirmfoto…"
  wurde zu "ldschirmfoto…", der Namensanfang fiel weg)
- Whitespace inkl. geschütztem Leerzeichen (macOS-Screenshots) wird zu "-",
  damit der Dateiname ohne Encoding in URLs funktioniert
- Fallback "datei", wenn nach der Bereinigung nichts Verwertbares übrig bleibt
- 5 Unit-Tests für Kürzung, Whitespace, Sonderzeichen und Fallback

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 11:11:01 +00:00
8afaf9d4e2 Kundenrueckmeldung 25.08.: Formulierung zur Prognose-Gegenpruefung praezisiert
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 10:48:54 +00:00
94684f7ffd Firstlines-Kachel: Beschriftung praezisiert (Berater statt aktiv)
Auf der Firstlines-Seite koennen Zeilen ein 'Account abgelaufen'-Badge tragen und
trotzdem in die gezaehlte Menge fallen - 'aktiv' war dafuer das falsche Wort. Die
Kopfzeile heisst jetzt ':active Berater - :total insgesamt' (de/en/es/fr), passend
zur 'Berater'-Spalte der Statistik.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 10:48:28 +00:00
8dc0361fec Firstlines: Seite und Dashboard-Kachel zaehlen wieder dieselbe Menge
Beim Formulieren der Kundenrueckmeldung aufgefallen: der vorige Commit hat die
Inkonsistenz an einer Stelle behoben und an einer anderen wieder eingebaut. Die
Kachel zaehlt ueber isActiveConsultant() (Mitgliedsebene + gesetztes
Abrechnungskonto), die Kopfzeile der Firstlines-Seite zaehlte aber ueber
isActiveAccount() (Konto in der Zukunft). Bei abgelaufenen Jahresgebuehren nannten
Kachel und Seite damit unterschiedliche Zahlen - genau das, was der Fix vermeiden
sollte.

firstlines() bekommt den BackofficeDashboardService injiziert und nutzt dieselbe
Regel. Test um beide Randfaelle erweitert (abgelaufenes vs. gar kein
Abrechnungskonto), damit die Doppel-Definition nicht zurueckkommt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 10:47:50 +00:00
cee4a2e0eb Statistiken: Review-Fixes vor Livegang (Fehldarstellungen + Test-Regression)
Ergebnis des Senior-Reviews über dev/2026-07-22 (Konzept 22.07., Feedback 24.07.,
Plan 19.08.). Rechnerisch war nichts zu korrigieren: Payline-Punkte und Provision
des Prognose-Blocks (N4) stimmen exakt mit der Provisions-Engine überein
(54.718,5 P. bzw. 3.772,88 EUR gegengerechnet an User 454). Behoben sind
Darstellungs- und Konsistenzfehler, die der Beraterin als falsche Zahlen
aufgefallen wären:

- "+N neu" schrumpfte rückwirkend, der Bestand daneben nicht: newAboCount() nutzte
  weiter activeAbo(), während aboStockCount() auf das Monatsende zurückrechnet.
  Beide teilen sich jetzt aboStockQuery(); die Jahres-Wachstumstabelle ebenso.
- Level-Kachel widersprach sich ("Zum nächsten Level Diamant fehlen X" über
  "Karriere-Level: Diamant"): Text auf die Monats-Qualifikation umformuliert
  (de/en/es/fr), Überschrift "Qualifikation in diesem Monat", Karriere-Level nur
  noch bei Abweichung.
- Firstlines-Kachel zählte alle direkten Partner, "Teampartner gesamt" daneben nur
  aktive (F1): Kachel nutzt jetzt isActiveConsultant(), die Detailseite listet
  weiter alle und weist beide Zahlen aus.
- Neupartnerzahlen vergangener Monate sanken auf dem Home-Dashboard, sobald
  Jahresgebühren ausliefen: neue User::newPartnerReferenceDate() (laufender Monat =
  heute, abgeschlossener Monat = Monatsende). Dabei die drei Inline-Kopien der
  Neupartner-Regel auf scopeNewPartnerIn()/isNewPartnerIn() zusammengeführt.
- Top-3-Karte ließ den Betrachter aus, obwohl "ich gehört in die Liste" in der
  Juli-Runde für alle anderen Ranglisten umgesetzt wurde: zählt jetzt mit, inkl.
  is_self und "ich"-Badge.
- Neues Profilfoto zeigte bis zu 6 h ein totes Bild: die URL steckt nicht mehr im
  Cache, sondern entsteht beim Lesen (withProfileImages()).
- Test-Regression seit der Juli-Runde: Unit/Services/BackofficeDashboardServiceTest
  fiel über die fehlende Spalte status_points (kam mit nonPaylinePoints() dazu).
- Kleinigkeiten: ProfileImageController gab die komplette Exception als
  Flash-Message aus (jetzt Log + Sprachkey), BackofficeDrilldownService formatierte
  Datumsangaben hart auf d.m.Y statt über Util::formatDateDB().

Tests: neuer HomeMonthlyStatsTest (buildMonthlyStats war ungetestet) plus Tests für
Stichtags-Zählung und Foto-Cache. Fünf Tests kodierten das alte Verhalten
(Betrachter zählt nicht mit, abgelaufener Account zählt nie) und wurden auf die
neue Semantik umgestellt, jeweils mit Gegenprobe. Suite: 861 grün (vorher 855),
89 rot — ausschließlich die in docs/2026-06-11-test-suite-vorbestehende-fehler.md
dokumentierten Altlasten.

Offen und bewusst nicht enthalten:
- Deploy-Schritt: backoffice:store-statistics-snapshots --force (vorher dumpen).
  Kein vorhandener Snapshot enthält recruiter_ranking, Mai/Juni zusätzlich weder
  non_payline_points noch die Führungskräfte-"gesamt"-Spalten; der Cron überspringt
  vorhandene Snapshots ohne --force. Achtung: die Neuberechnung ist nicht
  verlustfrei.
- TreeHtmlRendererTest/TreeCalcBotIntegrationTest: die Abo-Badges machten den
  Renderer datenbankabhängig; die Dateien waren schon vorher rot und gehören in die
  separate Test-Aufräumaufgabe.
- deleteImage bleibt ein GET-Route (gleiches Muster wie das Shop-Bild).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 09:41:53 +00:00
f6faad9ae7 Abo: extern-erledigt für gelöschte Abos + Lieferhistorie-Umzug (Ticket Kunz Mai-Punkte)
- AboExternalCompletionService: Bestellungen gelöschter/ersetzter Abos können
  nachträglich extern erledigt werden (Punkte + Status 8 + Versandstatus);
  das gelöschte Abo wird dabei nicht reaktiviert
- IncentiveTracker::trackSalesVolume: Abo-Zuordnung inkl. gelöschter Abos
  (withTrashed) - nachgebuchte Umsätze bereits getrackter Abos zählen weiter
- Neue Wartungs-Commands: abo:extern-done {shopping_order_id} und
  abo:move-orders {from_abo} {to_abo} (--dry-run, Guards: gleicher Kunde)
- Latent datumsabhängigen Alt-Test in AboExternalCompletionTest repariert
  (Buchung nutzt date(), nicht Carbon::setTestNow)

Fall Kunz (dev/2026-08-12-intensiv-tickets): Dannenhauers gelieferte, aber am
17.06. beim Nachtragen vergessene Mai-Order 53274 → abo:extern-done (+54 P);
Bockhackers Lieferhistorie von gelöschtem Abo 197 → 496 umgehängt (Zähler 4).
Bockhackers 24 P existieren bereits (Juni-Buchung der Mai-Lieferung).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 12:06:29 +00:00
867dc21b57 Statistiken: Kundenfeedback 2026-08-19 komplett (N1-N8) + Review-Fixes + VP-/Incentive-Repair gehärtet
Statistik-Änderungen 2026-08-19 (dev/2026-07-22/entwicklungsplan-2026-08-19.md, alle 8 Pakete gemäß beantworteten F-Fragen):
- N1 Monatsstatistik: Team-Cluster (Firstlines klickbar -> neue Seite user_team_firstlines, Teampartner gesamt/Payline [nur aktive], Abos Payline mit +N) + Level-Kachel (Quali-Stufe, fehlende Payline-/persönliche Punkte, Karriere-Level); LiveCommissionService-Snapshot v3 reicht Level-/Quali-Daten durch
- N2 Abo-Kacheln auf Home monatsbezogen (aboStockCount-Rückrechnung über cancel/deleted/Status-Historie; VIP-Statistik bleibt tagesaktuell)
- N3 VIP-Statistik 'Recruiter des Monats' (Neupartner-Definition wie KPI, rekursives Ketten-Volumen als Gesamtpunkte inkl. Starterpaket) + Drilldown/CSV-Spalte 'Punkte gesamt'
- N4 Prognose-Block 'Level & Provision laut Prognose (Stand 25.)' mit unverbindlich-Kennzeichnung (ebenengenaue offene Abo-Punkte + TP-Laufstände)
- N5 Checkout-Herkunft: Dropdown raus, Pflicht-Freitext (lokalisierte Fehlermeldung)
- N6 Berater-Profilfoto (user_accounts.profile_image, Einwilligung als Sprachkey-Platzhalter, DSGVO-Löschung) + teaminterne Top-3-Karte (TopResultsService, Scope-Schalter)
- N7 USt-Optionen: 'nicht umsatzsteuerpflichtig' zuerst + '§ 19 UStG'-Textfix
- N8 Jahresgebühr-Nachfassliste je Berater (AnnualFeeDueService, überfällig/1-3 Monate, Sidenav)

Kontroll-Review (8 Perspektiven) angewendet: next-Level-Fallback im Steady-State (resolveNextLevel — sonst 'Höchstes Level erreicht' für Normalfälle), _calculated-Punktebasis bei mehr Paylines, keine Wiederbelebung deaktivierter Abos in der Monatsrückrechnung, Payline-Abo-Kachel monatskonsistent, Top-3-Nachrücker; Performance: BackofficeDashboardService als Singleton mit lineBuckets-Memo (+flushLineBucketsMemo), level_forecast 6h-Cache; Reuse: User-Scopes firstlinesOf/newPartnerIn, SalesVolumePoints::totalPointsByUser, Slim::storeCroppedImage (3 Upload-Kopien vereinigt), activeAbo-Scope statt Inline-Prädikate, Form::open-Konvention, Return-Types

Repair-Härtung (Kundenentscheide): VP-Nachtrepair dedupliziert über shopping_order_id (Live-Pfad schreibt sie jetzt mit), zählende Alt-Zeilen-Erkennung, Vormonats-Scan für Monatswechsel, Firmenaccount-Buchungen (config mivita.company_user_id) werden automatisch auf den Berater umgebucht (in place bzw. per Gegenbuchung bei geschlossenem Monat; Echtfall Anna Schall Order 58797 -> Christina Schuder), Berater-zu-Berater nur Warnung; Incentive-Repair: m_sponsor=NULL löscht nicht mehr, FK-Repair vor Stale-Bereinigung, gebatchte Lookups, zentrales Incentive::qualificationWindow(); .env aus dem Git-Tracking entfernt (Pflege per SFTP)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 10:44:44 +00:00
841d87e161 Incentive: Ticket-Fixes 2026-08-12 + Team-Struktur nach active_date sortiert
Incentive (Montenegro 2026):
- Live-Tracker: whereDate() für Zeitraum-Vergleiche – Bestellungen am letzten
  Tag des Qualifikationszeitraums gingen verloren (DATE-Spalte vs. Datetime)
- Repair/Rebuild: Neupartner-Definition auf Starterpaket-Bestelldatum im
  Zeitraum umgestellt (statt users.created_at) – Bestandskunden-Upgrades
  zählen jetzt wie im Live-Tracking (Fall Dino Portaro → Steffi Karrer)
- Neu: removeStaleSponsorTrackings() – Partner-Trackings wandern nach
  Sponsorwechsel zum aktuellen Sponsor (Fall Anna Schall lag beim Firmenkonto)
- incentive:calculate: Sponsorwechsel-Bereinigung eingebunden + Statistik

Team-Struktur (/user/team/structure):
- Downline sortiert nach active_date (Partner-Aktivierung) statt users.id:
  zentral in TreeHtmlRenderer::sortByActivation() (wirkt sofort, auch für
  gespeicherte Monats-Snapshots) + orderBy in beiden Downline-Queries

Doku: dev/2026-08-12-intensiv-tickets/README.md (4 Kundentickets analysiert,
Lücke durch verspätete Zeitraum-Verlängerung hatte sich per Nacht-Cron geheilt)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 10:28:06 +00:00
ac3bd6e8ea Statistiken/Abo: Kundenfeedback 2026-07-24 komplett umgesetzt + Steuern: DATEV-Fixes 08+09/2025 (Sammel-Commit Branch-WIP)
Feedback-Umsetzung (dev/2026-07-22/feedback-analyse-2026-07-24.md, alle 10 Punkte):
- VIP-Statistik: eigener Berater ('ich', Linie 0) in Neue-Kundenabos- und beiden Top-10-Listen; Top-10-Umsatz sortiert nach abgerechneten Ist-Punkten + Abo-Anzahl-Spalte; Fußnote nicht-payline-wirksame Punkte (SalesVolumePoints::nonPaylinePoints)
- Führungskräfte-Liste: beide Sichten 'direkt' (an nächster FK abgeschnitten, additiv) und 'gesamt' (komplette Downline), Gesamt-Spalte nach Kundenzählweise
- Team-Abos: je Ebene 'abgerechnet (Monat)' und 'bis zum 25. erwartet'; Payline-Karte mit Teamabo-/Kundenabo-Zählern
- Incentive-Live-Ranking: Qualifizierte zuerst nummeriert (fixt auch isWinner rank<=max_winners)
- Porto: Netto-/Brutto-Kennzeichnung (Berater netto, Endkunden inkl. MwSt., portal_view-Flag; auch CRM-/Shop-Warenkorb + Checkout), Yard::shipping_tax-Fix
- Struktur-Ansicht: Abo-Badges 'A' (eigenes Abo) und '(n)' (Kundenabos) je Teampartner, gebatcht
- Abo-Löschung: Softdelete setzt active=0 (UserAbo deleting-Hook) + Bereinigungs-Migration für gelöschte-aber-aktive Abos
- Doku: technische Analyse + Kundinnen-Feedback-Dokument unter dev/2026-07-22/
- Tests: IncentiveRankingTest, TeamStructureAboBadgesTest, YardShippingTaxTest (neu); BackofficeStatisticsListsTest, TeamAboOverviewTest, PaylineForecastServiceTest, AboAdminDeleteTest, AboOneTimeViewTest erweitert

Außerdem enthalten (Branch-WIP vor dem Feedback): DATEV-Abgleich-Fixes 08+09/2025, USt-ID-Live-Validierung sowie restliche unkommittete Stände (u. a. Abo-Pause-Sprachkeys).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:56:02 +00:00
657d4bd5b7 Abo: Fix Checkout-Rücksprung bei Einmalprodukten in der Bestellstrecke
Die Submit-Validierung checkSendYardForPayment() prüfte jede Cart-Zeile
gegen das Abo-Sortiment (12/13) – Einmal-Zeilen (show_on 3) fielen durch
und der Checkout-Klick sprang per back() kommentarlos auf die
Bestellseite zurück. Einmal-Zeilen werden jetzt gegen das
Einmal-Sortiment validiert (manipulierte Zeilen weiterhin abgewiesen).

Zusätzlich zeigt list.blade.php jetzt alle Validierungsfehler an
(vorher nur Comp-Produkt-Fehler → Fehler blieb unsichtbar).

Tests: 2 neue Regressionstests in AboCreateOneTimeCartTest (19 grün).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 13:02:43 +00:00
cdb121fde0 Abo: Einmalprodukte bereits bei der Abo-Anlage + es/fr-Sprachsync
Einmalprodukte (Normalsortiment, show_on 3) können jetzt schon beim
Abo-Abschluss hinzugefügt werden – im Portal-Wizard (Abschnitt im
Upgrade-Schritt) und in allen drei Sales-Center-Abo-Strecken (eigene
Produktliste). Variante A: Zeilen laufen als Cart-Option abo_addon mit
durch den Checkout, werden sofort bezahlt und mit der Erstlieferung
verschickt (Rechnung/Lieferschein gruppieren automatisch).

- Checkout persistiert is_abo_addon (CheckoutRepository, PaymentHelper)
- createAboItems/ensureUserAboItemsFromLatestOrder überspringen Addons
  (sonst würden Einmalprodukte dauerhaft wiederkehren); amount ohne
  Addon-Anteil; aboHasBaseProduct ignoriert Addon-Zeilen
- CreateAboReplacementOrder propagiert is_abo_addon in Ersatzbestellungen
- iq-shopping-cart.js: zweite Datatable + abo_addon-Flag-Weitergabe
- i18n: neue abo.onetime_create_*-Keys (de/en/es/fr); zusätzlich es/fr
  komplett synchronisiert (abo-Kartenblock, navigation, payment,
  weborder inkl. Entfernung PAYONE-Altlasten, withdrawal.php neu)
- Tests: AboCreateOneTimeItemsTest, AboCreateOneTimeCartTest,
  AboPortalCreateOneTimeTest (17 neue, 122 Tests gesamt grün)

Plan: dev/2026-07-21-abo-einmalprodukte-anlage/

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:36:40 +00:00
1e7ae92d0c Stornorechnung: Fix überdeckte Artikelzeile + Regenerate-Command
Der ACHTUNG-Hinweiskasten hat wegen fixer 15mm-Höhe des Titel-Blocks
den Tabellenkopf und die erste Artikelzeile überdeckt (z.B. Artikel
5010400 auf den Stornos zu Bestellung 56965/56966). Titel-Block wächst
jetzt mit dem Inhalt.

Neuer Command sales:regenerate-cancellation-pdf erzeugt die PDFs
bestehender Stornorechnungen neu (gleiche Nummer/Pfade, keine
Punktebuchung, keine Mails) – per Rechnungs-ID oder mit --order per
Bestell-ID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 14:19:28 +00:00
8e09aaefe8 Stripe-Integration (S7.4): Payment-Dashboard für Stripe
- Provider-Status/Uptime: Stripe-Kachel aktiviert; payment:check-uptime prüft
  zusätzlich api.stripe.com/v1/ (401 ohne Key = erreichbar) inkl. Auto-Incident
- Zahlungen: Anbieter-Filter, Provider-Badges, Umsatz je Provider,
  Stripe-Dashboard-Deep-Links (ShoppingPayment/PaymentTransaction::
  stripeDashboardUrl, pi_-Guard, testmodus-bewusst)
- Rohe Transaktionen: Anbieter-Filter/-Spalte, Erfolgsraten je Anbieter;
  Bugfix: Stringable-Vergleich ließ den txaction-Filter immer mit leerem Wert
  greifen — Liste war dauerhaft leer
- Neue Kachel „Abos je Anbieter" (Index + GF-Ansicht) mit Fehlerzählern und
  Stripe-Anteil-Fortschrittsbalken (Auslaufmodell PAYONE → Stripe ablesbar)
- Incident-Kachel weist Stripe/PAYONE getrennt aus; Log-Viewer mit
  Kanal-Umschalter stripe/payone
- Plan-Doku: S7.4-Umsetzungsstatus, Abo-Go-Live 2026-07-10 vermerkt

Tests: PaymentDashboardProviderTest (9 neu), Dashboard-Suite 49 grün.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:57:50 +00:00
ce4750fd1a Stripe-Integration (S4, S5, S7.6): Abos, Zahlungsart ändern, Zahlarten-Kommunikation
Phase S4 – Abos über Stripe (E2E komplett im Testmodus):
- Abo-/Recovery-Checkout: customer_creation=always + setup_future_usage=off_session
- StripePaymentService::chargeAbo (off-session PaymentIntent, Idempotency-Key),
  StripeGateway, Provider-Dispatch im Abbuchungs-Cron (PAYONE-Pfad unverändert)
- Webhook: Zahlungsmittel-Capture vor Paid-Pfad, 503 payment_not_yet_visible bei
  Cron-Transaktions-Race; Command stripe:process-pending-events (Cron-Ende + 10-min
  Scheduler) verarbeitet liegengebliebene Events nach
- createNewAbo/advanceAboAfterRecoveryPayment Stripe-aware inkl. Race-Absicherung;
  Recovery-Orders nutzen advanceAboAfterRecoveryPayment (latenter PAYONE-Duplikat-Bug)
- Migrationen: Stripe-Abo-Spalten auf shopping_payments; clearingtype varchar(3)→(20)
  inkl. Reparatur der zu 'str' gekürzten Live-Zeilen

Phase S5 – Zahlungsart ändern (Scope: jede Zahlart, nicht nur Kreditkarte):
- Stripe Checkout mode=setup ohne Abbuchung; PAYONE-Abos ziehen beim Wechsel auf
  Stripe um (Auslaufmodell §7.2)
- AboPaymentMethodService: beginStripeChange/finalize/applyStripePaymentMethod
  (idempotent, detacht alte PM, card_changes-Log); Webhook setup_intent.succeeded
  als Sicherheitsnetz; Einstiege Portal/CRM/Berater-Kunden-Link flow-abhängig
- Views/Mails flow-abhängig („Zahlungsweise" statt „Kreditkarte")

Phase S7.6 – Zahlarten-Kommunikation:
- Einheitliche Zahlarten-Logos (SVG) + wiederverwendbares Partial payment_logos;
  Checkout/Footer zeigen Kern-Zahlarten + „···"-Icon, /zahlungsarten komplett neu
  mit allen aktiven Stripe-Zahlarten (gegen Account-Konfiguration verifiziert)
  und Vorkasse (PAYONE); Sofort/SEPA entfernt

Tests: StripeAboTest (15), StripePaymentMethodChangeTest (10),
PaymentMethodsCommunicationTest (28), PaymentProviderManagerTest angepasst.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 14:45:17 +00:00
6f21845607 Stripe-Integration (S0–S3): Einmalzahlungen über Stripe Checkout + Notfall-Go-Live
Wegen PAYONE-Ausfall vorgezogen live geschaltet: alle Einmalzahlungen
(Shop + Salescenter) laufen über Stripe Hosted Checkout, Abos sind bis
Phase S4 eingefroren.

- Provider-Abstraktion: PaymentProvider-Enum, PaymentGatewayInterface,
  PaymentProviderManager (Settings-Schalter, provider-treu je Bestand),
  PayoneGateway als unveränderter Adapter
- StripePaymentService: Checkout Session (eine Gesamtposition, Metadata,
  success/cancel-Redirects), StripeClientWrapper (lazy, Dark Launch),
  StripeCustomerService, StripeTransactionRecorder (client_secret-Redaction)
- Webhook /stripe/webhook: Signaturprüfung, Idempotenz über
  stripe_webhook_events (Crash-Retry-Reprocessing), txaction-Prioritäten,
  Betrags-Guard, lockForUpdate-Paid-Pfad, genau eine Bestätigungsmail
- Zahlungsmethoden-Anzeige aus charge.succeeded (carddata inkl. Wallet,
  auftragsunabhängiges Backfill aus dem Event-Store)
- Checkout: Wartungsmodus mit Admin-Bypass, Abo-Freeze-Hinweis,
  Stripe-Option (Online-Zahlung) statt Zahlartenwahl, Vorkasse ausblendbar
- Abo-Freeze-Guards: Cron user:make_abo_order, Retry, Kartenupdate
- Admin-Settings-Karte Zahlungen (Provider, Wartung, Freeze, Vorkasse)
- Hotfix: 500 bei Vorkasse ohne Clearing-Daten in admin/sales/_detail
  und checkout-final (PAYONE-Ausfall)
- Doku dev/2026-07-08-stripe/ (Plan S0–S8, Konzept, IST-Analyse)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 15:56:41 +00:00
671022a28d Abo: Kreditkarte aktualisieren (CRM, Portal, Berater-Link) + Admin-Abo-Löschung
- 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>
2026-07-08 15:56:19 +00:00
63c36555a5 Widerruf: Gesetzliche Online-Widerrufsfunktion (Formular, Bestätigungsmail, Admin-Verwaltung)
- Ö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>
2026-07-08 15:56:02 +00:00
eac923968d Admin-Abo: Produkte trotz Mindestlaufzeit abziehen/entfernen
Die Admin-Detailseite postete Warenkorb-Aktionen mit view=me/ot, wodurch
Add-only-Modus und Change-Lock auch fuer Admins griffen (Reduzieren wurde
verworfen, Entfernen lieferte 403). Formular postet jetzt mit view=admin;
checkPermissions laesst view=admin nur noch fuer Admins zu (schliesst
zugleich die Luecke, dass unbekannte Views alle Besitzer-Pruefungen
uebersprangen).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 08:17:27 +00:00
d50be7135a Backoffice-Statistik: Neue-Kundenabos-Ranking & Führungskräfte-Abos
Zwei neue Listen unter der Team-Übersicht: Ranking der Berater nach neuen
Kundenabos im Monat (inkl. Gesamtbestand und Linie) sowie eigene und
Kundenabos aller Führungskräfte ab Stufe Vertriebsleiter mit Gesamtsumme.
Beides fließt mit in die Monats-Snapshots ein.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:24:01 +00:00
0660f4e0a0 Abo-Recovery: fehlgeschlagene Folgezahlung im Checkout nachholen
- 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>
2026-07-02 10:38:05 +00:00
78db952130 Doku: CLAUDE.md erweitert & Salescenter-/Arbeitsnotizen
CLAUDE.md um Multi-Tenant-Routing, Payment/PAYONE, Konventionen & Dev-Test-
Commands ergaenzt. Neue Arbeitsnotizen unter docs/salescenter/ sowie
docs/Kurzfristige Anpassungen.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:37:53 +00:00
84238c7694 Subdomain-Cleanup-Commands
Neue Artisan-Commands DeleteAllKasSubdomains und DeletePartnerShopSubdomains
zum Aufraeumen von KAS-/Partnershop-Subdomains, jeweils mit Feature-Tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:37:45 +00:00
9b7665c59a Abo-Wartungs-Commands & Ausfuehrungs-Reminder aktivieren
Neue Artisan-Commands CreateAboReplacementOrder (Ersatzbestellung fuer ein
Abo anlegen) und RepairAboOrderUserAssignment (fehlerhafte User-Zuordnung
von Abo-Bestellungen korrigieren), jeweils mit Feature-Tests.
Ausserdem den abo:send-execution-reminder-Schedule (06:00) aktiviert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:37:39 +00:00
2b2847add1 Abo: Doppelbestellung verhindern, Payment-Link loeschen & Pending-Status
Verhindert das Anlegen eines zweiten Abos (eigenes oder Kundenabo) wenn
bereits eines initiiert ist bzw. ein offener Abo-Payment-Link existiert
(redirectIfAboAlreadyInitiated). Payment-Links koennen jetzt geloescht
werden, abgesichert ueber canDeleteInstance + Shop-Zugehoerigkeit, mit
Erfolgs-/Fehler-Flash. Kundenabo-Uebersicht zeigt den Pending-Initial-
Payment-Status (wartend / in Bearbeitung / fehlgeschlagen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:37:30 +00:00
42ef4dd1c6 DHL-Versand: Sendungs-Info-Button in Bestelllisten & Absenderadresse
Neuer schlanker DHL-Info-Button (getDhlShippingButton) in den Order- und
Shop-Sales-Datatables, oeffnet ein Read-only-Modal (modal_dhl_shipping_info)
mit den Tracking-Daten; dhlShipments wird dafuer eager geladen.
Absenderadresse in config/dhl.php aktualisiert, plus kleinere Fixes in
DhlShipmentController/Service/DataHelper und CreateReturnLabelJob.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:37:13 +00:00
7a620d66ae Backoffice-Statistik: Punkte-Fix (kumulative Snapshots) & Meine Statistik
Punkte/Umsatz korrekt aus per-Zeile points/total_net je status berechnen
statt SUM der kumulativen month_*-Snapshots (zaehlte massiv mehrfach).
Zentralisiert in SalesVolumePoints, genutzt von Dashboard- & Drilldown-Service.

Detail-Tabelle neu gegliedert: Eigenpunkte | Shop | Abo | Sonstige |
Kundenpunkte (=Summe) | Gesamt, mit gruener Hervorhebung der Basis-/Summenspalten.

Neue VIP-Seite "Meine Statistik" (Untermenue neben Team-Statistik):
KPI-Karten + nachvollziehbare Detailtabellen, gruppiert nach eigenen
Bestellungen, Shop-Bestellungen und Abos (PersonalStatisticsService).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:31:14 +00:00
9565814221 Team-Abo-Uebersicht: nur aktive Berater zaehlen
Die Linientabelle zaehlte alle Downline-User statt nur aktive Berater
(m_level + gueltiges payment_account), wodurch zu viele Berater angezeigt
wurden. Filter-Logik aus BackofficeDashboardService (isActiveConsultant,
activeTeamUserIds) wiederverwendet und in TeamAboOverviewService
(buildLineRow, teamKpis, monthlyGrowth) angewendet.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 13:25:44 +00:00
386851aee8 Backoffice-Statistik vereinfacht & Dashboard-Monatsstatistik konsolidiert
- Statistik-Uebersicht: Tabelle auf Linie/Berater/Umsatz(Punkte)/Teamabos/
  Teamkundenabos reduziert; Neupartner als grünes "+N"-Badge in der
  Berater-Spalte; 1000-Punkte-Shop & Neupartner als klickbare KPI-Cards
  (Augen-Icon/Liste anzeigen)
- Teamkundenabos-Drilldown pro Teampartner gruppiert (Anzahl + Volumenpunkte
  gesamt, aufklappbar je Abo), CSV-Export angepasst
- Home: fehlerhafte Monatsstatistik-Card (inkonsistente aktiv-Definition,
  kein Monatsbezug, schwere Live-Logik im Blade) mit der Abo-KPI-Card
  zusammengefuehrt; Logik nach HomeController::buildMonthlyStats() ausgelagert,
  konsistent ueber TeamAboOverviewService/BackofficeDashboardService
- Team-Payline fuer laufenden Monat live berechnet und 6h gecacht (Snapshot)
- Kacheln in Cluster (Umsatz/Neupartner/Abos) gruppiert
- Services: teamKpis() um *_new ergaenzt, personalAboKpis() & newPartnersCount() neu
- i18n (de/en/es/fr), Tests ergaenzt/aktualisiert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 13:00:38 +00:00
4135726772 Abo-Uebersichten: Team-Ansicht konsolidiert, Monatsfilter, Wachstum & Dashboard-KPIs
- /user/abos/ot: Filter nach Eingangsmonat (start_date) ueber neuen UserAbo-Scope
- Konsolidierte Team-Abo-Ansicht nach Ebenen/Linien (Bestand, neue Abos im Monat,
  Eigen-/Kundenabos, Punkte je Ebene + Summen) via TeamAboOverviewService
- team/customers leitet auf konsolidierte Ansicht um; Navigation bereinigt
- Mitglieder-Detail-Modal mit Abo-Zusammenfassung (Eigen-Abo, Kundenabos,
  Punkte, Beginn, naechste Ausfuehrung, Ausfuehrungen)
- Wachstums-Tabelle (Jahr x Monate) + Liniendiagramm (neue Eigen-/Kundenabos)
- Dashboard-KPI-Kachel (Gesamtabos, Kundenabos, 1000-Punkte-Shops)
- i18n de/en/es/fr, Tests (Filter, Overview, Wachstum, Modal, Redirect)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 14:07:29 +00:00
65b867f8ee Abo Einmalprodukte: Phasen 5-7, konfigurierbare Sperrfristen, Lokalisierung & Dashboard
Rechnung/Lieferschein:
- PDF-Gruppierung Abo- vs. Einmal-Artikel (is_abo_addon), rueckwaertskompatibel
- Vorschau-Command sales:preview-invoice (umgeht Test-System-Gate, ohne Rechnungsnummer)

Reminder-Mail (vorbereitet, Cron inaktiv):
- MailAboExecutionReminder + Template, abo:send-execution-reminder, Spalte onetime_reminder_sent_for
- Berater -> Sales Center, Kunde -> Portal; Vorschau abo:preview-reminder
- Datum-Fix: formatDateDB() statt formatDate() (ICU-Format)

Sperrfristen konfigurierbar (Admin-Settings):
- abo-lock-days-change (Liefertag + Abo-Produkte), abo-lock-days-pause-cancel
- Meldungen mit :lock-Platzhalter; Pausieren/Kuendigen vom Change-Lock entkoppelt
- Abo-Produkt-Aenderungen im Lock-Fenster server- + clientseitig eingefroren
- Selbstbedienung: Add-only/Lock nach $view (kein Admin-Bypass), Stale-Cache-Fix beim Mengen-Update

Dashboard:
- Einmalprodukte-Card + Team-Abo-KPI-Card, News-Card responsiv (col-xl-6)

Lokalisierung:
- Sprachdateien en/es/fr vollstaendig mit de synchronisiert (inkl. marketingplan/portal)
- LocalizationSyncTest sichert Key-Gleichheit ab

Tests: AboOneTimeInvoiceGrouping, AboExecutionReminder, AboLockDaysSetting,
AboOnetimeDashboardAndMail, PreviewInvoiceCommand, TeamAboOverview + erweiterte View-Tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:38:11 +00:00
9f996a7f1b Abo: Extern erledigt zaehlt als ausgefuehrte Abo-Lieferung
getCountPaidOrders() zaehlt jetzt bezahlte ODER extern erledigte (Status 8)
Bestellungen als ausgefuehrte Lieferung. "Extern erledigt" = Lieferung
ausgefuehrt und Punkte vergeben, daher muss der Lieferungs-Zaehler hochgehen
(Anzeige "Abo-Lieferungen X / Mindestlaufzeit" sowie canCancelAbo), obwohl
paid=false bleibt (Inkasso laeuft ausserhalb des Systems).

Der Incentive-Zaehler (qualified_abos via IncentiveTracker::trackAboActivated)
lief bereits korrekt; Test ergaenzt diese Absicherung explizit.

Tests: getCountPaidOrders 0 -> 1 nach Erledigen, Incentive qualified_abos = 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:57:29 +00:00
6720d99e08 Admin-Abos: Filterleiste und Textsuche nach PayOne-ID
Neue serverseitige, pro Admin persistente Filter auf /admin/abos:
- Status (echte Abo-Status aus UserAbo::$statusTypes, nur vorhandene Werte)
- Aktiv / Inaktiv
- Ist fuer (Berater/Kunde, is_for me/ot)
- Zahlung (Kreditkarte/PayPal ueber clearingtype cc/wlt)
- Startdatum (Monat + Jahr, optisch gruppiert; whereMonth/whereYear)
- Naechste Ausfuehrung (feste Liefertage 5/10/20/25 ueber abo_interval)
- Berater-Zuordnung (Member-Dropdown, Proportionen wie /admin/business/points)
- Reset-Button (korrigiert: bleibt auf admin_abos, Label ausserhalb des Buttons)

Layout in zwei Zeilen, damit nichts zusammengequetscht wird.

Textsuche der Tabelle: payone_userid via filterColumn suchbar gemacht
(addColumn-Spalten schliesst Yajra sonst von der globalen Suche aus).

Lang-Keys tables.month/year/all/inactive (de/en) ergaenzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 09:54:50 +00:00
4709dfde55 Abo: Bestellung extern als erledigt markieren (ohne Rechnung/Inkasso)
Neue Admin-Funktion fuer angehaltene Abo-Bestellungen, die der Kunde
ausserhalb des Systems geliefert und verrechnet hat.

Aktion (AboExternalCompletionService):
- Punkte gutschreiben wie bei eingegangener Zahlung (SalesPointsVolume,
  idempotent gegen Doppelbuchung)
- UserAboOrder auf neuen gruenen Status 8 "extern erledigt"; paid bleibt false
- Bestellung: shipped=2 (Lieferung bestaetigt), keine Rechnung/kein Lieferschein
- Blockade aufheben: Abo zurueck auf status=2 (okay), aktiv, next_date neu,
  damit der naechste Termin regulaer laeuft
- wie bei regulaerer Ausfuehrung: IncentiveTracker + AboOneTimeService-Purge

Einstiegspunkte: Button + Bestaetigungs-Modal auf der Kunden-Bestelldetailseite
(SalesController) und auf der Abo-Detailseite (Admin\AboController@externDone,
Route admin_abos_extern_done). Status 8 (gruen) in UserAbo/UserAboOrder, Labels
de/en.

Tests: tests/Feature/AboExternalCompletionTest (Happy Path, Idempotenz,
bezahlt-/Nicht-Abo-Guards, Abo-Detail-Einstieg) - 5 gruen.

Zusaetzlich: docs/2026-06-11-test-suite-vorbestehende-fehler.md - Diagnose der
93 vorbestehenden (von master geerbten) Test-Suite-Fehler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 08:11:45 +00:00
ccc6f85809 Payment Phase R: PAYONE-Integration entflechten (R1–R7 + Review)
Strangler-Refactoring der PAYONE-Zahlungslogik in eine testbare
Service-Schicht, ohne Verhalten in Checkout/Cron/IPN zu ändern.

Neue Services (app/Services/Payment/Payone):
- PayonePaymentService (+ PayonePaymentResult-DTO) für CC-/SEPA-Check
  und Abo-Abbuchung
- PayoneRequestBuilder, PaymentMethodResolver, PayoneMethodConfig
- PayoneTransactionRecorder: zentrale PaymentTransaction-Erstellung
  (API-Response, Invoice, Webhook) inkl. PCI-Redaction (cardpan/cardcvc2)
- PayoneWebhookHandler (+ PayoneWebhookResult): IPN/TSOK-Logik aus
  Api\PayoneController; response('TSOK') statt echo/exit
- PayoneTransportException + Handler-Mapping (403) statt abort() im Service

Hotfixes (R1): var_dump/exit & bare exit() entfernt, Log-Redaction.

Security (R-Review):
- PAYONE_KEY_HASH (MD5-Hash, kein Klartext) mit Boot-Hard-Fail; schließt
  zugleich die ''-Webhook-Bypass-Lücke. Artisan-Command payone:hash-key
  (verdeckte Eingabe, optional --write) zur Hash-Erzeugung.
- Webhook: txaction Pflichtfeld, mode-Fallback auf payment->mode.

Pay\PayoneController ist @deprecated-Fassade (614 → 328 Zeilen),
delegiert an Services via Container-Singleton.

Migration: fehlende Spalte shopping_payments.carddata nachgezogen.

Tests: 47 grün (PayonePaymentService, Recorder, Webhook, Resolver,
TransportException, hash-key). End-to-End im Test-System verifiziert
(siehe dev/2026-06-09-payments).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 13:42:45 +00:00
1668e5a879 Abo Einmalprodukte: Phase 4 - Ausfuehrung, Purge & User-Retry
- UserMakeOrder: bestaetigte Einmal-Artikel in den Yard, is_abo_addon
  auf ShoppingOrderItem; amount bleibt reiner Abo-Betrag (Reihenfolge)
- AboOneTimeService::purgeAfterExecution: loescht alle Einmal-Artikel
  und rechnet Comp-Produkte neu - nur im Erfolgszweig (Cron + Retry)
- User-Retry in Sales Center und Portal mit Berechtigungspruefung,
  gemeinsames Confirm-Modal; Admin-Retry unveraendert
- Tests: AboMakeOrderOneTimeTest, AboUserRetryTest; Plan-Doku Phase 4

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 15:32:27 +00:00
8f616ff4de Abo Einmalprodukte: Review-Gate (VIP), Verbindlichkeit & Summen-Layout
- Live-Review-Gate: Einmalprodukte nur fuer VIP im Sales Center sichtbar,
  Portal ausgeblendet (AboHelper::isOneTimeFeatureVisible + Gates in Controllern)
- Nur verbindlich bestaetigte Einmal-Artikel fliessen in die Lieferung;
  Service-Helfer confirmedItems/pendingItems/pendingGross
- Footer-Layout der Einmalprodukt-Liste: bestaetigte Summe + Gesamtbetrag,
  Trennstrich, offener Betrag und neue Gesamtsumme (dunkelgruen)
- Uebersetzungen DE/EN/ES/FR (onetime_new_total u.a.), Tests angepasst/ergaenzt

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 14:59:22 +00:00
5c11d48e6d Abo Einmalprodukte und Bestätigung abschließen
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 15:28:08 +00:00
70f2c6012a 27-05-2026 DHL Modul v2.1 / Optimierung tracking 2026-05-27 18:51:23 +02:00
c4fbb517f6 27-05-2026 Update DHL Modul v2.0 2026-05-27 13:40:38 +00:00
34d8f60bbd User Statistik 2026-05-18 17:23:28 +02:00
013a5de23c 13-05-2026 implementation FR 2026-05-13 17:33:52 +02:00
a639c4f63c Steuerberater Modul tax 2026-05-08 15:34:57 +02:00