Legacy-Abbau Schritt 3+4: kl_*-Tabellen und legacy_*-Spalten entfernt
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>
This commit is contained in:
+22
-100
@@ -166,10 +166,9 @@ function ledger_fetch_tenant_totals(PDO $pdo, int $tenantId, ?int $year = null):
|
||||
* - year: int
|
||||
* - active_only: bool|null
|
||||
* - participant_ids: list<int>
|
||||
* - legacy_mitarbeiter_ids: list<int>
|
||||
* - email_norms: list<string>
|
||||
*
|
||||
* @return list<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}>
|
||||
* @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
|
||||
{
|
||||
@@ -193,17 +192,6 @@ 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'])
|
||||
@@ -230,7 +218,6 @@ 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,
|
||||
@@ -249,7 +236,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.legacy_mitarbeiter_id, p.display_name, p.email, p.email_norm, p.paypal_name, p.active
|
||||
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);
|
||||
@@ -260,7 +247,6 @@ 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,
|
||||
@@ -280,7 +266,7 @@ function ledger_fetch_participant_summaries(PDO $pdo, int $tenantId, array $opti
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @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
|
||||
{
|
||||
@@ -292,19 +278,6 @@ 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;
|
||||
}
|
||||
|
||||
function ledger_is_default_tenant(PDO $pdo, int $tenantId): bool
|
||||
{
|
||||
$tenant = ledger_fetch_default_tenant($pdo);
|
||||
@@ -313,7 +286,7 @@ function ledger_is_default_tenant(PDO $pdo, int $tenantId): bool
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{participant_id: int, display_name: string, email: ?string, email_norm: ?string, paypal_name: ?string, active: bool, legacy_mitarbeiter_id: ?int, user_id: ?int, role: ?string, membership_status: ?string}>
|
||||
* @return list<array{participant_id: int, display_name: string, email: ?string, email_norm: ?string, paypal_name: ?string, active: bool, user_id: ?int, role: ?string, membership_status: ?string}>
|
||||
*/
|
||||
function ledger_fetch_participants_for_admin(PDO $pdo, int $tenantId): array
|
||||
{
|
||||
@@ -325,7 +298,6 @@ function ledger_fetch_participants_for_admin(PDO $pdo, int $tenantId): array
|
||||
p.email_norm,
|
||||
p.paypal_name,
|
||||
p.active,
|
||||
p.legacy_mitarbeiter_id,
|
||||
p.user_id,
|
||||
tm.role,
|
||||
tm.status AS membership_status
|
||||
@@ -347,7 +319,6 @@ function ledger_fetch_participants_for_admin(PDO $pdo, int $tenantId): array
|
||||
'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,
|
||||
'legacy_mitarbeiter_id' => $row['legacy_mitarbeiter_id'] !== null ? (int)$row['legacy_mitarbeiter_id'] : null,
|
||||
'user_id' => $row['user_id'] !== null ? (int)$row['user_id'] : null,
|
||||
'role' => $row['role'] !== null ? (string)$row['role'] : null,
|
||||
'membership_status' => $row['membership_status'] !== null ? (string)$row['membership_status'] : null,
|
||||
@@ -426,17 +397,12 @@ function ledger_anonymize_participant(PDO $pdo, int $tenantId, int $participantI
|
||||
return $stmt->rowCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the ledger entry mirrored from a legacy row as voided instead of
|
||||
* deleting it, so corrections stay auditable. Idempotent: voiding an
|
||||
* already-voided or missing entry is a no-op and returns false.
|
||||
*/
|
||||
/**
|
||||
* Storniert einen Journaleintrag anhand seiner Ledger-ID - immer auf den
|
||||
* Mandanten eingegrenzt, damit ueber eine untergeschobene ID nicht in fremden
|
||||
* Mandanten gebucht werden kann. Haengt am Eintrag noch eine Legacy-Zeile,
|
||||
* wird sie mitgeloescht, damit der Mirror den Eintrag nicht wieder auferstehen
|
||||
* laesst.
|
||||
* Mandanten storniert werden kann. Markiert den Eintrag als storniert statt
|
||||
* ihn zu loeschen, damit Korrekturen nachvollziehbar bleiben. Idempotent: ein
|
||||
* bereits stornierter oder fehlender Eintrag ergibt false.
|
||||
*
|
||||
* @param string|null $expectedType Absichern, dass z. B. ueber das
|
||||
* Einzahlungs-Formular kein Strich storniert
|
||||
@@ -444,15 +410,8 @@ function ledger_anonymize_participant(PDO $pdo, int $tenantId, int $participantI
|
||||
*/
|
||||
function ledger_void_entry(PDO $pdo, int $tenantId, int $entryId, ?string $expectedType = null): bool
|
||||
{
|
||||
// Nur bekannte Legacy-Tabellen, damit der Tabellenname nie ungeprueft in
|
||||
// ein DELETE wandert.
|
||||
$legacyKeys = [
|
||||
'kl_Einzahlungen' => 'EinzahlungsID',
|
||||
'kl_Kaffeeverbrauch' => 'VerbrauchID',
|
||||
];
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT id, type, legacy_table, legacy_id
|
||||
"SELECT id, type
|
||||
FROM ledger_entries
|
||||
WHERE id = ? AND tenant_id = ? AND voided_at IS NULL
|
||||
LIMIT 1"
|
||||
@@ -467,51 +426,30 @@ function ledger_void_entry(PDO $pdo, int $tenantId, int $entryId, ?string $expec
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
$stmt = $pdo->prepare('UPDATE ledger_entries SET voided_at = NOW() WHERE id = ? AND tenant_id = ? AND voided_at IS NULL');
|
||||
$stmt->execute([$entryId, $tenantId]);
|
||||
|
||||
$pdo->prepare('UPDATE ledger_entries SET voided_at = NOW() WHERE id = ? AND tenant_id = ? AND voided_at IS NULL')
|
||||
->execute([$entryId, $tenantId]);
|
||||
|
||||
$legacyTable = $entry['legacy_table'] !== null ? (string)$entry['legacy_table'] : null;
|
||||
if ($legacyTable !== null && $entry['legacy_id'] !== null && isset($legacyKeys[$legacyTable])) {
|
||||
$pdo->prepare("DELETE FROM {$legacyTable} WHERE {$legacyKeys[$legacyTable]} = ?")
|
||||
->execute([(int)$entry['legacy_id']]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return $stmt->rowCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Storniert den juengsten, noch nicht stornierten Web-Selbsteintrag eines
|
||||
* Teilnehmers (Quelle 'self_entry' bei SaaS-nativen Mandanten bzw. 'legacy_web'
|
||||
* beim migrierten Default-Mandanten). Bewusst nur eigene Web-Eintraege - vom
|
||||
* Kassenwart per Sammelerfassung gesetzte Striche ('manual_bulk'/'legacy_manual')
|
||||
* bleiben unangetastet. Beim Default-Mandanten wird zusaetzlich die
|
||||
* kl_Kaffeeverbrauch-Zeile geloescht, damit sie nicht erneut gespiegelt wird
|
||||
* und auf den Legacy-Sammelseiten verschwindet.
|
||||
* Teilnehmers (Quelle 'self_entry'). Bewusst nur eigene Web-Eintraege - vom
|
||||
* Kassenwart per Sammelerfassung gesetzte Striche ('manual_bulk') bleiben
|
||||
* unangetastet.
|
||||
*
|
||||
* @return array{ok: bool, marks?: int, error?: string}
|
||||
*/
|
||||
function ledger_void_own_self_entry(PDO $pdo, int $tenantId, int $participantId): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT id, marks_count, legacy_table, legacy_id
|
||||
"SELECT id, marks_count
|
||||
FROM ledger_entries
|
||||
WHERE tenant_id = ?
|
||||
AND participant_id = ?
|
||||
AND type = 'consumption'
|
||||
AND voided_at IS NULL
|
||||
AND source IN ('self_entry', 'legacy_web')
|
||||
AND source = 'self_entry'
|
||||
ORDER BY booked_at DESC, id DESC
|
||||
LIMIT 1"
|
||||
);
|
||||
@@ -525,11 +463,6 @@ function ledger_void_own_self_entry(PDO $pdo, int $tenantId, int $participantId)
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
if ($entry['legacy_table'] === 'kl_Kaffeeverbrauch' && $entry['legacy_id'] !== null) {
|
||||
$pdo->prepare('DELETE FROM kl_Kaffeeverbrauch WHERE VerbrauchID = ?')
|
||||
->execute([(int)$entry['legacy_id']]);
|
||||
}
|
||||
|
||||
$pdo->prepare("UPDATE ledger_entries SET voided_at = NOW() WHERE id = ? AND voided_at IS NULL")
|
||||
->execute([(int)$entry['id']]);
|
||||
|
||||
@@ -546,9 +479,7 @@ function ledger_void_own_self_entry(PDO $pdo, int $tenantId, int $participantId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a consumption entry straight into the ledger, without any legacy
|
||||
* kl_Kaffeeverbrauch row. Used for tenants that have no legacy shadow table
|
||||
* (every tenant except the migrated default tenant).
|
||||
* Records a consumption entry straight into the ledger.
|
||||
*/
|
||||
function ledger_record_consumption(PDO $pdo, int $tenantId, int $participantId, int $marks, int $unitPriceCents, string $source, ?int $createdByUserId = null): int
|
||||
{
|
||||
@@ -568,10 +499,6 @@ function ledger_record_consumption(PDO $pdo, int $tenantId, int $participantId,
|
||||
return (int)$pdo->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a payment entry straight into the ledger, without any legacy
|
||||
* kl_Einzahlungen row. Used for tenants that have no legacy shadow table.
|
||||
*/
|
||||
/**
|
||||
* Bucht eine Einzahlung. $amountCents darf negativ sein (Abzug, Auszahlung,
|
||||
* Korrektur) - fuer Tippfehler ist aber ledger_void_entry der richtige Weg,
|
||||
@@ -597,12 +524,11 @@ function ledger_record_payment(PDO $pdo, int $tenantId, int $participantId, int
|
||||
}
|
||||
|
||||
/**
|
||||
* Ledger-native equivalent of the legacy "100-Tage-Liste" front/back split:
|
||||
* active participants whose summed marks within the trailing window are at
|
||||
* or above the ten-mark threshold ($atLeastTen), or below it. Used for
|
||||
* tenants without a legacy kl_Kaffeeverbrauch history to filter against.
|
||||
* "100-Tage-Liste" Vorder-/Rueckseiten-Aufteilung: aktive Teilnehmer, deren
|
||||
* summierte Striche im nachlaufenden Fenster die Zehner-Schwelle erreichen
|
||||
* ($atLeastTen) oder darunter liegen.
|
||||
*
|
||||
* @return list<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}>
|
||||
* @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_participants_by_window_marks(PDO $pdo, int $tenantId, int $windowDays, bool $atLeastTen): array
|
||||
{
|
||||
@@ -637,7 +563,7 @@ function ledger_fetch_participants_by_window_marks(PDO $pdo, int $tenantId, int
|
||||
* - 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}>
|
||||
* @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, voided_at: ?string}>
|
||||
*/
|
||||
/**
|
||||
* Verbrauch eines Teilnehmers pro Monat eines Jahres (nur nicht stornierte
|
||||
@@ -707,8 +633,6 @@ function ledger_fetch_recent_entries(PDO $pdo, int $tenantId, array $options = [
|
||||
le.booked_at,
|
||||
le.source,
|
||||
le.note,
|
||||
le.legacy_table,
|
||||
le.legacy_id,
|
||||
le.voided_at
|
||||
FROM ledger_entries le
|
||||
JOIN participants p
|
||||
@@ -735,8 +659,6 @@ function ledger_fetch_recent_entries(PDO $pdo, int $tenantId, array $options = [
|
||||
'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,
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user