Abschluss des Legacy-Abbaus. Die Migration in participants/ledger_entries war
laut Dev-DB vollstaendig (keine legacy_mitarbeiter_id, kein legacy_table), die
kl_*-Tabellen enthielten nur noch Golden-Master-Testfixtures. Da kein echter
Altbestand mehr importiert werden muss (Prod entsteht aus der jetzigen Dev-DB),
kommt die Altwelt komplett raus.
Laufzeit-Code (Schritt 3a):
- ledger.php: Option legacy_mitarbeiter_ids, die Spalten aus allen SELECTs und
Rueckgaben, ledger_fetch_participant_summary_by_legacy_id sowie das Loeschen
gespiegelter Legacy-Zeilen in ledger_void_entry/ledger_void_own_self_entry
entfernt
- imports.php, paypal-inbox.php: legacy_mitarbeiter_id aus Ergebnis und
Typannotationen entfernt
- ledger-preview.php: Spalte "Legacy" entfernt
Skripte (Schritt 3b/3c):
- die acht reinen Legacy-/Golden-Master-Skripte entfernt (backfill-*,
seed-golden-master, golden-master-data, check-golden-master, check-m4-*,
check-m3-saas-basis)
- init-mysql-dev.php legt jetzt einen SaaS-Mandanten mit Owner-Login,
Teilnehmer, Journalbuchungen und Hinweis an statt kl_*-Zeilen
Schema (Schritt 4, Migration 0024):
- participants.legacy_mitarbeiter_id + uq_participants_tenant_legacy
- ledger_entries.legacy_table/legacy_id + uq_ledger_entries_tenant_legacy
- DROP der Tabellen kl_Einzahlungen, kl_Kaffeeverbrauch, kl_hinweise,
kl_config, kl_Mitarbeiter
Verifikation unveraendert gruen: http-smoke 33/0, role-matrix 55/0,
tenant-isolation 11/1 (Altfehler), m3-auth/tenant-resolution/billing gruen.
check-m3-settings-flow 7/6 ist vorbestehend (schon bei 3e24910 rot, mit
spaeteren Settings-Feldern nicht synchron) und unabhaengig von diesem Abbau.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die Zugriffspruefung lief bisher in 19 Dateien auf einen Fallback gegen die
Legacy-Tabelle kl_Mitarbeiter (checkKaffeelisteAdmin/-Access). Der Zugang
haengt jetzt ausschliesslich an der SaaS-Anmeldung.
- Fallback-Bloecke in allen 19 Dateien entfernt, ebenso getUserName/getUserId
- functions.php besteht nur noch aus der SaaS-Anmeldung; die per String
zusammengebaute Abfrage "WHERE Email like '<eingabe>'" ist damit weg
- config.php baut keine sqlsrv-Verbindung mehr auf, der Kompatibilitaetslayer
lib/sqlsrv_mysql_compat.php ist verwaist und entfaellt
- Auto-Login per DEV_AUTH_EMAIL gibt es nicht mehr: er meldete jeden Besucher
an und machte damit den Login-Schutz unpruefbar. Angemeldet wird auch lokal
ueber login.php; die Variable dient nur noch scripts/init-mysql-dev.php
http-smoke war an die Golden-Master-Daten des Default-Mandanten gebunden und
hatte deshalb sechs dauerhaft rote Pruefungen. Der Test legt sich jetzt einen
eigenen Mandanten mit bekannten Salden an, meldet sich per HTTP an und raeumt
danach auf. Geschuetzte Seiten werden nicht mehr ueber das Wort "Login" im
Text geprueft, sondern ueber die tatsaechliche 302-Umleitung.
http-smoke 27 PASS / 6 FAIL -> 33 PASS / 0 FAIL
role-matrix 55 PASS / 0 FAIL -> unveraendert
tenant-isolation 9 PASS / 1 FAIL -> 11 PASS / 1 FAIL (Altfehler)
Ausserdem "keine Zugang" -> "keinen Zugang" in zwei Fehlermeldungen; der
Rollentest erkannte die Ablehnung an genau diesem Tippfehler und wurde
mitgezogen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die Migration in participants/ledger_entries ist abgeschlossen: kein
Teilnehmer traegt noch eine legacy_mitarbeiter_id, kein Journaleintrag eine
legacy_table. Damit war der jeweils zweite Zweig ("Default-Mandant schreibt
zusaetzlich nach kl_Einzahlungen/kl_Kaffeeverbrauch/kl_Mitarbeiter") in
sechs Dateien nicht mehr erreichbar - er musste aber bei jeder Aenderung
mitgepflegt werden, zuletzt bei der Bemerkung fuer Einzahlungen.
Entfernt:
- Dual-Write beim Buchen: einzahlung.php, stricheintragen.php, index.php,
jahresauswertung.php, app/imports.php, app/paypal-inbox.php
- Dual-Write in der Mitgliederverwaltung: ledger_create_participant,
ledger_update_participant, ledger_set_participant_active,
ledger_anonymize_participant
- die verwaisten Spiegelfunktionen ledger_mirror_legacy_payment,
ledger_mirror_legacy_consumption und ledger_void_entry_by_legacy_id
Nebenbei behoben: kaffeeliste.php hat die Teilnehmer-Detailseite nur fuer
Mitglieder mit legacy_mitarbeiter_id verlinkt - die hat seit der Migration
niemand mehr, die Seite war also fuer alle unerreichbar. Verlinkt und
adressiert wird jetzt ueber participant_id; "user_id" bleibt als Alias
erhalten. Der Mandanten-Isolationstest prueft jetzt ledger_void_entry, also
den Pfad, den letzteneintraege.php tatsaechlich nutzt.
Pruefskripte unveraendert gegenueber der Baseline vor dem Umbau
(http-smoke 27/6, role-matrix 55/0); die Isolationspruefung steigt von
9 auf 11 PASS bei gleichem vorbestehendem Fehler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Einzahlungen koennen jetzt eine Bemerkung tragen und negativ sein, damit
Abzuege (Auszahlung bei Austritt, Erstattung) nachvollziehbar gebucht werden
koennen.
- ledger_entries.note wurde bisher nur beim Import befuellt und wird nun auch
bei Einzahlungen geschrieben und angezeigt
- Negative Betraege verlangen zwingend eine Bemerkung, sonst ist spaeter nicht
mehr nachvollziehbar, warum jemandem Geld abgezogen wurde
- Plausibilitaetsgrenze von 1000 EUR gegen Groessenordnungs-Tippfehler; bei
einem Fehler in einer Zeile wird gar nichts gebucht und die Eingaben bleiben
stehen
- Legacy-Tabelle kl_Einzahlungen bekommt eine Bemerkung-Spalte, sonst haette
ledger_mirror_legacy_payment die Notiz beim Re-Sync wieder mit NULL
ueberschrieben
- PayPal-Buchungen tragen jetzt Zahler, Datum und Mitteilung als Bemerkung
Ausserdem behoben: letzteneintraege.php hat Einzahlungen und Striche direkt
aus den Legacy-Tabellen gelesen, ohne nach Mandant zu filtern. Jeder
Mandanten-Admin sah damit die Buchungen aus den Legacy-Tabellen, und das
Stornieren lief ueber die nicht mandantengetrennten Legacy-IDs. Die Seite
arbeitet jetzt auf dem mandantengebundenen Journal, und ledger_void_entry
prueft Mandant und Buchungsart, bevor es storniert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mandanten leiten ihre PayPal-Benachrichtigungsmails an ein zentrales
IMAP-Postfach weiter. Die Zuordnung zum Mandanten erfolgt ueber
Plus-Adressierung (zahlungen+<token>@...), der Token wird pro Mandant
erzeugt und im Backend angezeigt.
- Parser fuer PayPal-Eingangsmails, tolerant gegenueber falsch kodierten
Waehrungssymbolen und HTML-Struktur weitergeleiteter Mails
- Gutgeschrieben wird der Nettobetrag, also was tatsaechlich ankam
(bei Waren & Dienstleistungen nach Abzug der PayPal-Gebuehr)
- Deduplizierung ueber den Transaktionscode, damit doppelt weitergeleitete
Mails nicht doppelt buchen
- Eindeutiger Namens-Match bucht automatisch, alles andere landet in einer
Warteschlange zur manuellen Zuordnung
- Absenderpruefung gegen paypal.de/.com, da weitergeleitete Mails keine
gueltige SPF/DKIM-Signatur mehr haben
- IMAP-Zugang ausschliesslich ueber Server-/Env-Einstellungen, nicht pro
Mandant konfigurierbar
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher gab es nur PayPal. Jetzt sind pro Mandant drei unabhaengig
aktivierbare Zahlungswege konfigurierbar (mandant-einstellungen.php):
- Barzahlung mit frei waehlbarem Ansprechpartner.
- PayPal wie bisher, mit Zahlungslink.
- Ueberweisung mit Kontoinhaber und IBAN (Format wird geprueft,
Leerzeichen werden normalisiert).
Das Mitglieder-Dashboard (index.php) zeigt unter "Bezahlen" alle
aktivierten Methoden mit dem jeweils offenen Betrag; die IBAN wird zur
besseren Lesbarkeit in Viererbloecken angezeigt.
Neue tenant_settings-Spalten via Migration 0021 (cash_enabled,
cash_contact, bank_transfer_enabled, bank_account_holder, bank_iban).
billing_stripe_price_id() und der komplette abo-upgrade.php-Ablauf warfen
bisher eine ungefangene RuntimeException (Fatal Error), sobald
STRIPE_SECRET_KEY nicht gesetzt ist (z. B. lokale Dev-Umgebung ohne echte
Stripe-Zugangsdaten) - bestand bereits im alten Code, ist durch die neue
Tarif-Vergleichstabelle mit mehr Buchen-Buttons aber leichter auslösbar
geworden. Jetzt: sauberer 502 'Bezahldienst nicht verfuegbar' statt
Fatal-Error-Seite.
Erstmals lokal per HTTP end-to-end gegen echten Dev-Server + MariaDB
getestet (PHP 8.3 + MariaDB 11.4 als portable Binaries installiert, siehe
docs/dev-mysql.md): eigener Test-Mandant mit 12 aktiven Teilnehmern,
Tarif-Tabelle korrekt gerendert (5 Tarife, Pflicht-Upgrade-Hinweis, Buchen-
Buttons, Enterprise-Anfrage-Link), Buchung von 'free' bei Ueberschreitung
korrekt mit 400 abgelehnt, Buchung eines bezahlten Tarifs ohne Stripe-Key
jetzt sauber mit 502 statt Fatal-Error-Crash. Alle bestehenden
Regressionstests weiterhin gruen: Golden Master (104), Billing-Capacity
(10), M8-Isolation (10), M8-Rollenmatrix (55).
- mandant-einstellungen.php: vollstaendige Tarif-Vergleichstabelle statt nur
erzwungenem Upgrade-Hinweis. Jeder Tarif zeigt Limit, Preis und einen
Aktions-Button (Buchen/Kuendigen/Anfragen); aktueller Tarif markiert,
zu kleine Tarife fuer die aktuelle Teilnehmerzahl deaktiviert.
- abo-upgrade.php: unterscheidet jetzt Upgrade, Downgrade zwischen bezahlten
Stufen (Preis wird in-place gewechselt statt neuer Checkout-Session,
Stripe prorated automatisch) und Wechsel auf 'free' (echte Kuendigung der
bestehenden Subscription), statt nur eine Checkout-Session zu erzeugen.
- app/stripe.php: neue Helper stripe_get_subscription(),
stripe_update_subscription_price(), stripe_cancel_subscription().
- app/billing.php: billing_plan_selectable_for() prueft serverseitig, ob
ein Zieltarif die aktuelle Teilnehmerzahl noch abdeckt (Downgrade-Schutz).
- docs/billing.md: Phase 4 dokumentiert.
Getestet: php -l fuer alle geaenderten Dateien und das gesamte Repo (keine
Syntaxfehler), reine Funktionslogik-Tests ohne DB (billing_plans(),
billing_required_plan_code(), stripe_flatten_params()) gruen. Kein Live-Test
gegen echte Stripe-Testobjekte moeglich (keine PHP/DB-Laufzeitumgebung
verfuegbar) - vor Go-Live im Stripe-Testmodus nachholen.
Bugfixes aus dem Code-Review:
- XSS: unescaptes $_SERVER['PHP_SELF'] in csvupload.php und
letzteneintraege.php durch feste Seitennamen ersetzt.
- Stripe-Webhook: Event-Deduplizierung (neue Tabelle stripe_webhook_events)
gegen doppelte Verarbeitung/Dolibarr-Rechnungen bei Retry-Zustellung.
- Stripe-Webhook: Tarifwechsel aus dem Kundenportal wird lokal nachgezogen
(plan_code aus dem Preis-lookup_key bei subscription.updated).
- Post-Redirect-Get fuer jahresauswertung, mailversenden und die
Selbst-Stricheintragung - verhindert Doppelbuchung/Doppelversand per
Browser-Refresh.
- Jahresbonus: Mails erst nach erfolgreichem Commit; Restcent-Ausgleich
beim letzten Empfaenger, damit die Summe exakt stimmt.
- Verschachtelte HTML-Dokumente in csvupload/einzahlung/stricheintragen
entfernt (Layout kommt aus header.php).
- Rate-Limit fuer den Versand von E-Mail-Verifizierungslinks.
Neue Funktionen:
- Mitglieder koennen ihren zuletzt selbst eingetragenen Strich wieder
stornieren (nur eigene Web-Eintraege, Kassenwart-Eintraege bleiben).
- Monatsuebersicht des eigenen Verbrauchs im Mitglieder-Dashboard.
- Automatische Zahlungserinnerung: opt-in pro Mandant ab der
Warnschwelle, mit Intervall; Cron-Skript scripts/send-payment-reminders.php.
- CSV-Import fuer Mitgliederlisten inkl. herunterladbarer Vorlage
(mitglieder-vorlage.php), Semikolon-/Komma- und BOM-Erkennung.
- Logo als PDF-Wasserzeichen pro Mandant (Upload in den Mandant-
Einstellungen, geschuetzt unter var/tenant_logos/, ersetzt den
Text-Wasserzeichen im Ausdruck).
Robusterer Teilnehmer-Lookup im Dashboard ueber user_id (Fallback E-Mail).
Der PDF-Export war bisher immer zweiseitig (Vieltrinker-/Wenigtrinker)
mit fester Mindestzeilenzahl, unabhängig von der tatsächlichen
Mitgliederzahl. Umgestellt auf:
- Bis 49 aktive Mitglieder: eine Seite ohne Trennung.
- Ab 50 Mitgliedern: weiterhin zwei Seiten (Vorder-/Rückseite).
- Zeilenhöhe richtet sich nach der Mitgliederzahl (16-40px), statt fest
16px für alle.
- Neue Mandanten-Einstellungen: freie Zeilen für neue Mitglieder an/aus,
Trennmodus bei zwei Seiten (Trinkverhalten oder alphabetisch).
Migration 0015 ergänzt die dafür nötigen tenant_settings-Spalten.
Gegen die Dev-DB verifiziert: 8 Mitglieder -> einseitiges PDF,
53 Mitglieder (temporäre Testdaten) -> zweiseitiges PDF, Einstellungen
inkl. Validierung geprüft.
Favicon zeigte noch das echte AOK-Logo; durch ein neutrales
Kaffeetassen-Icon in der Marken-Grünfarbe der Seite ersetzt.
Das Sidebar-Toggle-Icon nutzte ein Font-Awesome-Glyph, dessen CSS nie
eingebunden war und dessen Webfont-Dateien im Repo fehlen - das Icon
konnte dadurch nie gerendert werden. Durch ein reines CSS-Hamburger-
Symbol (drei Balken via box-shadow) ersetzt, das ohne Font-Abhängigkeit
auskommt. Per Puppeteer im mobilen Viewport (375x812) verifiziert:
Sidebar öffnet sich beim Tippen auf das Icon und Linkklicks navigieren
korrekt.
footer.php: Einzahlung/Striche/Mitglieder/Hinweise/Jahresabschluss
waren nur fuer den alten Default-Mandanten-Admin verlinkt, nicht fuer
SaaS-Mandanten-Owner/Admin/Kassenwart - die Seiten selbst erlaubten
Zugriff laengst, waren aber ueber die Navigation nicht erreichbar.
namenanpassen.php: nie auf das neue Mandantenmodell portiert, nutzte
ausschliesslich die globale kl_Mitarbeiter-Tabelle direkt per SQL und
haette fuer jeden Mandanten ausser dem Standard-Mandanten leer
funktioniert. Jetzt ueber participants/saas-Rollen: normale Mitglieder
koennen nur sich selbst umbenennen, Owner/Admin jedes Mitglied des
eigenen Mandanten. Live getestet inkl. Tenant-Isolation (Mitglied kann
nicht den Owner umbenennen).
Sperrt Direktzugriff auf var/ (Session-/Mail-Dateien), database/,
scripts/, docs/, app/, lib/ sowie env.local*.php, .sql und .md-Dateien.
Lokal beim PHP-Dev-Server ohne Wirkung (kein Apache), greift aber auf
echtem Apache-Webhosting.
PHP-FPM/mod_php auf Shared Hosting erbt keine shell-exportierten
Umgebungsvariablen wie der lokale Dev-Server. app/bootstrap.php laedt
jetzt optional env.local.php (nicht eingecheckt, siehe
env.local.example.php als Vorlage) und setzt die Variablen per
putenv(), bevor irgendein app_env()/getenv()-Aufruf passiert. Greift
jetzt auch fuer CLI-Skripte (migrate.php, grant-platform-admin.php) ueber
scripts/dev-db.php.
- index.php zeigt bei abweichendem Host (APP_HOST-Env) die Landingpage
statt des Dashboards, damit kaffeeliste.de und app.kaffeeliste.de aus
demselben Webspace bedient werden koennen. Ohne gesetztes APP_HOST
(lokale Entwicklung) unveraendertes Verhalten.
- landing.php verlinkt Login/Registrierung ueber APP_HOST fest auf die
App-Domain, damit Besucher der Marketingdomain dort landen.
- functionsLDAP.php entfernt (nur ueber die tote IIS/AUTH_USER-Branch
erreichbar, vollstaendig durch app/saas-auth.php ersetzt); zugehoerige
tote AD/LDAP-Variablen aus config.php und der AUTH_USER-Zweig aus
functions.php entfernt.
- mailausgebe.php, umfrage.php, umfrageergebnisse.php entfernt: aus der
Navigation nicht erreichbare Debug-/Einzweck-Seiten ohne echte
Berechtigungspruefung (mailausgebe.php dumpte alle Mitglieder-E-Mails,
umfrageergebnisse.php hatte nur einen auskommentierten Basic-Auth-
Block). scripts/http-smoke.php entsprechend bereinigt.
Statt automatisch hochzustufen, blockiert das Anlegen/Reaktivieren
weiterer aktiver Mitglieder, sobald das Limit des gebuchten Tarifs
erreicht ist (Neuanlage, Bearbeiten mit Statuswechsel, Aktivieren-
Button). mitarbeiterverwalten.php zeigt die aktuelle Auslastung an.
Neuer Regressionstest scripts/check-billing-capacity.php deckt alle
drei Enforcement-Stellen ab.
Bei erfolgreicher Stripe-Zahlung (invoice.paid) wird automatisch ein
Dolibarr-Kunde ermittelt/angelegt und eine validierte Rechnung als
Buchhaltungsspiegel erzeugt. Dolibarr-Fehler blockieren die
Stripe-Webhook-Verarbeitung nicht, sondern landen im Audit-Log zur
manuellen Nachbearbeitung. Getestet gegen die produktive
Dolibarr-Instanz des Kunden (kein Sandbox verfuegbar) mit einem
rechtebeschraenkten API-Key und einem nicht validierten Test-Datensatz.
Kunde hat nur eine produktive Dolibarr-Instanz. Testansatz angepasst:
rechtebeschraenkter API-Key, Test ueber Entwurfs-Rechnungen gegen einen
dedizierten Test-Kunden statt Sandbox-Vorabtest.
app/stripe.php: minimaler REST-Client fuer die Stripe-API auf Basis von
PHP-Streams statt eines vendorten SDKs - diese PHP-Installation hat keine
curl-Extension, Streams sind zudem portabler und brauchen kein
composer.json.
- Fuer jeden bezahlten Tarif per API ein Stripe-Produkt mit monatlichem
Preis angelegt, referenziert ueber einen stabilen lookup_key statt
hartcodierter Price-ID; Erstellung ist idempotent.
- abo-upgrade.php: erstellt eine Stripe-Checkout-Session fuer den
gewaehlten Tarif, tenant_id/plan_code als Metadaten auf Session UND
Subscription (damit spaetere Subscription-Events zuordenbar bleiben).
- abo-portal.php: oeffnet das Stripe Customer Portal fuer bestehende
Kunden (Zahlungsmittel/Kuendigung, ohne eigene UI dafuer).
- stripe-webhook.php: verifiziert die Stripe-Signature per HMAC-SHA256
mit Zeitstempel-Toleranz, verarbeitet checkout.session.completed,
customer.subscription.updated/.deleted, invoice.paid/.payment_failed
und haelt tenant_billing aktuell. Bewusst kein CSRF-/Login-Check
(Stripe ruft unauthentifiziert auf), stattdessen ausschliesslich
Signaturpruefung als Echtheitsnachweis.
- mandant-einstellungen.php: echter "Jetzt upgraden"-Button (Stripe
Checkout) sowie "Zahlungsmethode verwalten/Abo kuendigen" (Customer
Portal), sobald ein Stripe-Kunde existiert.
Live getestet (Stripe-Testmodus, kein echtes Geld): voller Checkout-Flow
bis zum echten Redirect auf checkout.stripe.com, Webhook-Verarbeitung
durch selbst erzeugte, korrekt signierte Test-Events (da diese
Dev-Umgebung keine oeffentlich erreichbare URL fuer echte Stripe-
Zustellung hat) inklusive Ablehnung falscher Signaturen, Customer Portal
mit echtem per API angelegtem Test-Kunden. Alle Regressionstests
weiterhin gruen (36 Seiten HTTP-Smoke, Golden Master, M8-Isolation/
Rollenmatrix).
Noch offen: echten Webhook-Endpunkt auf die Produktions-Domain eintragen,
sobald diese feststeht; Wechsel auf Live-Keys; Dolibarr-Sync (Phase 3).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Vorbereitung fuer Stripe Billing (Zahlungseinzug) + bestehendes Dolibarr
des Kunden (Rechnungsstellung/Buchhaltung) statt eines eigenen Rechnungs-
systems oder eines zusaetzlichen ERP nur fuer Kaffeeliste - Begruendung
in docs/billing.md.
- Neue Tabelle tenant_billing (plan_code, subscription_status, sowie
bereits vorbereitete, noch ungenutzte Felder fuer Stripe/Dolibarr-IDs).
- app/billing.php: billing_plans() als einzige Quelle der Tarifstufen
(synchron mit preise.php), billing_required_plan_code() anhand aktiver
Teilnehmerzahl, billing_check_tenant() vergleicht gebuchten mit
benoetigtem Tarif - rein informativ, kein Enforcement, solange Stripe
nicht angebunden ist.
- Neue Mandanten starten automatisch auf plan_code='free' bei der
Registrierung.
- mandant-einstellungen.php zeigt Owner/Admin ihren aktuellen Tarif und
Teilnehmerstand, mit Hinweis bei Bedarf einer hoeheren Stufe.
- Back-Office zeigt zusaetzlich zur Mandantenliste den gebuchten und
(falls abweichend) den tatsaechlich benoetigten Tarif.
Live getestet: Tarifgrenzen (10/25/50/150) mit einem Mandanten unter und
einem ueber dem Freikontingent geprueft, UI in Mandant-Einstellungen und
Back-Office verifiziert. Alle M8-Isolations-/Rollenmatrix-Tests weiterhin
gruen.
Phase 2 (Stripe) und Phase 3 (Dolibarr-Sync) folgen, sobald Test-Keys
beziehungsweise Sandbox-Zugang vorliegen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
datenschutz.php: Entwurf, der die tatsaechliche technische Verarbeitung
beschreibt (Konto- vs. Teilnehmerdaten, Verantwortlicher/Auftrags-
verarbeiter-Trennung, Rechtsgrundlagen, Aufbewahrungspflichten nach
HGB/AO als Begruendung fuer Anonymisieren-statt-Loeschen, Betroffenen-
rechte mit Verweis auf den Selbstbedienungs-Export). Wie AGB deutlich
als pruefungsbeduerftiger Entwurf gekennzeichnet.
preise.php: neue oeffentliche Preisliste mit den vorgeschlagenen Stufen
(gratis bis 10, 3,99 EUR bis 25, 7,99 EUR bis 50, 12,99 EUR bis 150,
auf Anfrage darueber).
Kleinunternehmer-Korrektur (kein Umsatzsteuerpflichtiger mehr):
- impressum.php: USt-IdNr-Zeile entfernt, Hinweis nach Paragraf 19 UStG ergaenzt.
- agb.php Paragraf 4: keine festen Preise mehr im Text, stattdessen Verweis
auf preise.php; "zzgl. USt" durch Kleinunternehmer-Hinweis ersetzt.
Footer-Links auf allen oeffentlichen und internen Seiten um Datenschutz
und Preise ergaenzt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
impressum.php: Angaben aus https://ctb-it.de/impressum/ uebernommen
(Clemens Creutzburg, Einzelunternehmer, USt-IdNr DE347068189), Anschrift
auf die aktuelle Adresse (In den Sieben Stuecken 9d, 30655 Hannover)
aktualisiert.
agb.php: Entwurf fuer ein B2B-SaaS-Vertragsverhaeltnis (Leistungs-
beschreibung, gestaffelte Preise nach Teilnehmerzahl, Verfuegbarkeit,
AVV-Verweis, Kuendigung, Haftungsbegrenzung), deutlich als Entwurf
gekennzeichnet mit Empfehlung zur anwaltlichen Pruefung vor
Produktivbetrieb - keine rechtssichere Fertigstellung durch mich.
Footer-Links auf Impressum/AGB ergaenzt: alle Public-Seiten (Landing,
Login, Registrierung, Passwort-Reset, E-Mail-Verifizierung) sowie der
App-interne Footer fuer eingeloggte Seiten, damit die Impressumspflicht
(leichte Erreichbarkeit von jeder Seite) erfuellt ist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neue, bewusst von tenant_memberships/saas_user_has_role() komplett
getrennte Platform-Admin-Ebene (neue Tabelle platform_admins), damit die
bestehende, automatisiert getestete Mandanten-Isolation
(check-m8-tenant-isolation.php, check-m8-role-matrix.php) unangetastet
bleibt - Platform-Admin-Rechte wirken ausschliesslich auf den neuen
backoffice-*.php-Seiten.
- backoffice.php: Uebersicht aller Mandanten (Status, Teilnehmerzahl,
Saldensumme).
- backoffice-mandant.php: reine Leseansicht eines Mandanten (Einstellungen,
Mitglieder/Rollen, letzte Buchungen, letzte Admin-Aktionen). Bewusst
kein Schreibzugriff von hier aus.
- backoffice-export.php: nutzt dieselbe app_export_tenant_data() wie der
Selbstbedienungs-Export, ausgeloest durch den Platform-Admin fuer
beliebige Mandanten.
- scripts/grant-platform-admin.php: CLI-only Bootstrap fuer den ersten
Platform-Admin, bewusst keine Web-UI dafuer.
- Jede Back-Office-Ansicht/-Export wird im Audit-Log DES BETROFFENEN
MANDANTEN protokolliert (Transparenzpflicht), nicht nur beim Betreiber.
Der bestehende Selbstbedienungs-Export (datenexport.php aus M8) bleibt
zusaetzlich bestehen statt ersetzt zu werden: der Mandant ist im AV-
Verhaeltnis Verantwortlicher, Art. 15/20/28 DSGVO verpflichten den
Auftragsverarbeiter zur Unterstuetzung bei Ausk''unfts-/Portabilitaets-
rechten - ein jederzeit verfuegbarer Mandanten-Export erfuellt das direkt.
Details und Begruendung in docs/backoffice.md.
Live getestet: Back-Office zeigt alle Mandanten korrekt (inkl. echter
Bestandsmandanten), Detail/Export fuer Test-Mandant funktioniert,
Audit-Log korrekt geschrieben. Kritischer Test bestanden: derselbe
Platform-Admin sieht auf normalen Mandanten-Seiten weiterhin nur seinen
eigenen Mandanten; ein eingeloggter Nicht-Platform-Admin bekommt 403.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
faq.php zeigte bislang fuer jeden Mandanten denselben, komplett
AOK-spezifischen Inhalt (Kaffeemaschinen-Bedienung, interner
Ansprechpartner) - fuer andere Kunden unbrauchbar und inhaltlich falsch.
- Neue Tabelle faq_entries (tenant-scoped, Soft-Delete, sort_order).
- Migration uebernimmt die bisherigen AOK-Inhalte einmalig als FAQ des
migrierten Default-Mandanten; andere Mandanten sehen sie nicht.
- Neue Mandanten bekommen bei der Registrierung automatisch eine kurze
generische Starter-FAQ (faq_seed_default_entries), frei editierbar.
- faq.php: lesbar fuer alle angemeldeten Mitglieder eines Mandanten,
Anlegen/Bearbeiten/Entfernen auf owner/admin beschraenkt.
- Landingpage verweist nicht mehr auf faq.php (jetzt interner,
mandantengebundener Inhalt statt oeffentlicher Marketing-Seite).
Live getestet: AOK-Carry-over fuer Default-Mandant, frisch registrierter
Test-Mandant bekam isolierte generische Starter-FAQ, Anlegen mit
HTML-Payload (korrekt escaped), Bearbeiten und Soft-Delete geprueft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ergaenzt die beiden letzten offenen M7-Punkte und schaerft nebenbei die
Copy insgesamt:
- Neue Sektion 'So funktioniert's' mit dem im Plan vorgesehenen
Drei-Schritte-Ablauf (Kaffee nehmen, Strich setzen, bei Bedarf
bezahlen), bisher komplett gefehlt.
- Funktionsuebersicht von drei auf sechs Karten erweitert (Mitglieder/
Rollen, Guthaben/Einzahlungen, PayPal, CSV-Import/-Export,
Mandantenfaehigkeit, Hinweise/Info-Mails) statt der bisherigen,
eher technischen Kurzbeschreibung.
- Demo-Screenshot: bereinigter Screenshot der Gesamtuebersicht mit
Testdaten (keine echten Kundendaten) aus docs/m0/screenshots/ als
assets/images/demo-kaffeeliste.png eingebunden.
- FAQ-Auszug: fuenf produktbezogene Fragen (Einrichtung, Mandanten-
trennung, Rollen, Export/Loeschung, PayPal) direkt auf der
Landingpage. Bewusst nicht die bestehende faq.php excerpted, da die
komplett AOK-spezifisch ist (Kaffeemaschinen-Bedienung, interner
Ansprechpartner) und fuer Interessenten nicht generisch verstaendlich
waere.
- Neue CSS-Klassen fuer Schritte/Demo-Rahmen/FAQ-Grid/Abschluss-CTA in
assets/css/public.css, konsistent mit dem bestehenden Grundton
(gruener Akzent, schlichte Kartenoptik, keine Marketing-Verspieltheit).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/betrieb-backup-monitoring.md: taeglicher mysqldump-Cron-Job mit
Aufbewahrung, Restore-Befehl inklusive Hinweis auf scripts/migrate.php,
vierteljaehrlicher Restore-Test. Produktive PHP-Fehlerkonfiguration
(display_errors aus, log_errors an) und die aktiv zu beobachtenden
Signale ohne dediziertes APM-Tool: audit_log fuer ungewoehnliche
Admin-Aktionen, rate_limit_attempts fuer Brute-Force-Versuche,
outbound_emails.status=failed fuer Mailversand-Probleme.
Damit ist M8 fuer den geplanten Scope abgeschlossen, mit einer bewusst
offenen Ausnahme (Content-Security-Policy, braucht Template-Bereinigung
der bestehenden Inline-Styles).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Zwei getrennte Flows:
- Teilnehmer anonymisieren statt loeschen (ledger_anonymize_participant):
Name/E-Mail/PayPal-Name werden durch einen Platzhalter ersetzt, das
Mitglied deaktiviert und vom Login-Konto getrennt. Buchungshistorie
bleibt fuer die Kassenfuehrung erhalten, konsistent mit dem
Storno-statt-Delete-Prinzip. Default-Mandant spiegelt die
Anonymisierung in kl_Mitarbeiter (Email dort NOT NULL UNIQUE, bekommt
Platzhalter statt NULL). Inhaber kann nicht anonymisiert werden.
- Mandant vollstaendig loeschen (mandant-loeschen.php, nur Inhaber):
erfordert exakte Eingabe des Kundenkuerzels, loescht die tenants-Zeile;
alle tenant-scoped Tabellen kaskadieren per Fremdschluessel. users
bleiben bestehen (koennen zu mehreren Mandanten gehoeren). Der
migrierte Default-Mandant ist ausgenommen, da seine kl_Mitarbeiter-
Historie sonst verwaisen wuerde.
Live getestet: Mitglied mit Buchungshistorie anonymisiert (Historie
blieb erhalten), Mandantenloeschung mit falscher/richtiger Bestaetigung
geprueft, vollstaendiger Cascade-Delete ueber alle tenant-scoped Tabellen
verifiziert, globale users-Zeile bleibt korrekt erhalten.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neue Seite datenexport.php (Owner/Admin) laedt einen vollstaendigen
JSON-Export des eigenen Mandanten herunter: Stammdaten, Einstellungen,
Teilnehmer, Mitglieder mit Rolle, alle Ledger-Buchungen, Hinweise,
CSV-Importe, Mail-Versandlog und Admin-Protokoll. Passwort- und
Token-Hashes werden bewusst nicht exportiert; der Export selbst wird im
Audit-Log protokolliert. Link von konto.php aus.
Live getestet: eigens angelegter Test-Mandant, Export heruntergeladen,
Header und JSON-Struktur geprueft, keine Passwoerter im Export gefunden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
scripts/check-m8-tenant-isolation.php: legt zwei frische, isolierte
Test-Mandanten mit je einem Teilnehmer, einer Ledger-Buchung, einem
Hinweis und einem Audit-Log-Eintrag an und prueft 10 Faelle - Lesezugriffe
(Teilnehmerlisten, Einzelabruf, letzte Buchungen, aktive Hinweise,
Audit-Log) und Schreibzugriffe (Buchung, Zugangsvergabe, Storno) sind
strikt auf den jeweils richtigen Mandanten beschraenkt. Raeumt sich selbst
auf. 10/10 gruen.
scripts/check-m8-role-matrix.php: legt einen Test-Mandanten mit je einem
Nutzer pro Rolle an (owner/admin/treasurer/member/viewer), loggt sich per
echtem HTTP-Request ein (manueller Cookie-Jar ueber file_get_contents, da
diese PHP-Installation keine curl-Extension hat) und prueft alle elf
rollen-geschuetzten Seiten gegen die erwartete Rollenliste. 55/55 gruen
(5 Rollen x 11 Seiten) - bestaetigt, dass die Rollenpruefungen ueberall
konsistent mit dem im Plan dokumentierten Rollenmodell durchgesetzt sind.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Erste drei Bausteine der Haertung:
- Security-Headers (X-Content-Type-Options, X-Frame-Options, Referrer-
Policy, Permissions-Policy, HSTS bei HTTPS) laufen automatisch ueber
app_send_security_headers() am Ende von app/bootstrap.php fuer jede
dynamische Seite; landing.php war als einzige Seite ganz ohne PHP und
bekam einen minimalen Bootstrap-Aufruf. Bewusst kein CSP, da die
bestehenden Templates durchgaengig auf Inline-style-Attribute setzen.
- DB-gestuetzte Rate-Limits (neue Tabelle rate_limit_attempts) fuer
Login (10/15min je E-Mail, 20/15min je IP), Registrierung (5/h je IP)
und Passwort-Reset-Anfrage (5/h je E-Mail, 10/h je IP); bei
ausgereiztem Reset-Limit erscheint dieselbe generische Meldung wie im
Erfolgsfall, um kein Konto-Enumeration-Signal zu geben.
- Zentrales Audit-Log (neue Tabelle audit_log) fuer Mitgliederverwaltung,
Zugangsvergabe/-entzug, Storno, Mandant-Einstellungen, Hinweise,
CSV-Import, Jahresbonus-Verteilung und Live-Mailversand; sichtbar fuer
Owner/Admin auf mandant-einstellungen.php.
Live getestet: Rate-Limit greift nach 10 Fehlversuchen, Audit-Log-Eintrag
mit korrekten Metadaten und Nutzernamen ueber einen isolierten Test-
Mandanten geprueft. Alle Regressionstests weiterhin gruen (26/26 Smoke,
104 Golden-Master-Assertions).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Klaerung auf Nutzerfrage: saas_send_mail() nutzt produktiv PHP mail()
statt eines externen SMTP-Relays, laeuft also automatisch ueber den
Mailserver des Webspace-Anbieters, der fuer die eigene Domain i.d.R.
bereits autorisiert ist (SPF/rDNS). Dokumentiert die dafuer noetigen
Env-Variablen, insbesondere APP_MAIL_FROM, das produktiv nicht auf dem
Platzhalter noreply@kaffeeliste.local stehen bleiben darf.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jahresauswertung.php verband sich bisher mit fest codierten (kaputten)
Zugangsdaten selbst zur Datenbank statt ueber config.php, hatte keine
Zugriffskontrolle und kein CSRF, und verteilte bei jedem Aufruf sofort
einen hart codierten Bonus-Topf (490 Striche a 0,20 Euro) per PHPMailer
(dessen Quelldateien im Repo fehlen) mit AOK-spezifischem Mailtext.
Nach Abstimmung mit dem Kunden als generisches, mandantenfaehiges Feature
neu gebaut statt nur deaktiviert oder rein lesend umgesetzt:
- Admin gibt einen frei waehlbaren Gesamtbetrag ein, das System verteilt
ihn proportional zu den Jahresstrichen auf alle aktiven Mitglieder.
- Standardmaessig aktive Dry-Run-Checkbox zeigt die Verteilung, ohne zu
buchen oder Mails zu verschicken.
- Bestaetigter Lauf bucht ueber dasselbe Zweig-Muster wie ueberall
(Default-Mandant Dual-Write, andere Mandanten ledger_record_payment)
und verschickt personalisierte Mails ueber saas_send_mail(), protokolliert
im outbound_emails-Versandlog.
- Zugriffskontrolle ergaenzt (owner/admin/treasurer + Legacy-Fallback).
- http-smoke.php: jahresauswertung.php jetzt regulaerer Check statt
uebersprungenem unsicherem Aufruf; damit sind keine Seiten mehr
uebersprungen oder als bekannter offener Punkt markiert (26/26 gruen).
Live getestet: Dry-Run mit korrekter proportionaler Verteilung (Summe
ergibt exakt den Gesamtbetrag), Live-Lauf bucht und versendet korrekt,
Testdaten anschliessend vollstaendig entfernt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mailversenden.php hatte keine Zugriffskontrolle, versendete bei jedem
GET-Request sofort echte Mails und nutzte PHPMailer, dessen Quelldateien
im Repo gar nicht vorhanden waren (nur composer.json/Lizenz) - der Aufruf
waere also ohnehin mit Fatal Error abgebrochen. Zusaetzlich waren SMTP-
Host, Absender, PayPal-Link und FAQ-URL fest auf einen Alt-Kunden (AOK)
codiert.
- Ersetzt PHPMailer durch die bestehende saas_send_mail()-Abstraktion aus
M3 (Transport log/mail je nach APP_MAIL_TRANSPORT) statt eine fehlende
Abhaengigkeit nachzuvendoren.
- Neue Tabelle outbound_emails protokolliert jeden Versandversuch:
Mandant, Mitglied, Vorlage, Betreff, Status, Fehler - das Versandlog.
- Formular hat eine standardmaessig aktive Dry-Run-Checkbox; im Dry-Run
wird nur geloggt, saas_send_mail() nicht aufgerufen.
- Mailtext ist jetzt tenant-generisch (Saldo, optionaler PayPal-Link nur
wenn der Mandant PayPal aktiviert hat, eigener Dashboard-Link) statt
hartcodierter Alt-Kunden-Inhalte.
- Zugriffskontrolle ergaenzt (owner/admin/treasurer + Legacy-Fallback).
- http-smoke.php: mailversenden.php ist jetzt reguel</EOF>
exportKaffeeliste.php band config.php direkt ein (keine Bootstrap-Kette)
und hatte ueberhaupt keine Zugriffskontrolle. TCPDF war im Repo nur
teilweise vorhanden (include/-Verzeichnis und Font-Definitionen fehlten
komplett), wodurch der Export bislang immer mit einem Fatal Error abbrach.
- TCPDF/include/ und die 14 PDF-Standard-Fonts (Helvetica, Courier, Times,
Symbol, ZapfDingbats) aus dem offiziellen TCPDF-6.6.2-Release nachvendort
(nicht die vollen ~25 MB an Unicode-Fonts, die hier nicht gebraucht
werden).
- Zugriffskontrolle ergaenzt (owner/admin/treasurer + Legacy-Fallback wie
bei den anderen Treasurer-Seiten).
- Vieltrinker-/Wenigtrinker-Aufteilung liest jetzt tenant-sicher ueber
ledger_fetch_participants_by_window_marks() statt Legacy-SQL direkt
gegen kl_Kaffeeverbrauch; Preis pro Strich kommt aus tenant_settings
statt kl_config. N+1-Abfragen pro Zeile durch die bereits geladene
Teilnehmerzusammenfassung ersetzt.
- scripts/http-smoke.php prueft den Export jetzt als regulaeren Check
(gueltige PDF-Antwort) statt als bekannten offenen Punkt.
- Live getestet: gueltiges zweiseitiges PDF, Namen/Salden im Textstream
verifiziert, Aufteilung stimmt mit Ledger-Daten ueberein.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
csvupload.php verarbeitete CSV-Zeilen bisher sofort beim Upload, ohne
Vorschau, ohne Zugriffskontrolle (nur CSRF) und schrieb ausschliesslich in
die global unscoped kl_Einzahlungen-Tabelle.
- Neue Tabellen payment_import_batches/payment_import_rows protokollieren
jeden Import: Datei, Pruefsumme, jede Zeile mit Rohwerten, erkanntem
Mitglied, Status und erzeugter Ledger-Zeile.
- Zweistufiger Ablauf: Hochladen zeigt nur eine Vorschau (matched/
duplicate/unmatched/invalid), erst "Import bestaetigen" bucht.
- Zuordnung per paypal_name oder display_name (tenant-scoped), Dubletten-
pruefung gegen bestehende nicht-stornierte Ledger-Zahlungen.
- Buchung folgt dem etablierten Zweig-Muster: Default-Mandant per
Dual-Write nach kl_Einzahlungen plus Spiegelung, alle anderen Mandanten
direkt ueber ledger_record_payment().
- Zugriffskontrolle ergaenzt (owner/admin/treasurer + Legacy-Fallback).
- Live getestet: Treffer, unbekannter Name, ungueltiger Betrag korrekt
klassifiziert; Import bestaetigt mit korrektem Dual-Write; erneuter
Upload derselben Datei erkennt die Zeile korrekt als Dublette.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
stricheintragen.php und einzahlung.php lasen ihren Mitarbeiter-Picker
bisher direkt aus der global unscoped kl_Mitarbeiter-Tabelle. Fuer jeden
Mandanten ausser dem Default-Mandanten zeigte das fremde Namen und
Schreiben schlug (sicher, aber unverstaendlich) am Tenant-Check in
ledger_mirror_legacy_* fehl.
- Picker kommt jetzt aus participants (tenant-scoped), Formularfelder
nutzen participant_id statt MitarbeiterID.
- Schreibpfad pro Teilnehmer: mit legacy_mitarbeiter_id (Default-Mandant)
weiterhin Dual-Write nach kl_Kaffeeverbrauch/kl_Einzahlungen plus
Ledger-Spiegelung; ohne Legacy-Verknuepfung (jeder andere Mandant) direkt
ueber neue ledger_record_consumption()/ledger_record_payment().
- Vorderseite/Rueckseite-Filter (100-Tage-Regel) bleiben fuer den
Default-Mandanten exakt auf der bisherigen Legacy-Logik; andere
Mandanten nutzen die neue ledger_fetch_participants_by_window_marks()
mit tenant_settings.sheet_window_days.
- Nebenbei behoben: einzahlung.php verlinkte auf ?aktion=... statt
?action=..., wodurch die Vorderseite/Rueckseite-Buttons nie griffen.
Preis-pro-Strich-Vorbelegung kommt jetzt aus tenant_settings statt der
seit M3 nicht mehr gepflegten kl_config-Tabelle.
- Live getestet: isolierter Test-Mandant, Picker zeigt nur eigene
Teilnehmer, Buchungen rein Ledger-nativ mit korrektem Saldo, 10-Striche-
Schwelle korrekt sortiert, Default-Mandant-Dual-Write weiterhin gruen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Hinweise:
- Neue Tabelle notices (tenant-scoped, Soft-Delete via deleted_at) loest
die global unscoped kl_hinweise als aktive Datenquelle ab; Migration
uebernimmt einmalig aktuell gueltige kl_hinweise-Eintraege fuer den
Default-Mandanten. kl_hinweise bleibt als Golden-Master-Referenz stehen.
- hinweise.php und der Banner in header.php sind tenant-scoped umgestellt.
Mitgliederverwaltung:
- mitarbeiterverwalten.php verwaltet jetzt participants (tenant-scoped)
statt der global unscoped kl_Mitarbeiter-Tabelle als primaere Quelle.
Das behebt nebenbei ein Mandanten-Datenleck: jeder SaaS-Mandant mit
Owner/Admin-Rolle haette zuvor die komplette Default-Mandanten-
Mitgliederliste sehen und bearbeiten koennen.
- Fuer den Default-Mandanten bleibt Dual-Write nach kl_Mitarbeiter
bestehen, damit stricheintragen.php/einzahlung.php weiter funktionieren;
andere Mandanten werden rein participant-nativ verwaltet.
- Die Legacy-Administrator-Checkbox ist raus. Stattdessen kann ein Admin
je Mitglied unabhaengig von Name/E-Mail einen Login-Zugang mit Rolle
(member/treasurer/admin) gewaehren oder entziehen
(saas_grant_participant_access / saas_revoke_participant_access).
Einladung laeuft ueber den bestehenden Passwort-Reset-Mechanismus,
Entzug setzt die Mitgliedschaft auf revoked statt sie zu loeschen.
- Kompletter Flow live getestet: anlegen, Zugang gewaehren, Einladungsmail,
Passwort setzen, Login, Rollenschutz, Zugang entziehen, Login-Sperre.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Uebersichtstabelle und M5-Abschnitt markierten Mitgliederverwaltung,
Storno-Korrekturen und die Sammelerfassung noch als offen, obwohl sie
in dieser Session umgesetzt wurden. Ausserdem dokumentiert: der PayPal-
Bereich ist bewusst im Dashboard gebuendelt statt als eigene Route, wie
urspruenglich in der Zielarchitektur skizziert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- mitarbeiterverwalten.php war rein ueber Legacy-Admin gesperrt und haette
neue SaaS-Mandanten ausgeschlossen; jetzt Rollen-/Legacy-Fallback wie in
kaffeeliste.php (owner/admin).
- Anlegen/Bearbeiten/Aktivieren/Deaktivieren spiegeln transaktional nach
participants (ledger_mirror_legacy_participant neu ergaenzt), damit
Ledger-Ansichten sofort den aktuellen Mitgliederstand zeigen.
- Gespeicherte XSS-Luecke behoben: Name/E-Mail waren in Formular und Liste
ungeschuetzt ausgegeben, jetzt ueber saas_html().
- Admin-Rollenvergabe bleibt bewusst Legacy-only (kein automatischer
Login-Account ohne Einladungsflow); dokumentiert als offener Punkt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- stricheintragen.php und einzahlung.php erhielten bisher gar keine
Zugriffskontrolle (nur CSRF), jetzt Rollen-/Legacy-Fallback wie in
kaffeeliste.php; Sammeleintraege spiegeln transaktional ins Ledger
(ledger_mirror_legacy_payment neu ergaenzt).
- letzteneintraege.php war rein ueber Legacy-Admin gesperrt und haette
neue SaaS-Mandanten ausgeschlossen; Loeschen markiert den gespiegelten
Ledger-Eintrag jetzt per voided_at statt ihn zu entfernen
(ledger_void_entry_by_legacy_id).
- check-m4-ledger-migration.php an das Storno-Modell angepasst: verwaiste
Ledger-Zeilen sind nur noch ein Fehler, wenn sie nicht voided sind.
- Nebenbei: PHP-Warning bei Sammelerfassung behoben, toten Code entfernt,
veraltete README (verwies auf nicht existierende saas-app/-Struktur)
korrigiert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- neue ledger-preview.php mit Tenant-Summen und letzten Ledger-Buchungen bauen
- Admin-Navigation und HTTP-Smoke um Ledger-Preview erweitern
- M4- und Meilenstein-Doku auf Preview-Stand aktualisieren
- gemeinsames Public-CSS fuer Landingpage und Auth-Seiten ergaenzen
- Login, Registrierung und Passwort-/E-Mail-Seiten vom App-Layout trennen
- App-Sidebar von Public-Login- und Registrierungslinks bereinigen
- Webspace- und Subdomain-Strategie dokumentieren
- Mail-Transport fuer Reset- und Verifizierungslinks ergaenzen
- Public-Landingpage mit Hero-Asset und App-CTAs bauen
- Mail-Flow-Test, Smoke-Abdeckung und SaaS-Doku aktualisieren
- Auth-Token-Tabelle mit gehashten Single-Use-Tokens einführen
- Passwort-Reset und E-Mail-Verifikation als Dev-Flow bauen
- Token-Flow-Test, Smoke-Abdeckung und M3-Dokumentation aktualisieren
- M2-Abschlussstatus und Abschlusskriterien dokumentieren
- M3-SaaS-Basisplan mit Tenant/User/Participant-Modell ergänzen
- Hauptplan mit M2-Abschluss und M3-Startpunkten aktualisieren
- Upload-Formular mit CSRF-Token absichern
- CSV-Dateien temporär unter var/uploads speichern und nach Import löschen
- Dateityp, Dateigröße und Dateiendung prüfen
- CSV-Ausgabe escapen und PayPal-Name-Lookup korrigieren
- M2- und Sicherheitsdokumentation aktualisieren
- Löschaktionen in letzteneintraege.php mit CSRF-Token absichern
- POST ohne Token für Einzahlungen und Strich-Einträge blockieren
- M2- und Sicherheitsdokumentation zum CSRF-Rollout aktualisieren