Commit graph

78 commits

Author SHA1 Message Date
ae59679150 Datenschutzerklaerung: Cloudflare Turnstile und Missbrauchsprotokoll
- Abschnitt V (DE/EN/ES/FR): neuer Absatz zum Schutz von Kontaktformular
  und Registrierung mit Cloudflare Turnstile (Daten, Zweck, Art. 6 Abs. 1
  lit. f DSGVO, USA-Uebermittlung) sowie Rate-Limit und 30-Tage-Protokoll
  abgewiesener Anfragen
- Stand auf 09.10.2026

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:35:17 +00:00
4c944aa15d Spamschutz Kontakt + Registrierung: Turnstile, Rate-Limit, Honeypot; Passwort min. 8
- Kontaktformular: keine Mail mehr an die eingegebene Adresse (wurde als
  Spam-Versender missbraucht); Anfrage nur an Kontakt-Adresse + Berater (BCC),
  Reply-To = Absender; Eingaben in der Mail escaped
- Kontakt + Registrierung: Cloudflare Turnstile statt reCAPTCHA (Schluessel
  aus dem Code in die .env), Rate-Limits, Honeypot + Mindest-Ausfuellzeit,
  Namensregel (lateinische Schrift, max. 50 Zeichen), Form Requests
- Registrierung: Partner-Nummer nur fuer aktive Berater (vorher ungeprueft)
- CRM: POST /register abgeschaltet (legte ohne Captcha Benutzer an)
- Passwort-Mindestlaenge systemweit 8 Zeichen ueber Password::defaults()
- Log abgefangener Anfragen: storage/logs/form-spam-*.log
- Doku und Protokoll: dev/2026-10-09-spam/README.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:22:02 +00:00
0a3eb791d4 PDF: Seitenzahlen auf allen Seiten, Lieferschein-Beschriftung
- page_script in invoice, delivery, credit und cancellation ans Ende des
  body verschoben (wirkte nur auf Seite 1), Beschriftung uebersetzt
- Gutschriften rendern mit enable_php wie Rechnungen
- Lieferschein: Schluessel pdf.delivery_note_no ohne ueberzaehligen Punkt
- Tests: Position des Skripts, alle pdf.*-Schluessel in allen Sprachen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:06:55 +00:00
4274bff9e2 Composer-Sicherheitsupdates, dompdf 3, email-Regel ohne Zeilenumbrueche
- Patch-/Minor-Updates (guzzle, commonmark, flysystem, excel,
  phpspreadsheet, phpseclib, laravel/framework 11.x)
- barryvdh/laravel-dompdf 3.1 (dompdf 3.1.6), Test fuer den PDF-Weg
  inkl. eingebettetem PHP und Briefpapier
- Rueckport des Laravel-12.60-Fixes gegen CRLF in der email-Regel
- Protokoll: Ergebnis composer audit, offene Punkte Laravel 12 und
  PDF-Seitenzahlen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:50:31 +00:00
47a46b1401 Protokoll: GlitchTip live (v1.3.0), Exception-Mails ab 22.10. abschalten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:35:04 +00:00
0b02b1b15c GlitchTip: Fehlerprotokoll fuer Server, Fehlersignale und Browser
- sentry/sentry-laravel 4.28; config/sentry.php gehaertet (keine PII,
  keine Request-Bodys, before_send ErrorReportScrubber)
- Handler: Integration::captureUnhandledException, CommandNotFoundException
  nicht melden
- ReportErrorSignals (global): toter interner Link (nach Pfad wegen
  Catch-all-Route), 403/429 fuer Angemeldete, 419; gedrosselt
- Browser: selbst gehostetes SDK (esbuild-Bundle 11.5.0), Loader ohne
  Build-Schritt und ohne Regex-Lookbehind, in 7 Layouts mit area-Tag
- phpunit.xml: DSNs und ntfy-URL leer, CACHE_DRIVER=array (Tests schrieben
  bisher in den lokalen Datei-Cache)
- dev/glitch-tip/README.md mit Instanz, Org, Projekt-IDs, API-Auswertung

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:46:36 +00:00
36feed6324 Protokoll: Uptime Kuma prueft /health
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:18:25 +00:00
f20b2ba1c6 Protokoll der Umstellung mit Aufraeum-Liste, Kommentar Execution-Reminder
- dev/betrieb/2026-10-08-protokoll-umstellung.md: Ergebnis, Releases,
  Aufraeumen ab 22.10.2026 (Server, Mac, Git-Server), offene Punkte
