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
This commit is contained in:
+281
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/bootstrap.php';
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
function ledger_normalize_email_norms(array $emails): array
|
||||
{
|
||||
$normalized = [];
|
||||
foreach ($emails as $email) {
|
||||
$emailNorm = strtolower(trim((string)$email));
|
||||
if ($emailNorm !== '') {
|
||||
$normalized[$emailNorm] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return array_keys($normalized);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int>
|
||||
*/
|
||||
function ledger_normalize_ids(array $ids): array
|
||||
{
|
||||
$normalized = [];
|
||||
foreach ($ids as $id) {
|
||||
$id = (int)$id;
|
||||
if ($id > 0) {
|
||||
$normalized[$id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return array_keys($normalized);
|
||||
}
|
||||
|
||||
function ledger_current_year(): int
|
||||
{
|
||||
return (int)date('Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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}
|
||||
*/
|
||||
function ledger_empty_totals(): array
|
||||
{
|
||||
return [
|
||||
'total_payments_cents' => 0,
|
||||
'total_costs_cents' => 0,
|
||||
'total_marks' => 0,
|
||||
'balance_cents' => 0,
|
||||
'year_payments_cents' => 0,
|
||||
'year_costs_cents' => 0,
|
||||
'year_marks' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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}
|
||||
*/
|
||||
function ledger_fetch_tenant_totals(PDO $pdo, int $tenantId, ?int $year = null): array
|
||||
{
|
||||
$year = $year ?? ledger_current_year();
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT
|
||||
COALESCE(SUM(CASE WHEN type = 'payment' THEN amount_cents ELSE 0 END), 0) AS total_payments_cents,
|
||||
COALESCE(SUM(CASE WHEN type = 'consumption' THEN -amount_cents ELSE 0 END), 0) AS total_costs_cents,
|
||||
COALESCE(SUM(CASE WHEN type = 'consumption' THEN marks_count ELSE 0 END), 0) AS total_marks,
|
||||
COALESCE(SUM(amount_cents), 0) AS balance_cents,
|
||||
COALESCE(SUM(CASE WHEN type = 'payment' AND YEAR(booked_at) = ? THEN amount_cents ELSE 0 END), 0) AS year_payments_cents,
|
||||
COALESCE(SUM(CASE WHEN type = 'consumption' AND YEAR(booked_at) = ? THEN -amount_cents ELSE 0 END), 0) AS year_costs_cents,
|
||||
COALESCE(SUM(CASE WHEN type = 'consumption' AND YEAR(booked_at) = ? THEN marks_count ELSE 0 END), 0) AS year_marks
|
||||
FROM ledger_entries
|
||||
WHERE tenant_id = ?
|
||||
AND voided_at IS NULL"
|
||||
);
|
||||
$stmt->execute([$year, $year, $year, $tenantId]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
if ($row === false) {
|
||||
return ledger_empty_totals();
|
||||
}
|
||||
|
||||
return [
|
||||
'total_payments_cents' => (int)$row['total_payments_cents'],
|
||||
'total_costs_cents' => (int)$row['total_costs_cents'],
|
||||
'total_marks' => (int)$row['total_marks'],
|
||||
'balance_cents' => (int)$row['balance_cents'],
|
||||
'year_payments_cents' => (int)$row['year_payments_cents'],
|
||||
'year_costs_cents' => (int)$row['year_costs_cents'],
|
||||
'year_marks' => (int)$row['year_marks'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Options:
|
||||
* - year: int
|
||||
* - active_only: bool|null
|
||||
* - participant_ids: list<int>
|
||||
* - email_norms: list<string>
|
||||
*
|
||||
* @return list<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}>
|
||||
*/
|
||||
function ledger_fetch_participant_summaries(PDO $pdo, int $tenantId, array $options = []): array
|
||||
{
|
||||
$year = isset($options['year']) ? (int)$options['year'] : ledger_current_year();
|
||||
$where = ['p.tenant_id = ?'];
|
||||
$params = [$year, $year, $year, $tenantId];
|
||||
|
||||
if (array_key_exists('active_only', $options) && $options['active_only'] !== null) {
|
||||
$where[] = 'p.active = ?';
|
||||
$params[] = $options['active_only'] ? 1 : 0;
|
||||
}
|
||||
|
||||
if (array_key_exists('participant_ids', $options)) {
|
||||
$participantIds = is_array($options['participant_ids'])
|
||||
? ledger_normalize_ids($options['participant_ids'])
|
||||
: [];
|
||||
if ($participantIds === []) {
|
||||
return [];
|
||||
}
|
||||
$where[] = 'p.id IN (' . implode(',', array_fill(0, count($participantIds), '?')) . ')';
|
||||
array_push($params, ...$participantIds);
|
||||
}
|
||||
|
||||
if (array_key_exists('email_norms', $options)) {
|
||||
$emailNorms = is_array($options['email_norms'])
|
||||
? ledger_normalize_email_norms($options['email_norms'])
|
||||
: [];
|
||||
if ($emailNorms === []) {
|
||||
return [];
|
||||
}
|
||||
$where[] = 'p.email_norm IN (' . implode(',', array_fill(0, count($emailNorms), '?')) . ')';
|
||||
array_push($params, ...$emailNorms);
|
||||
}
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
p.id AS participant_id,
|
||||
p.tenant_id,
|
||||
p.display_name,
|
||||
p.email,
|
||||
p.email_norm,
|
||||
p.paypal_name,
|
||||
p.active,
|
||||
COALESCE(SUM(CASE WHEN le.type = 'payment' THEN le.amount_cents ELSE 0 END), 0) AS total_payments_cents,
|
||||
COALESCE(SUM(CASE WHEN le.type = 'consumption' THEN -le.amount_cents ELSE 0 END), 0) AS total_costs_cents,
|
||||
COALESCE(SUM(CASE WHEN le.type = 'consumption' THEN le.marks_count ELSE 0 END), 0) AS total_marks,
|
||||
COALESCE(SUM(le.amount_cents), 0) AS balance_cents,
|
||||
COALESCE(SUM(CASE WHEN le.type = 'payment' AND YEAR(le.booked_at) = ? THEN le.amount_cents ELSE 0 END), 0) AS year_payments_cents,
|
||||
COALESCE(SUM(CASE WHEN le.type = 'consumption' AND YEAR(le.booked_at) = ? THEN -le.amount_cents ELSE 0 END), 0) AS year_costs_cents,
|
||||
COALESCE(SUM(CASE WHEN le.type = 'consumption' AND YEAR(le.booked_at) = ? THEN le.marks_count ELSE 0 END), 0) AS year_marks
|
||||
FROM participants p
|
||||
LEFT JOIN ledger_entries le
|
||||
ON le.tenant_id = p.tenant_id
|
||||
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
|
||||
ORDER BY p.display_name, p.id";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
$summaries = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$summaries[] = [
|
||||
'participant_id' => (int)$row['participant_id'],
|
||||
'tenant_id' => (int)$row['tenant_id'],
|
||||
'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,
|
||||
'paypal_name' => $row['paypal_name'] !== null ? (string)$row['paypal_name'] : null,
|
||||
'active' => (int)$row['active'] === 1,
|
||||
'total_payments_cents' => (int)$row['total_payments_cents'],
|
||||
'total_costs_cents' => (int)$row['total_costs_cents'],
|
||||
'total_marks' => (int)$row['total_marks'],
|
||||
'balance_cents' => (int)$row['balance_cents'],
|
||||
'year_payments_cents' => (int)$row['year_payments_cents'],
|
||||
'year_costs_cents' => (int)$row['year_costs_cents'],
|
||||
'year_marks' => (int)$row['year_marks'],
|
||||
];
|
||||
}
|
||||
|
||||
return $summaries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
function ledger_fetch_participant_summary(PDO $pdo, int $tenantId, int $participantId, ?int $year = null): ?array
|
||||
{
|
||||
$summaries = ledger_fetch_participant_summaries($pdo, $tenantId, [
|
||||
'participant_ids' => [$participantId],
|
||||
'year' => $year ?? ledger_current_year(),
|
||||
]);
|
||||
|
||||
return $summaries[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options:
|
||||
* - participant_id: int
|
||||
* - type: string
|
||||
* - include_voided: bool
|
||||
* - limit: int
|
||||
*
|
||||
* @return list<array{id: int, tenant_id: int, participant_id: int, display_name: string, type: string, amount_cents: int, marks_count: ?int, unit_price_cents: ?int, booked_at: string, source: string, note: ?string, legacy_table: ?string, legacy_id: ?int, voided_at: ?string}>
|
||||
*/
|
||||
function ledger_fetch_recent_entries(PDO $pdo, int $tenantId, array $options = []): array
|
||||
{
|
||||
$limit = isset($options['limit']) ? (int)$options['limit'] : 100;
|
||||
$limit = max(1, min($limit, 500));
|
||||
$where = ['le.tenant_id = ?'];
|
||||
$params = [$tenantId];
|
||||
|
||||
if (empty($options['include_voided'])) {
|
||||
$where[] = 'le.voided_at IS NULL';
|
||||
}
|
||||
|
||||
if (!empty($options['participant_id'])) {
|
||||
$where[] = 'le.participant_id = ?';
|
||||
$params[] = (int)$options['participant_id'];
|
||||
}
|
||||
|
||||
if (!empty($options['type'])) {
|
||||
$where[] = 'le.type = ?';
|
||||
$params[] = trim((string)$options['type']);
|
||||
}
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
le.id,
|
||||
le.tenant_id,
|
||||
le.participant_id,
|
||||
p.display_name,
|
||||
le.type,
|
||||
le.amount_cents,
|
||||
le.marks_count,
|
||||
le.unit_price_cents,
|
||||
le.booked_at,
|
||||
le.source,
|
||||
le.note,
|
||||
le.legacy_table,
|
||||
le.legacy_id,
|
||||
le.voided_at
|
||||
FROM ledger_entries le
|
||||
JOIN participants p
|
||||
ON p.tenant_id = le.tenant_id
|
||||
AND p.id = le.participant_id
|
||||
WHERE " . implode(' AND ', $where) . "
|
||||
ORDER BY le.booked_at DESC, le.id DESC
|
||||
LIMIT {$limit}";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
$entries = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$entries[] = [
|
||||
'id' => (int)$row['id'],
|
||||
'tenant_id' => (int)$row['tenant_id'],
|
||||
'participant_id' => (int)$row['participant_id'],
|
||||
'display_name' => (string)$row['display_name'],
|
||||
'type' => (string)$row['type'],
|
||||
'amount_cents' => (int)$row['amount_cents'],
|
||||
'marks_count' => $row['marks_count'] !== null ? (int)$row['marks_count'] : null,
|
||||
'unit_price_cents' => $row['unit_price_cents'] !== null ? (int)$row['unit_price_cents'] : null,
|
||||
'booked_at' => (string)$row['booked_at'],
|
||||
'source' => (string)$row['source'],
|
||||
'note' => $row['note'] !== null ? (string)$row['note'] : null,
|
||||
'legacy_table' => $row['legacy_table'] !== null ? (string)$row['legacy_table'] : null,
|
||||
'legacy_id' => $row['legacy_id'] !== null ? (int)$row['legacy_id'] : null,
|
||||
'voided_at' => $row['voided_at'] !== null ? (string)$row['voided_at'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
@@ -5,7 +5,8 @@ Stand: 2026-07-13
|
||||
M4 uebernimmt die finanznahen Legacy-Buchungen additiv in das neue
|
||||
tenant-sichere Ledger-Modell. Die bestehenden Legacy-Seiten lesen und schreiben
|
||||
weiterhin die bisherigen `kl_*`-Tabellen; das Ledger dient zunaechst als
|
||||
vergleichbare, pruefbare Zielstruktur.
|
||||
vergleichbare, pruefbare Zielstruktur. Erste App-Abfragen koennen nun ueber
|
||||
einen zentralen Ledger-Service gegen `ledger_entries` laufen.
|
||||
|
||||
## Ziel
|
||||
|
||||
@@ -40,6 +41,13 @@ Ergaenzende Skripte:
|
||||
```text
|
||||
scripts/backfill-ledger-entries.php
|
||||
scripts/check-m4-ledger-migration.php
|
||||
scripts/check-m4-ledger-service.php
|
||||
```
|
||||
|
||||
Ergaenzende App-Dateien:
|
||||
|
||||
```text
|
||||
app/ledger.php
|
||||
```
|
||||
|
||||
Ausgefuehrter Dev-Stand:
|
||||
@@ -48,6 +56,8 @@ Ausgefuehrter Dev-Stand:
|
||||
- 9 Legacy-Einzahlungen wurden als `payment` gespiegelt.
|
||||
- 12 Legacy-Kaffeeverbrauch-Zeilen wurden als `consumption` gespiegelt.
|
||||
- Ein zweiter Backfill-Lauf blieb idempotent und erzeugte keine Dubletten.
|
||||
- `app/ledger.php` stellt zentrale Abfragen fuer Tenant-Summen,
|
||||
Teilnehmer-Summen, einzelne Teilnehmer und letzte Buchungen bereit.
|
||||
|
||||
## Abbildungsregeln
|
||||
|
||||
@@ -111,6 +121,7 @@ nicht aus den `kl_*`-Tabellen geloescht.
|
||||
php scripts/backfill-default-tenant.php
|
||||
php scripts/backfill-ledger-entries.php
|
||||
php scripts/check-m4-ledger-migration.php
|
||||
php scripts/check-m4-ledger-service.php
|
||||
php scripts/check-golden-master.php
|
||||
```
|
||||
|
||||
@@ -128,10 +139,15 @@ Installation aus `.local/` verwendet werden.
|
||||
- Web-Striche aus `Eintragsart = 2` bleiben als `source = legacy_web`
|
||||
erkennbar: erfuellt.
|
||||
- Ledger-Summen entsprechen dem Golden Master: erfuellt.
|
||||
- Zentrale Ledger-Abfragen liefern dieselben Teilnehmer-Summen wie der Golden
|
||||
Master: erfuellt.
|
||||
- Tenant-Summen, aktive Teilnehmer, Einzelteilnehmer und letzte Buchungen sind
|
||||
ueber `app/ledger.php` abrufbar: erfuellt.
|
||||
|
||||
## Aktueller Pruefstatus
|
||||
|
||||
- M4 Ledger-Migration: gruen mit 73 Assertions.
|
||||
- M4 Ledger-Service: gruen mit 102 Assertions.
|
||||
- M3 SaaS-Basis: weiterhin gruen.
|
||||
- M3 Tenant-Aufloesung: weiterhin gruen.
|
||||
- M3 Passwort/E-Mail: weiterhin gruen.
|
||||
@@ -143,8 +159,8 @@ Installation aus `.local/` verwendet werden.
|
||||
|
||||
## Noch offen in M4
|
||||
|
||||
- Neues Repository/Service fuer Ledger-Abfragen der App-Seiten.
|
||||
- Tenant-sichere Dashboard-, Strich-, Einzahlungs- und Listenqueries auf Basis
|
||||
des Ledgers.
|
||||
- Erste read-only App-Seite oder Preview auf Basis von `app/ledger.php`.
|
||||
- Tenant-sichere Dashboard-, Strich-, Einzahlungs- und Listenqueries schrittweise
|
||||
von Legacy-Tabellen auf das Ledger umstellen.
|
||||
- Storno-/Reversal-Modell in der UI statt harter Deletes.
|
||||
- Delta-Strategie fuer den finalen Cutover.
|
||||
|
||||
@@ -284,7 +284,7 @@ Uebersicht:
|
||||
| M1 | Golden Master | Legacy-Ergebnisse sind als Vergleichsbasis eingefroren; HTTP-Smoke prueft sichere Seiten |
|
||||
| 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 und Paritaetscheck sind umgesetzt |
|
||||
| M4 | Datenmigration | Gestartet: Ledger-Tabelle, Legacy-Backfill, Paritaetscheck und Ledger-Service sind umgesetzt |
|
||||
| M5 | App-Kern | Dashboard, Striche, Einzahlungen, Mitglieder und Liste laufen |
|
||||
| M6 | Betriebsflows | Import, Export, Mail und Jahresprozesse sind auditierbar |
|
||||
| M7 | Landingpage | Erste werbliche Seite ist oeffentlich nutzbar; spaetere Ausbaustufen folgen |
|
||||
@@ -457,12 +457,16 @@ Schritte:
|
||||
spiegeln: erster Backfill erledigt.
|
||||
- Legacy-IDs speichern: erledigt ueber `legacy_table` und `legacy_id`.
|
||||
- Salden gegen Golden-Master vergleichen: Kontrollskript erledigt.
|
||||
- Ledger-Service fuer Tenant-Summen, Teilnehmer-Summen und letzte Buchungen:
|
||||
erledigt.
|
||||
|
||||
Ergebnis:
|
||||
|
||||
- Bestehender Datenbestand kann im neuen Modell abgebildet werden.
|
||||
- Saldenparitaet ist ueber `scripts/check-m4-ledger-migration.php`
|
||||
nachweisbar.
|
||||
- Erste App-Abfragen koennen ueber `app/ledger.php` tenant-sicher aus dem neuen
|
||||
Modell lesen.
|
||||
- Dokumentation: `docs/m4-data-migration.md`.
|
||||
|
||||
Abhaengigkeiten:
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/dev-db.php';
|
||||
require __DIR__ . '/golden-master-data.php';
|
||||
require __DIR__ . '/../app/ledger.php';
|
||||
|
||||
function m4_service_env(string $name, string $default): string
|
||||
{
|
||||
$value = getenv($name);
|
||||
if ($value === false || trim($value) === '') {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return trim($value);
|
||||
}
|
||||
|
||||
function m4_service_assert_equal(string $label, mixed $expected, mixed $actual, array &$failures, int &$passes): void
|
||||
{
|
||||
if ($expected === $actual) {
|
||||
$passes++;
|
||||
echo "PASS {$label}\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$failures[] = "{$label}: expected " . var_export($expected, true) . ', got ' . var_export($actual, true);
|
||||
echo "FAIL {$label}\n";
|
||||
}
|
||||
|
||||
function m4_service_assert(string $label, bool $condition, array &$failures, int &$passes): void
|
||||
{
|
||||
m4_service_assert_equal($label, true, $condition, $failures, $passes);
|
||||
}
|
||||
|
||||
function m4_service_fetch_tenant_id(PDO $pdo, string $slug): int
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT id FROM tenants WHERE slug = ?');
|
||||
$stmt->execute([$slug]);
|
||||
$tenantId = $stmt->fetchColumn();
|
||||
|
||||
if ($tenantId === false) {
|
||||
throw new RuntimeException("Tenant '{$slug}' not found.");
|
||||
}
|
||||
|
||||
return (int)$tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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}
|
||||
*/
|
||||
function m4_service_totals_from_summaries(array $summaries): array
|
||||
{
|
||||
$totals = ledger_empty_totals();
|
||||
|
||||
foreach ($summaries as $summary) {
|
||||
$totals['total_payments_cents'] += (int)$summary['total_payments_cents'];
|
||||
$totals['total_costs_cents'] += (int)$summary['total_costs_cents'];
|
||||
$totals['total_marks'] += (int)$summary['total_marks'];
|
||||
$totals['balance_cents'] += (int)$summary['balance_cents'];
|
||||
$totals['year_payments_cents'] += (int)$summary['year_payments_cents'];
|
||||
$totals['year_costs_cents'] += (int)$summary['year_costs_cents'];
|
||||
$totals['year_marks'] += (int)$summary['year_marks'];
|
||||
}
|
||||
|
||||
return $totals;
|
||||
}
|
||||
|
||||
$pdo = dev_pdo();
|
||||
dev_apply_schema($pdo);
|
||||
|
||||
$tenantSlug = m4_service_env('M4_DEFAULT_TENANT_SLUG', m4_service_env('M3_DEFAULT_TENANT_SLUG', 'default'));
|
||||
$tenantId = m4_service_fetch_tenant_id($pdo, $tenantSlug);
|
||||
$expected = gm_expected_summaries();
|
||||
$emails = array_keys($expected);
|
||||
$failures = [];
|
||||
$passes = 0;
|
||||
|
||||
$summaries = ledger_fetch_participant_summaries($pdo, $tenantId, [
|
||||
'email_norms' => $emails,
|
||||
'year' => gm_current_year(),
|
||||
]);
|
||||
|
||||
$summariesByEmail = [];
|
||||
foreach ($summaries as $summary) {
|
||||
if ($summary['email_norm'] !== null) {
|
||||
$summariesByEmail[$summary['email_norm']] = $summary;
|
||||
}
|
||||
}
|
||||
|
||||
m4_service_assert_equal('ledger service summary count', count($expected), count($summariesByEmail), $failures, $passes);
|
||||
|
||||
$keyMap = [
|
||||
'total_payments' => 'total_payments_cents',
|
||||
'total_costs' => 'total_costs_cents',
|
||||
'total_marks' => 'total_marks',
|
||||
'balance' => 'balance_cents',
|
||||
'year_payments' => 'year_payments_cents',
|
||||
'year_costs' => 'year_costs_cents',
|
||||
'year_marks' => 'year_marks',
|
||||
];
|
||||
|
||||
foreach ($expected as $email => $expectedSummary) {
|
||||
m4_service_assert("ledger service summary exists {$email}", isset($summariesByEmail[$email]), $failures, $passes);
|
||||
if (!isset($summariesByEmail[$email])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($keyMap as $expectedKey => $actualKey) {
|
||||
m4_service_assert_equal(
|
||||
"ledger service {$email} {$expectedKey}",
|
||||
$expectedSummary[$expectedKey],
|
||||
$summariesByEmail[$email][$actualKey],
|
||||
$failures,
|
||||
$passes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$allSummaries = ledger_fetch_participant_summaries($pdo, $tenantId, [
|
||||
'year' => gm_current_year(),
|
||||
]);
|
||||
$tenantTotals = ledger_fetch_tenant_totals($pdo, $tenantId, gm_current_year());
|
||||
foreach (m4_service_totals_from_summaries($allSummaries) as $key => $value) {
|
||||
m4_service_assert_equal("ledger service tenant total {$key}", $value, $tenantTotals[$key], $failures, $passes);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SELECT COUNT(*) FROM participants WHERE tenant_id = ? AND active = 1');
|
||||
$stmt->execute([$tenantId]);
|
||||
$activeExpectedCount = (int)$stmt->fetchColumn();
|
||||
$activeSummaries = ledger_fetch_participant_summaries($pdo, $tenantId, ['active_only' => true]);
|
||||
m4_service_assert_equal('ledger service active participant count', $activeExpectedCount, count($activeSummaries), $failures, $passes);
|
||||
|
||||
$paypalSummary = $summariesByEmail['gm-paypal@test.local'] ?? null;
|
||||
m4_service_assert('ledger service paypal summary is present', $paypalSummary !== null, $failures, $passes);
|
||||
if ($paypalSummary !== null) {
|
||||
$singleSummary = ledger_fetch_participant_summary($pdo, $tenantId, $paypalSummary['participant_id'], gm_current_year());
|
||||
m4_service_assert('ledger service single participant summary is present', $singleSummary !== null, $failures, $passes);
|
||||
if ($singleSummary !== null) {
|
||||
m4_service_assert_equal('ledger service single participant email', 'gm-paypal@test.local', $singleSummary['email_norm'], $failures, $passes);
|
||||
m4_service_assert_equal('ledger service single participant balance', 650, $singleSummary['balance_cents'], $failures, $passes);
|
||||
}
|
||||
|
||||
$paypalEntries = ledger_fetch_recent_entries($pdo, $tenantId, [
|
||||
'participant_id' => $paypalSummary['participant_id'],
|
||||
'limit' => 100,
|
||||
]);
|
||||
m4_service_assert_equal('ledger service paypal entry count', 2, count($paypalEntries), $failures, $passes);
|
||||
foreach ($paypalEntries as $entry) {
|
||||
m4_service_assert_equal('ledger service paypal entry tenant', $tenantId, $entry['tenant_id'], $failures, $passes);
|
||||
m4_service_assert_equal('ledger service paypal entry participant', $paypalSummary['participant_id'], $entry['participant_id'], $failures, $passes);
|
||||
}
|
||||
}
|
||||
|
||||
$recentEntries = ledger_fetch_recent_entries($pdo, $tenantId, ['limit' => 5]);
|
||||
m4_service_assert_equal('ledger service recent entry limit', 5, count($recentEntries), $failures, $passes);
|
||||
$previousBookedAt = null;
|
||||
foreach ($recentEntries as $entry) {
|
||||
m4_service_assert_equal('ledger service recent entry tenant', $tenantId, $entry['tenant_id'], $failures, $passes);
|
||||
m4_service_assert('ledger service recent entry has amount', $entry['amount_cents'] !== 0, $failures, $passes);
|
||||
if ($previousBookedAt !== null) {
|
||||
m4_service_assert('ledger service recent entries are sorted', $previousBookedAt >= $entry['booked_at'], $failures, $passes);
|
||||
}
|
||||
$previousBookedAt = $entry['booked_at'];
|
||||
}
|
||||
|
||||
$legacyConsumptionCount = (int)$pdo->query('SELECT COUNT(*) FROM kl_Kaffeeverbrauch')->fetchColumn();
|
||||
$consumptionEntries = ledger_fetch_recent_entries($pdo, $tenantId, [
|
||||
'type' => 'consumption',
|
||||
'limit' => 100,
|
||||
]);
|
||||
m4_service_assert_equal('ledger service consumption entry count', $legacyConsumptionCount, count($consumptionEntries), $failures, $passes);
|
||||
|
||||
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 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);
|
||||
|
||||
if ($failures !== []) {
|
||||
echo "\nM4 ledger service check failed with " . count($failures) . " failure(s):\n";
|
||||
foreach ($failures as $failure) {
|
||||
echo "- {$failure}\n";
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "\nM4 ledger service check passed with {$passes} assertions.\n";
|
||||
Reference in New Issue
Block a user