diff --git a/app/ledger.php b/app/ledger.php index 7913cb9..d29ba27 100644 --- a/app/ledger.php +++ b/app/ledger.php @@ -41,6 +41,44 @@ function ledger_current_year(): int return (int)date('Y'); } +function ledger_default_tenant_slug(): string +{ + $tenantSlug = app_env('M4_DEFAULT_TENANT_SLUG'); + if ($tenantSlug === null || trim($tenantSlug) === '') { + $tenantSlug = app_env('M3_DEFAULT_TENANT_SLUG', 'default'); + } + + return trim((string)$tenantSlug) !== '' ? trim((string)$tenantSlug) : 'default'; +} + +/** + * @return array{id: int, name: string, slug: string}|null + */ +function ledger_fetch_tenant_by_slug(PDO $pdo, string $slug): ?array +{ + $stmt = $pdo->prepare('SELECT id, name, slug FROM tenants WHERE slug = ? LIMIT 1'); + $stmt->execute([trim($slug)]); + $tenant = $stmt->fetch(); + + if ($tenant === false) { + return null; + } + + return [ + 'id' => (int)$tenant['id'], + 'name' => (string)$tenant['name'], + 'slug' => (string)$tenant['slug'], + ]; +} + +/** + * @return array{id: int, name: string, slug: string}|null + */ +function ledger_fetch_default_tenant(PDO $pdo): ?array +{ + return ledger_fetch_tenant_by_slug($pdo, ledger_default_tenant_slug()); +} + /** * @return array{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} */ @@ -99,6 +137,7 @@ function ledger_fetch_tenant_totals(PDO $pdo, int $tenantId, ?int $year = null): * - year: int * - active_only: bool|null * - participant_ids: list + * - legacy_mitarbeiter_ids: list * - email_norms: list * * @return list @@ -125,6 +164,17 @@ function ledger_fetch_participant_summaries(PDO $pdo, int $tenantId, array $opti array_push($params, ...$participantIds); } + if (array_key_exists('legacy_mitarbeiter_ids', $options)) { + $legacyIds = is_array($options['legacy_mitarbeiter_ids']) + ? ledger_normalize_ids($options['legacy_mitarbeiter_ids']) + : []; + if ($legacyIds === []) { + return []; + } + $where[] = 'p.legacy_mitarbeiter_id IN (' . implode(',', array_fill(0, count($legacyIds), '?')) . ')'; + array_push($params, ...$legacyIds); + } + if (array_key_exists('email_norms', $options)) { $emailNorms = is_array($options['email_norms']) ? ledger_normalize_email_norms($options['email_norms']) @@ -202,6 +252,19 @@ function ledger_fetch_participant_summary(PDO $pdo, int $tenantId, int $particip return $summaries[0] ?? 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_by_legacy_id(PDO $pdo, int $tenantId, int $legacyMitarbeiterId, ?int $year = null): ?array +{ + $summaries = ledger_fetch_participant_summaries($pdo, $tenantId, [ + 'legacy_mitarbeiter_ids' => [$legacyMitarbeiterId], + 'year' => $year ?? ledger_current_year(), + ]); + + return $summaries[0] ?? null; +} + /** * Options: * - participant_id: int diff --git a/docs/m4-data-migration.md b/docs/m4-data-migration.md index 4e59f23..f8cd617 100644 --- a/docs/m4-data-migration.md +++ b/docs/m4-data-migration.md @@ -151,7 +151,7 @@ Installation aus `.local/` verwendet werden. ## Aktueller Prüfstatus - M4 Ledger-Migration: grün mit 73 Assertions. -- M4 Ledger-Service: grün mit 110 Assertions. +- M4 Ledger-Service: grün mit 115 Assertions. - M3 SaaS-Basis: weiterhin grün. - M3 Tenant-Auflösung: weiterhin grün. - M3 Passwort/E-Mail: weiterhin grün. diff --git a/docs/m5-app-kern.md b/docs/m5-app-kern.md index faf10d4..1660e9a 100644 --- a/docs/m5-app-kern.md +++ b/docs/m5-app-kern.md @@ -13,15 +13,16 @@ Darstellung gegen die bestehende Legacy-Oberfläche vergleichbar bleiben. - Schreibende Legacy-Flows erst nach stabiler Leseparität umstellen. - Tenant-Kontext serverseitig bestimmen, nicht aus Formularfeldern. -## Umgesetzter erster Schritt +## Umgesetzte Schritte -Umgesetzte Datei: +Umgesetzte Dateien: ```text kaffeeliste.php +teilnehmerauswertung.php ``` -Umgesetzter Umfang: +Umgesetzter Umfang `kaffeeliste.php`: - Die Gesamtübersicht liest aktive Teilnehmer aus `ledger_fetch_participant_summaries()`. @@ -36,9 +37,26 @@ Umgesetzter Umfang: Ledger-Leseansichten sind für passende SaaS-Rollen sichtbar. - Export, letzte Einträge, CSV-Upload und Info-Mail bleiben noch Legacy-Flows. +Umgesetzter Umfang `teilnehmerauswertung.php`: + +- Die Detailauswertung liest Teilnehmerdaten über + `ledger_fetch_participant_summary_by_legacy_id()`. +- Die Route bleibt kompatibel zu bestehenden Links: + `teilnehmerauswertung.php?user_id=`. +- Gesamtwerte, Jahresübersicht und letzte Buchungen werden aus + `ledger_entries` geladen. +- PayPal-Anzeige nutzt die neuen Tenant-Settings statt `kl_config`. +- Owner, Admin und Treasurer dürfen die SaaS-Ansicht lesen; der lokale + Legacy-/Dev-Admin-Fallback nutzt weiterhin den Default-Tenant. + +Ergänzte Ledger-Helfer: + +- `ledger_fetch_default_tenant()` +- `ledger_fetch_participant_summary_by_legacy_id()` +- Filter `legacy_mitarbeiter_ids` in `ledger_fetch_participant_summaries()` + ## 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. @@ -47,6 +65,6 @@ Umgesetzter Umfang: ## Aktueller Prüfstatus - M4 Ledger-Migration: grün mit 73 Assertions. -- M4 Ledger-Service: grün mit 110 Assertions. +- M4 Ledger-Service: grün mit 115 Assertions. - Golden Master: grün mit 104 Assertions. - HTTP-Smoke: grün mit 23 geprüften Seiten. diff --git a/docs/saas-umstrukturierungsplan.md b/docs/saas-umstrukturierungsplan.md index c6b2f2c..6301d48 100644 --- a/docs/saas-umstrukturierungsplan.md +++ b/docs/saas-umstrukturierungsplan.md @@ -511,6 +511,8 @@ Schritte: - Mitgliederverwaltung tenant- und rollenbasiert umsetzen. - Gesamtübersicht umsetzen: erster read-only Stand in `kaffeeliste.php` erledigt. +- Teilnehmerauswertung umsetzen: read-only Stand in `teilnehmerauswertung.php` + erledigt. - Letzte Einträge und Korrekturen als Storno statt Delete umsetzen. - Hinweise als tenant-spezifische Notices umsetzen. diff --git a/kaffeeliste.php b/kaffeeliste.php index 1a3e08d..27a1c3f 100644 --- a/kaffeeliste.php +++ b/kaffeeliste.php @@ -8,23 +8,6 @@ $saasUser = saas_current_user($pdo); $tenantId = 0; $hasAccess = false; -function kaffeeliste_default_tenant(PDO $pdo): ?array -{ - $tenantSlug = getenv('M4_DEFAULT_TENANT_SLUG'); - if ($tenantSlug === false || trim($tenantSlug) === '') { - $tenantSlug = getenv('M3_DEFAULT_TENANT_SLUG'); - } - $tenantSlug = $tenantSlug !== false && trim($tenantSlug) !== '' - ? trim($tenantSlug) - : 'default'; - - $stmt = $pdo->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) . ' €'; @@ -36,7 +19,7 @@ if ($saasUser !== null && saas_user_has_role(['owner', 'admin', 'treasurer'], $s } if (!$hasAccess && $saasUser === null && checkKaffeelisteAdmin($conn, $mailadress)) { - $tenant = kaffeeliste_default_tenant($pdo); + $tenant = ledger_fetch_default_tenant($pdo); if ($tenant !== null) { $tenantId = (int)$tenant['id']; $hasAccess = true; diff --git a/scripts/check-m4-ledger-service.php b/scripts/check-m4-ledger-service.php index f170d97..449ec0d 100644 --- a/scripts/check-m4-ledger-service.php +++ b/scripts/check-m4-ledger-service.php @@ -148,6 +148,18 @@ if ($paypalSummary !== null) { m4_service_assert_equal('ledger service single participant balance', 650, $singleSummary['balance_cents'], $failures, $passes); } + $legacySummary = ledger_fetch_participant_summary_by_legacy_id( + $pdo, + $tenantId, + (int)$paypalSummary['legacy_mitarbeiter_id'], + gm_current_year() + ); + m4_service_assert('ledger service legacy participant summary is present', $legacySummary !== null, $failures, $passes); + if ($legacySummary !== null) { + m4_service_assert_equal('ledger service legacy participant id matches', $paypalSummary['participant_id'], $legacySummary['participant_id'], $failures, $passes); + m4_service_assert_equal('ledger service legacy participant balance', 650, $legacySummary['balance_cents'], $failures, $passes); + } + $paypalEntries = ledger_fetch_recent_entries($pdo, $tenantId, [ 'participant_id' => $paypalSummary['participant_id'], 'limit' => 100, @@ -181,6 +193,10 @@ m4_service_assert_equal('ledger service consumption entry count', $legacyConsump m4_service_assert_equal('ledger service empty participant id filter', [], ledger_fetch_participant_summaries($pdo, $tenantId, [ 'participant_ids' => [], ]), $failures, $passes); +m4_service_assert_equal('ledger service empty legacy id filter', [], ledger_fetch_participant_summaries($pdo, $tenantId, [ + 'legacy_mitarbeiter_ids' => [], +]), $failures, $passes); +m4_service_assert_equal('ledger service unknown legacy summary', null, ledger_fetch_participant_summary_by_legacy_id($pdo, $tenantId, 0), $failures, $passes); m4_service_assert_equal('ledger service unknown tenant summaries', [], ledger_fetch_participant_summaries($pdo, 0), $failures, $passes); m4_service_assert_equal('ledger service unknown tenant entries', [], ledger_fetch_recent_entries($pdo, 0), $failures, $passes); m4_service_assert_equal('ledger service unknown tenant totals', ledger_empty_totals(), ledger_fetch_tenant_totals($pdo, 0), $failures, $passes); diff --git a/scripts/http-smoke.php b/scripts/http-smoke.php index 792f276..38b0f5c 100644 --- a/scripts/http-smoke.php +++ b/scripts/http-smoke.php @@ -115,7 +115,7 @@ $checks = [ [ 'label' => 'Teilnehmerauswertung', 'path' => 'teilnehmerauswertung.php?user_id=1', - 'contains' => ['Auswertung', 'Test Admin'], + 'contains' => ['Auswertung', 'Test Admin', 'Jahresübersicht', 'Letzte Einzahlungen'], ], [ 'label' => 'Mailausgabe', diff --git a/teilnehmerauswertung.php b/teilnehmerauswertung.php index c89f5ab..d5f1dfb 100644 --- a/teilnehmerauswertung.php +++ b/teilnehmerauswertung.php @@ -1,213 +1,198 @@ $participant['participant_id'], + 'type' => 'payment', + 'limit' => 100, + ]) + : []; +$consumptionEntries = $participant !== null + ? ledger_fetch_recent_entries($pdo, $tenantId, [ + 'participant_id' => $participant['participant_id'], + 'type' => 'consumption', + 'limit' => 100, + ]) + : []; + +function teilnehmerauswertung_money(int $cents): string +{ + return saas_format_money_cents($cents) . ' €'; +} + +function teilnehmerauswertung_date(string $value): string +{ + try { + return (new DateTimeImmutable($value))->format('d.m.Y'); + } catch (Throwable $e) { + return $value; + } +} + +function teilnehmerauswertung_current_name(?array $saasUser, mixed $conn, string $mailadress): string +{ + $name = trim((string)($saasUser['display_name'] ?? '')); + if ($name !== '') { + return $name; + } + + return trim((string)getUserName($conn, $mailadress)); +} + +function teilnehmerauswertung_paypal_action(string $template, string $amount): string +{ + return trim($template) . $amount; +} + +function teilnehmerauswertung_render_payments(array $entries): void +{ + echo "

Letzte Einzahlungen

"; + echo ""; + if ($entries === []) { + echo ""; + } + foreach ($entries as $entry) { + echo ""; + echo ""; + echo ""; + echo ""; + } + echo "
DatumEinzahlung
Keine Einzahlungen vorhanden.
" . saas_html(teilnehmerauswertung_date((string)$entry['booked_at'])) . "" . saas_html(teilnehmerauswertung_money((int)$entry['amount_cents'])) . "
"; +} + +function teilnehmerauswertung_render_consumption(array $entries): void +{ + echo "

Letzte Striche

"; + echo ""; + if ($entries === []) { + echo ""; + } + foreach ($entries as $entry) { + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + } + echo "
DatumStricheKosten
Keine Striche vorhanden.
" . saas_html(teilnehmerauswertung_date((string)$entry['booked_at'])) . "" . number_format((int)($entry['marks_count'] ?? 0), 0, ',', '.') . "" . saas_html(teilnehmerauswertung_money(abs((int)$entry['amount_cents']))) . "
"; +} + include "header.php"; include "headerline.php"; include "nav.php"; - - ?> - - - - - \ No newline at end of file +