- Kernel: abo:send-execution-reminder ist bewusst aktiv (veralteten
  Kommentar entfernt)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:00:49 +00:00
e89cffa7b6 deploy.sh nur auf Live, dev/app-bak entfernt, Anleitung zur bereinigten Historie
- deploy.sh bricht ab, wenn APP_ENV nicht production ist
- dev/app-bak (altes Code-Backup) geloescht
- README Abschnitt H: Historie neu geschrieben

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:42:27 +00:00
c9cd17a41a Alarm-Mail bei fehlgeschlagenen geplanten Aufgaben und Queue-Jobs
- OperationalAlert: Log (Kanal monitoring) immer, Mail an
  MONITORING_ALERT_EMAIL (Standard EXCEPTION_MAIL), optional ntfy;
  gleiche Meldung hoechstens einmal pro Stunde
- Kernel: onFailure fuer alle geplanten Aufgaben (auch kuenftige)
- AppServiceProvider: Queue::failing
- withoutOverlapping mit kurzen Sperrdauern (10-60 Min.) statt 24 h

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:49:18 +00:00
fdbb4f3853 bootstrap/cache nicht mehr versioniert
packages.php und services.php erzeugt Laravel selbst; nach composer install
--no-dev unterscheiden sie sich auf Live vom lokalen Stand (Dev-Pakete) und
blockierten deploy.sh. Anleitung: Fallstricke zu Admin-Uebersetzungen, SFTP
und erzeugten Dateien.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:55:46 +00:00
fec88ee265 Systemstatus im Sysadmin-Bereich und /health fuer externe Ueberwachung
- HealthProbe: Datenbank, Cache, Queue (sync / database / Horizon),
  fehlgeschlagene Jobs (24 h), Scheduler-Herzschlag, Plattenplatz
- /health auf jeder Domain, ohne web-Gruppe, nur mit MONITORING_HEALTH_TOKEN
- monitoring:heartbeat minuetlich im Scheduler
- Sysadmin -> Systemstatus (my.mivita.care/sysadmin/status) mit Version,
  Umgebung und zuletzt fehlgeschlagenen Jobs
- Anleitung Abschnitt G

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:49:14 +00:00
22c85fc746 Betriebsanleitung: Umstellung auf v1.0.1, fileMode und info/exclude
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:23:02 +00:00
21daab259f Horizon fuer Live vorbereitet
- Supervisor bedient alle Queues der Jobs (high-priority, default, dhl-*),
  production mit 3 statt 10 Prozessen
- Redis-Queue mit after_commit: der Stripe-Paid-Pfad plant den
  Rechnungsnummer-Sync innerhalb der Transaktion ein
- /horizon nur fuer System-Admins (admin >= 4)
- horizon:snapshot alle 5 Minuten, queue:prune-failed nach 30 Tagen
- deploy.sh: QUEUE_WORKER (horizon | none), Anleitung Abschnitt F

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:32:26 +00:00
e1c6178bc7 Git-Deploy per Tag: deploy.sh, Version aus RELEASE, Worker-Pruefung
- dev/betrieb/deploy.sh: Tag auschecken, composer, Caches leeren (bewusst
  kein route:/config:cache wegen hostabhaengiger Routen und env()-Aufrufen),
  horizon:terminate, FPM-Reload, Worker-Pruefung am Ende
- dev/betrieb/README.md: Einrichtung Server, Umstellung des Live-Verzeichnisses,
  Release, Deploy, Rollback
