Commit Graph
67 Commits
Author SHA1 Message Date
clemens f0869d8e33 Anpassung optik 2026-07-19 22:51:51 +02:00
clemens 5e6262b6a8 Kaffeeliste-Ausdruck: adaptives Layout nach Mitgliederzahl
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.
2026-07-19 22:42:12 +02:00
clemens 7630a35bf4 Anpassung der Infos auf der Startseite 2026-07-19 22:28:53 +02:00
clemens a02fd3e4a4 Favicon (AOK-Logo) ersetzen und Hamburger-Menü auf Mobile reparieren
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.
2026-07-19 10:37:14 +02:00
clemens a74a7f5ee8 Anpassung htaccess 2026-07-17 12:53:09 +02:00
clemens e40955fffe Zwei Mehrmandanten-Bugs vor Go-Live behoben
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).
2026-07-17 11:48:45 +02:00
clemens 0416504d6a .htaccess ergaenzen: interne Verzeichnisse/Configdateien nicht oeffentlich
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.
2026-07-17 11:41:24 +02:00
clemens b6199623c5 Produktions-Env-Loading fuer echtes Webhosting ergaenzen
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.
2026-07-17 11:40:08 +02:00
clemens cb799c7c66 Deployment-Vorbereitung: Domain-Split und Legacy-/Debug-Seiten entfernt
- 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.
2026-07-17 11:37:41 +02:00
clemens d2330c81cd Harte Teilnehmer-Obergrenze je Tarif statt automatischem Stufenwechsel
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.
2026-07-17 11:19:00 +02:00
clemens 73d599f85b Billing Phase 3: Dolibarr-Rechnungssynchronisation
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.
2026-07-17 10:35:01 +02:00
clemens 80da042cae Phase 3 Plan aktualisiert: kein Dolibarr-Sandbox verfuegbar
Kunde hat nur eine produktive Dolibarr-Instanz. Testansatz angepasst:
rechtebeschraenkter API-Key, Test ueber Entwurfs-Rechnungen gegen einen
dedizierten Test-Kunden statt Sandbox-Vorabtest.
2026-07-17 00:14:01 +02:00
clemensandClaude Sonnet 5 d642814fb5 Billing Phase 2: Stripe Checkout, Kundenportal und Webhook-Verarbeitung
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>
2026-07-16 23:56:48 +02:00
clemensandClaude Sonnet 5 ade1fd0ed9 Billing Phase 1: Tarifstatus-Datenmodell und Tariflogik je Mandant
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>
2026-07-16 23:38:28 +02:00
clemensandClaude Sonnet 5 d08621b616 Datenschutzerklaerung und Preisliste ergaenzen, Kleinunternehmer-Status korrigieren
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>
2026-07-16 23:12:25 +02:00
clemensandClaude Sonnet 5 2e44bb208d Impressum und AGB-Entwurf ergaenzen, von allen Seiten erreichbar
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>
2026-07-16 22:23:28 +02:00
clemensandClaude Sonnet 5 cbe6547f3a Back-Office: globaler Platform-Admin-Zugang ueber alle Mandanten
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>
2026-07-16 22:18:48 +02:00
clemensandClaude Sonnet 5 8b0dcd2c70 FAQ pro Mandant statt fest codiertem AOK-Inhalt
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>
2026-07-15 23:18:09 +02:00
clemensandClaude Sonnet 5 d176619e1d M7 abschliessen: Landingpage mit Kernabläufen, Demo-Screenshot und FAQ
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>
2026-07-15 22:04:15 +02:00
clemensandClaude Sonnet 5 3fc65812ef M8: Backup/Restore-Prozess und Monitoring dokumentiert
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>
2026-07-15 20:19:20 +02:00
clemensandClaude Sonnet 5 f60c0bb85e M8: Loesch-/Anonymisierungsprozess fuer Teilnehmer und Mandanten
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>
2026-07-15 20:17:57 +02:00
clemensandClaude Sonnet 5 f3045dbab9 M8: Datenexport pro Mandant
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>
2026-07-15 20:10:30 +02:00
clemensandClaude Sonnet 5 3917a11db0 M8: Mandanten-Isolation und Rollenmatrix automatisiert testen
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>
2026-07-15 20:07:10 +02:00
clemensandClaude Sonnet 5 54217d2acb M8: Security-Headers, Rate-Limits und Audit-Log
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>
2026-07-15 18:04:14 +02:00
clemensandClaude Sonnet 5 7b517bcbf6 Doku: Mailversand im Produktivbetrieb ueber Webspace-Mailserver
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>
2026-07-15 17:49:49 +02:00
clemensandClaude Sonnet 5 536ef2ead2 M6: Jahresabschluss als generisches Feature statt AOK-spezifischem Bonus-Skript
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>
2026-07-15 17:21:34 +02:00
clemens d5195e7f9f M6: Mailversand als nachvollziehbaren Versandjob mit Dry-Run/Log
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>
2026-07-15 16:13:38 +02:00
clemensandClaude Sonnet 5 81dfff51a0 M6: PDF-Export tenant-nativ und TCPDF vollstaendig vendort
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>
2026-07-15 15:23:16 +02:00
clemensandClaude Sonnet 5 92e124753f M6: CSV-Import mit Vorschau, Dublettenpruefung und Audit-Trail
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>
2026-07-15 14:48:41 +02:00
clemensandClaude Sonnet 5 4d5f32ae6d M5: Sammelerfassung (Striche/Einzahlungen) tenant-nativ machen
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>
2026-07-15 00:57:51 +02:00
clemensandClaude Sonnet 5 aeb687f42e M5: Hinweise auf tenant-scoped Notices umziehen, Mitgliederzugang auf Rollen umstellen
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>
2026-07-15 00:18:55 +02:00
clemensandClaude Sonnet 5 dcc8dc3421 Meilensteinplan: M5-Stand mit tatsaechlichem Fortschritt abgleichen
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>
2026-07-14 23:50:43 +02:00
clemensandClaude Sonnet 5 de3dcee7ec M5: Mitgliederverwaltung tenant- und rollenbasiert umstellen
- 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>
2026-07-14 23:49:33 +02:00
clemensandClaude Sonnet 5 968a55f442 M5 abschliessen: Schreibseiten aufs Ledger umstellen, Storno statt Delete
- 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>
2026-07-14 23:43:39 +02:00
clemens aa9444f076 Dashboard auf Ledger-Lesemodell umstellen 2026-07-14 23:11:53 +02:00
clemens 078f28551d Teilnehmerauswertung auf Ledger-Lesemodell umstellen 2026-07-14 22:25:51 +02:00
clemens 8688cc78db Teilnehmerauswertung auf Ledger-Lesemodell umstellen 2026-07-14 22:21:28 +02:00
clemens 539117b409 Deutsche Umlaute in UI und Dokumentation korrigieren 2026-07-14 22:11:33 +02:00
clemens f9544f24fd M5 Kaffeeliste read-only auf Ledger umstellen
- kaffeeliste.php aus ledger_fetch_participant_summaries lesen lassen
- Legacy-Teilnehmerlinks ueber legacy_mitarbeiter_id erhalten
- Sidebar-Zugriff fuer Legacy-Admin und SaaS-Rollen trennen
- Smoke-Test um konkrete Ledger-Werte und Inaktiv-Ausschluss erweitern
- M5-App-Kern-Doku ergaenzen
2026-07-14 21:32:59 +02:00
clemens 1aa4a4989e M4 Ledger-Preview als read-only App-Seite ergaenzen
- 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
2026-07-14 20:13:44 +02:00
clemens 5824b066f4 Public-Auth-Seiten in Landingpage-Stil integrieren
- 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
2026-07-14 19:01:57 +02:00
clemens 726c5a9508 M4 Ledger-Service fuer erste App-Abfragen ergaenzen
- zentrale Ledger-Abfragen fuer Tenant- und Teilnehmer-Summen bauen
- letzte Buchungen und Einzelteilnehmer ueber app/ledger.php lesbar machen
- Service-Check und M4-Doku ergaenzen
2026-07-13 21:01:37 +02:00
clemens e4b0c81b48 M4 Ledger-Migration fuer Legacy-Buchungen starten
- ledger_entries als tenant-sichere Buchungstabelle ergaenzen
- Legacy-Einzahlungen und Kaffeeverbrauch idempotent spiegeln
- M4-Paritaetscheck und Datenmigrationsdoku ergaenzen
2026-07-13 20:45:28 +02:00
clemens b0e21bbb0a M3 Mailversand und Public-Landingpage vorbereiten
- 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
2026-07-13 20:38:01 +02:00
clemens 9f2e1bdfb1 M3 Tenant-Aufloesung per App-Session ergaenzen
- Mandantenauswahl fuer Benutzer mit mehreren Tenants bauen
- feste Tenant-Domains ohne Wildcard-Abhaengigkeit vorbereiten
- Login, Backfill, Smoke-Checks und M3-Dokumentation aktualisieren
2026-07-13 20:04:20 +02:00
clemens 8ab4a8a9bb M3 Passwort-Reset und E-Mail-Verifikation ergänzen
- 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
2026-07-13 19:26:58 +02:00
clemens 19ff11c21a M3 Mandant-Einstellungen und Rollencheck ergänzen
- zentrale Owner/Admin-Rollenpruefung in SaaS-Auth ergaenzen
- Mandant-Einstellungen fuer Preise, PayPal und Basisdaten bauen
- Settings-Flow-Test, Navigation und M3-Dokumentation aktualisieren
2026-07-12 22:32:51 +02:00
clemens 5baf6542ce M3 Registrierung und Login-Grundlage ergänzen
- SaaS-Auth-Helper und PDO-Datenbankzugriff einführen
- Registrierung, Login, Logout und Kontoansicht ergänzen
- Auth-Migration, Flow-Check und Smoke-Abdeckung aktualisieren
2026-07-12 20:17:42 +02:00
clemens eef0338e31 M3 SaaS-Basis mit Default-Tenant vorbereiten
- Tenant/User/Membership/Participant-Tabellen additiv migrieren
- Default-Tenant-Backfill fuer Legacy-Mitarbeiter und Admins ergaenzen
- M3-Kontrollskript und Plan-Dokumentation aktualisieren
2026-07-12 19:40:28 +02:00
clemens 8511b876f3 M2 abschließen und M3 vorbereiten
- M2-Abschlussstatus und Abschlusskriterien dokumentieren
- M3-SaaS-Basisplan mit Tenant/User/Participant-Modell ergänzen
- Hauptplan mit M2-Abschluss und M3-Startpunkten aktualisieren
2026-07-12 17:50:16 +02:00
clemens 30378683e6 CSV-Upload gegen CSRF und unsichere Ablage härten
- 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
2026-07-12 10:13:56 +02:00
clemens e5fd47ecf6 CSRF-Schutz für letzte Einträge ergänzen
- 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
2026-07-12 09:45:35 +02:00
clemens 981c770ce5 CSRF zweite Anpassung 2026-07-12 09:01:42 +02:00
clemens 444f0a269b CSRF - erste Erichtigung 2026-07-12 08:55:56 +02:00
clemens 174fff6f31 M2 Umsetzung 2026-07-12 01:07:35 +02:00
clemens 66bdd38d54 M1 Abschluss 2026-07-12 00:51:01 +02:00
clemens 84d957e3ec Fehlerverbesserung 2026-07-12 00:40:09 +02:00
clemens d9e70ce353 M0 Check DB Testdata 2026-07-12 00:12:02 +02:00
clemens 2e6d79e896 M0 Einrichtung 2026-07-11 22:24:22 +02:00
clemens 6f99e7246d dokumentation erstellt 2026-07-11 20:10:01 +02:00
clemens 92d94f1c95 renew 2026-07-11 18:21:33 +02:00
clemens cbea23083d design anpassung 2026-06-23 00:06:31 +02:00
clemens 1891ec0a51 weitere Bearibeitung 2026-06-17 16:45:14 +02:00
clemens 06645f1e9c Phase1 Bearbeitung 2026-06-15 18:36:57 +02:00
clemens e4fa714419 Add go-live and go-to-market roadmaps 2026-06-15 17:52:08 +02:00
clemens f1cc15a1f3 Ignore runtime lock files 2026-06-15 17:13:56 +02:00
clemens b08eb93547 Initial Kaffeekasse SaaS restart 2026-06-15 17:13:38 +02:00