diff --git a/app/ledger.php b/app/ledger.php index ea1e76e..7913cb9 100644 --- a/app/ledger.php +++ b/app/ledger.php @@ -101,7 +101,7 @@ function ledger_fetch_tenant_totals(PDO $pdo, int $tenantId, ?int $year = null): * - participant_ids: list * - email_norms: list * - * @return list + * @return list */ function ledger_fetch_participant_summaries(PDO $pdo, int $tenantId, array $options = []): array { @@ -140,6 +140,7 @@ function ledger_fetch_participant_summaries(PDO $pdo, int $tenantId, array $opti SELECT p.id AS participant_id, p.tenant_id, + p.legacy_mitarbeiter_id, p.display_name, p.email, p.email_norm, @@ -158,7 +159,7 @@ function ledger_fetch_participant_summaries(PDO $pdo, int $tenantId, array $opti AND le.participant_id = p.id AND le.voided_at IS NULL WHERE " . implode(' AND ', $where) . " - GROUP BY p.id, p.tenant_id, p.display_name, p.email, p.email_norm, p.paypal_name, p.active + GROUP BY p.id, p.tenant_id, p.legacy_mitarbeiter_id, p.display_name, p.email, p.email_norm, p.paypal_name, p.active ORDER BY p.display_name, p.id"; $stmt = $pdo->prepare($sql); @@ -169,6 +170,7 @@ function ledger_fetch_participant_summaries(PDO $pdo, int $tenantId, array $opti $summaries[] = [ 'participant_id' => (int)$row['participant_id'], 'tenant_id' => (int)$row['tenant_id'], + 'legacy_mitarbeiter_id' => $row['legacy_mitarbeiter_id'] !== null ? (int)$row['legacy_mitarbeiter_id'] : null, 'display_name' => (string)$row['display_name'], 'email' => $row['email'] !== null ? (string)$row['email'] : null, 'email_norm' => $row['email_norm'] !== null ? (string)$row['email_norm'] : null, @@ -188,7 +190,7 @@ function ledger_fetch_participant_summaries(PDO $pdo, int $tenantId, array $opti } /** - * @return array{participant_id: int, tenant_id: int, display_name: string, email: ?string, email_norm: ?string, paypal_name: ?string, active: bool, total_payments_cents: int, total_costs_cents: int, total_marks: int, balance_cents: int, year_payments_cents: int, year_costs_cents: int, year_marks: int}|null + * @return array{participant_id: int, tenant_id: int, legacy_mitarbeiter_id: ?int, display_name: string, email: ?string, email_norm: ?string, paypal_name: ?string, active: bool, total_payments_cents: int, total_costs_cents: int, total_marks: int, balance_cents: int, year_payments_cents: int, year_costs_cents: int, year_marks: int}|null */ function ledger_fetch_participant_summary(PDO $pdo, int $tenantId, int $participantId, ?int $year = null): ?array { diff --git a/docs/m4-data-migration.md b/docs/m4-data-migration.md index 0001b3a..4c87606 100644 --- a/docs/m4-data-migration.md +++ b/docs/m4-data-migration.md @@ -151,7 +151,7 @@ Installation aus `.local/` verwendet werden. ## Aktueller Pruefstatus - M4 Ledger-Migration: gruen mit 73 Assertions. -- M4 Ledger-Service: gruen mit 102 Assertions. +- M4 Ledger-Service: gruen mit 110 Assertions. - M3 SaaS-Basis: weiterhin gruen. - M3 Tenant-Aufloesung: weiterhin gruen. - M3 Passwort/E-Mail: weiterhin gruen. diff --git a/docs/m5-app-kern.md b/docs/m5-app-kern.md new file mode 100644 index 0000000..e1d6d9b --- /dev/null +++ b/docs/m5-app-kern.md @@ -0,0 +1,52 @@ +# M5 App-Kern + +Stand: 2026-07-14 + +M5 stellt die operativen App-Seiten schrittweise auf das neue tenant-sichere +Modell um. Der Start erfolgt bewusst read-only, damit Summen, Links und +Darstellung gegen die bestehende Legacy-Oberflaeche vergleichbar bleiben. + +## Ziel + +- Kernseiten aus `app/ledger.php` lesen lassen. +- Bestehendes Tabellenlayout und Bediengefuehl erhalten. +- Schreibende Legacy-Flows erst nach stabiler Leseparitaet umstellen. +- Tenant-Kontext serverseitig bestimmen, nicht aus Formularfeldern. + +## Umgesetzter erster Schritt + +Umgesetzte Datei: + +```text +kaffeeliste.php +``` + +Umgesetzter Umfang: + +- Die Gesamtuebersicht liest aktive Teilnehmer aus + `ledger_fetch_participant_summaries()`. +- Angezeigte Werte bleiben fachlich gleich: aktueller Stand, Gesamtausgabe, + Gesamtstriche und Gesamteinzahlungen. +- Links zur bestehenden `teilnehmerauswertung.php` bleiben ueber + `participants.legacy_mitarbeiter_id` erhalten. +- Owner, Admin und Treasurer duerfen die SaaS-Ansicht lesen. +- Der lokale Legacy-/Dev-Admin-Fallback nutzt nur ohne SaaS-Session den + Default-Tenant. +- Die Sidebar zeigt Legacy-Schreibseiten weiterhin nur fuer Legacy-Admins; + Ledger-Leseansichten sind fuer passende SaaS-Rollen sichtbar. +- Export, letzte Eintraege, CSV-Upload und Info-Mail bleiben noch Legacy-Flows. + +## Noch offen + +- `teilnehmerauswertung.php` lesend auf Ledger umstellen. +- `index.php` Dashboard lesend auf Ledger umstellen. +- Schreibseiten `stricheintragen.php` und `einzahlung.php` erst danach mit + Storno-/Reversal-Strategie vorbereiten. +- Export, Mail und Jahresprozesse bleiben M6-Themen. + +## Aktueller Pruefstatus + +- M4 Ledger-Migration: gruen mit 73 Assertions. +- M4 Ledger-Service: gruen mit 110 Assertions. +- Golden Master: gruen mit 104 Assertions. +- HTTP-Smoke: gruen mit 23 geprueften Seiten. diff --git a/docs/saas-umstrukturierungsplan.md b/docs/saas-umstrukturierungsplan.md index ce63b4b..169c1a1 100644 --- a/docs/saas-umstrukturierungsplan.md +++ b/docs/saas-umstrukturierungsplan.md @@ -305,7 +305,7 @@ Uebersicht: | M2 | Technisches Fundament | Abgeschlossen: Migrationen, Bootstrap, Session, CSRF-Helper und Legacy-Schreibseitenschutz stehen | | M3 | SaaS-Basis | Abgeschlossen: Tenants, User, Registrierung, Login, Rollen, Mail-Links und zentrale Mandantenauswahl funktionieren | | M4 | Datenmigration | Gestartet: Ledger-Tabelle, Legacy-Backfill, Paritaetscheck, Ledger-Service und Preview sind umgesetzt | -| M5 | App-Kern | Dashboard, Striche, Einzahlungen, Mitglieder und Liste laufen | +| M5 | App-Kern | Gestartet: Gesamtuebersicht liest read-only aus dem Ledger | | M6 | Betriebsflows | Import, Export, Mail und Jahresprozesse sind auditierbar | | M7 | Landingpage | Public-Seite und Auth-Seiten sind im gemeinsamen Stil nutzbar; spaetere Ausbaustufen folgen | | M8 | Haertung | Betrieb, Datenschutz, Monitoring und Isolation sind geprueft | @@ -509,7 +509,8 @@ Schritte: - Eigene Stricherfassung umsetzen. - Zahlungs-/PayPal-Bereich umsetzen. - Mitgliederverwaltung tenant- und rollenbasiert umsetzen. -- Gesamtuebersicht umsetzen. +- Gesamtuebersicht umsetzen: erster read-only Stand in `kaffeeliste.php` + erledigt. - Letzte Eintraege und Korrekturen als Storno statt Delete umsetzen. - Hinweise als tenant-spezifische Notices umsetzen. @@ -517,6 +518,7 @@ Ergebnis: - Ein Kunde kann seine Kaffeeliste operativ nutzen. - App sieht weiterhin nach bestehender Kaffeeliste aus. +- Erster M5-Stand ist in `docs/m5-app-kern.md` dokumentiert. Abhaengigkeiten: diff --git a/footer.php b/footer.php index 853dcf9..0a2cce2 100644 --- a/footer.php +++ b/footer.php @@ -15,13 +15,31 @@
  • Einzahlung eintragen
  • Striche eintragen
  • + +
  • Kaffeeliste anzeigen
  • Ledger Preview
  • + +
  • Mitglieder verwalten
  • Hinweise verwalten
  • @@ -35,7 +53,6 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){ ?>
  • FAQ Kaffeeliste
  • Kundenkonto
  • diff --git a/kaffeeliste.php b/kaffeeliste.php index 5cf724f..1a3e08d 100644 --- a/kaffeeliste.php +++ b/kaffeeliste.php @@ -1,148 +1,128 @@ prepare('SELECT id, name, slug FROM tenants WHERE slug = ? LIMIT 1'); + $stmt->execute([$tenantSlug]); + $tenant = $stmt->fetch(); + + return $tenant !== false ? $tenant : null; +} + +function kaffeeliste_money(int $cents): string +{ + return saas_format_money_cents($cents) . ' €'; +} + +if ($saasUser !== null && saas_user_has_role(['owner', 'admin', 'treasurer'], $saasUser)) { + $tenantId = (int)$saasUser['tenant_id']; + $hasAccess = true; +} + +if (!$hasAccess && $saasUser === null && checkKaffeelisteAdmin($conn, $mailadress)) { + $tenant = kaffeeliste_default_tenant($pdo); + if ($tenant !== null) { + $tenantId = (int)$tenant['id']; + $hasAccess = true; + } +} + +$participants = $hasAccess + ? ledger_fetch_participant_summaries($pdo, $tenantId, ['active_only' => true]) + : []; + include "header.php"; include "headerline.php"; include "nav.php"; - - - ?> - - - diff --git a/scripts/check-m4-ledger-service.php b/scripts/check-m4-ledger-service.php index 834c4a2..f170d97 100644 --- a/scripts/check-m4-ledger-service.php +++ b/scripts/check-m4-ledger-service.php @@ -106,6 +106,13 @@ foreach ($expected as $email => $expectedSummary) { continue; } + m4_service_assert( + "ledger service {$email} keeps legacy member id", + $summariesByEmail[$email]['legacy_mitarbeiter_id'] !== null, + $failures, + $passes + ); + foreach ($keyMap as $expectedKey => $actualKey) { m4_service_assert_equal( "ledger service {$email} {$expectedKey}", diff --git a/scripts/http-smoke.php b/scripts/http-smoke.php index 8dbbb1e..1a60eec 100644 --- a/scripts/http-smoke.php +++ b/scripts/http-smoke.php @@ -29,7 +29,18 @@ $checks = [ [ 'label' => 'Kaffeeliste', 'path' => 'kaffeeliste.php', - 'contains' => ['Aktive Mitarbeiter'], + 'contains' => [ + 'Aktive Mitarbeiter', + 'teilnehmerauswertung.php?user_id=', + 'GM Negativ', + 'gm-negative@test.local', + '-2,00 €', + 'GM Positiv', + '23,60 €', + 'GM Ohne Buchungen', + '0,00 €', + ], + 'not_contains' => ['GM Inaktiv'], ], [ 'label' => 'Ledger Preview', @@ -221,6 +232,14 @@ foreach ($checks as $check) { } } + foreach (($check['not_contains'] ?? []) as $needle) { + if (str_contains($response['body'], $needle)) { + $failures[] = "{$label}: unexpected text found: {$needle}"; + echo "FAIL {$label}\n"; + continue 2; + } + } + $passes++; echo "PASS {$label}\n"; }