- config('app.version') aus RELEASE, php artisan about --only=mivita
- monitoring:queue-probe prueft nach dem Deploy, ob Horizon Jobs abholt
- Vorlage dev/git-deploy-monitoring aus Pressekonto

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:11:34 +00:00
05de0f0698 Doku: Protokoll September, Nachtrag Bestelldetail Herkunft
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 12:49:50 +00:00
b8a18b09cf Doku: Protokoll Umsetzungen September, Nachtraege 06.10.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 12:36:06 +00:00
2381bbe3b9 Doku: Kundeninfo Neuerungen September (Stand 25.09.)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 12:08:50 +00:00
f2aa30243f Recruiting Challenge: Firmenkonto ohne Rang und Praemie, Bedingungen Punkt 4 ergaenzt
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>
2026-09-25 11:02:02 +00:00
e390d3b816 Doku: Bestaetigung wirkt rueckwirkend ab 09.10., keine automatische Erinnerung
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 10:53:17 +00:00
4f62ad1850 Doku: Challenge-Regeln 25.09. und Dry-Run-Befund user:cleanup
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 10:44:49 +00:00
979a12d1cb Doku: Deploy-Stand 25.09. markiert, Challenge-Start 9. Oktober
A1-A4 und E1 live seit 23.09., B2, A07, Abo-Warnung und E1-Nachtraege
live seit 25.09., Settings auf 0, Montenegro beendet. Offen: erster
user:cleanup-Lauf, Seeder fuer den Challenge-Start 09.10.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 10:28:27 +00:00
aa52b02a2a Doku: Plaene, Kundenbriefe, Log-Analyse und Ledger-Plan versioniert, Stand 23.09.
- September-Plan: B2, E1 (Gleichstand, Zahlungsdatum), A07 und die
  Abo-Warnung nachgetragen; Audit-Stand A07 erledigt.
- Bisher nur im Arbeitsbaum: Entwicklungsplan und Kundenbrief vom 07.09.
  samt PDF, Tickets vom 08.09., Log-Analyse 26.08., Ledger-Plan,
  August-Anerkennungen. E-Mail-Adressen von Kunden und Beratern durch
  Platzhalter bzw. Namen ersetzt.

Nicht versioniert (personenbezogene Daten): Kundenliste CSV/XLSX und die
Produktions-Logs in dev/2026-08-26-logs/*.log.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:45:10 +00:00
0b8f8abeeb Berater faellt aus (B2): pausieren nach 2 Tagen, Rueckgabe bei Zahlung, harter Cut am 28. Tag
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>
2026-09-23 15:44:45 +00:00
d21d94a060 Newsletter-Listen: Empfaengerlisten filtern, speichern und exportieren
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>
2026-09-23 15:44:09 +00:00
1a3af17f9a DATEV: stornierte Rechnungen im Rechnungsmonat, Pruefliste fuer den Steuerberater
- Eine spaeter stornierte Rechnung fehlte im Export ihres Rechnungsmonats,
  weil cancellation = true auch an der Originalrechnung steht. Sie bleibt
  jetzt als Erloes im Rechnungsmonat; die Stornierung wird im Monat des
  Stornobelegs gegengebucht (nur Belege ohne cancellation_id).
- Pruefliste je Buchungsstapel als CSV (Button im Steuerberater-Bereich):
  Belege, die sich ohne Zusatzinformation nicht abstimmen lassen (doppelte
  Rechnungsnummern, Stornos ausserhalb des Systems).
- Korrespondenz und offene Differenzen mit dem Steuerberater unter
  dev/2026-07-17-steuern/.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:42:54 +00:00
bdef77462d Deploy-Notiz E1: Dateiliste, Archiv aus dem Commit-Stand, Terminal- und Admin-Schritte
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:24:13 +00:00
2f24a35efa Incentive (E1): Titelbild der Challenge, Dashboard-Karte mit einem Button, VIP-Sichtbarkeit auf dem Dashboard getestet
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>
2026-09-22 14:06:15 +00:00
b83b43ef4c Incentive (E1): Teaser und Rangliste zu einer Incentive-Seite zusammengefasst
Eine Seite unter /incentive/{slug}: oben Teilnahme-Block mit Bedingungen
(oder fuer bestaetigte Teilnehmer der Kopf mit Rang, Punkten, Praemie und
Bedingungen zum Aufklappen), darunter Titelbild, Text, Kacheln, "So
funktioniert es", Galerie und das Live-Ranking mit Sprungmarke #ranking.
Die alte URL /incentive/{slug}/ranking leitet dorthin um, Dashboard und
Detailseite verlinken auf die Sprungmarke, show.blade.php entfaellt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 13:11:10 +00:00
f340d0de3d Incentive (E1): Bilder je Incentive, Teaser mit Call to Action oben, Praemie je Teampartner statt Punkte
Titelbild und Galerie liegen jetzt in public/img/incentive/<slug>/, der
Hauptordner ist nur noch Fallback fuers Titelbild. Damit erscheinen die
Montenegro-Impressionen nicht mehr unter der Challenge; die Fotos sind in
montenegro-incentive-2026/ verschoben.

Teaser: Call to Action "Bist du bereit fuer die Challenge?" mit Button ganz
oben. Bei Geldpraemie zeigen die Kacheln "Deine Praemie" (50 EUR je neuem
Teampartner) und "Top 3 Bonus" statt Incentive-Punkte, darunter der Abschnitt
"So funktioniert die Challenge". Rangliste: Button "Jetzt teilnehmen" wieder
in der Sekundaerfarbe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 12:37:58 +00:00
3341c929eb Incentive (E1): "Nur fuer VIP sichtbar" und Ranglisten-Seite mit Teilnahme-Block oben
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>
2026-09-22 11:11:33 +00:00
4e4b58e90e Incentive (E1): Recruiting Challenge mit finalen Texten als Seeder, Abstimmungsvorlage aktualisiert
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>
2026-09-22 10:19:03 +00:00
1f55e2c966 Doku: Inhaltsvorschlag und Abstimmungsfragen zur Recruiting-Challenge (E1)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 12:37:48 +00:00
e3efa2f5f0 Incentive (E1): Recruiting-Challenge mit Geldpraemie, Auszahlungs-Export und parallelen Incentives
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>
2026-09-18 12:10:27 +00:00
03416b0afe Audit 18.09.: Zahlungsdatum nur bei Zahlung, Neupartner-IDs im Snapshot, Pausenende folgt dem Termin
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>
2026-09-18 08:59:45 +00:00
b7e8bbd951 Statistik: Neupartner zaehlen im Monat der Zahlung, nicht der Registrierung
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>
2026-09-16 14:48:01 +00:00
cf0311afc1 Abo (A1+A2): Liefertag frei waehlbar 1-31, Aenderung nur nach vorn, Sperre erst am Ausfuehrungstag
A1: Jeder Monatstag ist als Liefertag erlaubt (UserAbo::allowedDeliveryDays),
Tage 29-31 liefern in kuerzeren Monaten am letzten Tag. Eine Stelle
AboHelper::dateForDayInMonth klammert; setNextDate, Erstausfuehrung,
Admin-Termin und Pause rechnen darueber, der Folgemonat immer aus dem
Liefertag (31.01 -> 28.02 -> 31.03). Liefertag-Aenderung gilt fuer den
laufenden Zyklus und nur nach vorn: ist der Tag im Referenzmonat vorbei,
wird abgelehnt, der Monat verschiebt sich nie (Rueckfrage R1).
Admin-Filter "naechste Ausfuehrung" als Datumsbereich. Prognose-Kacheln
bekommen die Kachel Monatsende, Team-Abos rechnen bis Monatsende.

A2: Sperrfristen Default 0, Setting 0 ist ausdruecklich 0 (fiel bisher auf
den Default zurueck). Bedingung Kalendertage <= Sperre, zentral in
AboHelper::isWithinLock: am Ausfuehrungstag ab 0:00 Uhr fixiert, Vortag bis
23:59 Uhr frei, fuer Liefertag, Produkte, Pause und Kuendigung (R2). Texte
mit _zero-Varianten ueber AboHelper::lockMessage.

Sprachdateien de/en/es/fr, neuer AboDeliveryDayTest, bestehende Tests
angepasst, Plan-Status mit Deploy-Schritt (Settings auf 0).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:28:55 +00:00
d6efe56cd4 Abo (A3): Status und Grund eines Abos sichtbar machen
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>
2026-09-16 10:24:18 +00:00
e8057f9f7b Abo (A4): Abschnitt "Abo pausieren oder kuendigen" mit Kuendigungs-Hinweis
Eigener Abschnitt auf der Abo-Seite (CRM me/ot und Kundenportal) mit
Fortschritt zur Mindestlaufzeit, Pause und Kuendigung. Die Kuendigung laeuft
ueber ein Modal, das die Folgen nennt (Abo endet, Konditionen entfallen, ein
neues Abo hat wieder 6 Pflichtlieferungen), die Pause als Alternative anbietet
und erst nach Bestaetigungs-Checkbox freigibt. Innerhalb der Sperrfrist vor
der Ausfuehrung erscheint statt der Buttons ein Hinweis
(AboHelper::isPauseCancelLocked). Das Einstellungs-Modal verweist nur noch
auf den Abschnitt. Die Kuendigung im Repository ist jetzt wie die Pause eine
eigenstaendige Aktion: Erfolgsmeldung statt Liefertermin-Hinweis, next_date
bleibt unangetastet.

Sprachdateien de/en/es/fr, Tests in AboPauseCancelSectionTest, Plan-Status.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 10:14:51 +00:00
44637d06a7 Team-Statistik: Neu-Badge-Link kannte $selectedTree nicht (ViewException auf Produktion)
Die Closure fuer den Link des gruenen "+N"-Badges band die neue Variable nicht per use ein;
sobald eine Linie neue Abos zaehlte, brach die Uebersicht mit "Undefined variable $selectedTree" ab.
Regressionstest rendert die Uebersicht mit einem neuen Teamabo und prueft den Badge-Link.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 15:40:22 +00:00
96545364f1 Team-Statistik = Team-Struktur (Provisionsbaum, Umschalter) und Laufstand-Fix in user_sales_volumes
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>
2026-09-14 15:31:32 +00:00
685f7a90b9 Kundenabo-Seite: Vorschau taggenau, Liste mit den tatsaechlich gebuchten Punkten
Vorschau und Liste liessen sich nicht gegeneinander nachrechnen — zwei Ursachen,
beide hier behoben.

1. Die Vorschau nahm eine pauschale Monatssumme als Basis. Damit enthielt die
   Kachel zum 5. auch schon die Lieferungen vom 10. Jetzt zaehlt je Termin, was
   bis zu diesem Tag gebucht war, plus die bis dahin offenen Lieferungen.
   Buchungen nach dem letzten Liefertag (Nachzuegler, manuelle Gutschriften)
   haengen sich an die letzte Kachel, damit die den Monatsstand zeigt. Jede
   Kachel weist ihre Aufschluesselung aus ("457 abgerechnet + 131 offen").

   Beraterin 454 im September: 132 / 457 / 588 / 776 statt 0 / 0 / 131 / 319.

2. Die Liste zeigte nur den wiederkehrenden Abo-Inhalt. Einmal-Artikel und
   sonstige Zubuchungen einer Lieferung werden nach der Ausfuehrung aus dem Abo
   entfernt und tauchten deshalb nirgends mehr auf — bei Abo 266 waren das 43
   Punkte, bei Abo 418 rund 39. Neue Spalte "Diesen Monat" mit den je Abo
   tatsaechlich gebuchten Punkten, bei Abweichung mit dem Zusatz "+43 einmalig",
   dazu eine Summenzeile. Ein nicht zuordenbarer Rest (geloeschtes Abo, Buchung
   ohne Ausfuehrungsbezug) wird gesondert ausgewiesen, damit die Addition aufgeht.

   Die Spalte erscheint nur ohne Monatsfilter; fuer einen vergangenen Monat waere
   sie irrefuehrend.

Summe der Monatsspalte = Basis der letzten Kachel; Summe der Abo-Punkte der Abos
mit Termin 20./25. = deren offener Teil.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 08:19:35 +00:00
cb5664a483 Kundenabo-Vorschau: bereits abgerechnete Punkte fehlten in der Basis
Die Kachelreihe "Punkte aus meinen Kundenabos zum 5./10./20./25." startete bei
einer Basis von 0 und addierte nur die noch offenen Ausfuehrungen. Damit standen
die bereits vergangenen Termine auf null, obwohl im Monat laengst Lieferungen
gebucht waren, und die spaeteren Termine zeigten nur den Rest statt des
Monatsstands.

Berater 454 im September: vorher 0 / 0 / 131 / 319, jetzt 457 / 457 / 588 / 776.
Betrifft im September 86 Berater mit zusammen 15.936 Punkten — reine Anzeige,
gebucht und abgerechnet wurde korrekt.

Die Basis ist jetzt die im Monat aus den eigenen Kundenabos abgerechnete
Punktzahl, ueber dieselbe Abgrenzung wie die Backoffice-Statistik
(SalesVolumePoints::customerAboPoints(), Stornos vorzeichengetreu). Damit liest
sich die Kachel wie die Prognose-Kachel auf dem Dashboard: Stand heute plus das,
was bis zum Termin noch hereinkommt. Hinweistext in allen vier Sprachen
nachgezogen.

Nicht aus dem Audit-Paket: Die Null-Basis stammt aus der K9-Umsetzung vom 07.09.,
nicht aus der A13-Korrektur.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 07:36:13 +00:00
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
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
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
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