prepare('INSERT INTO tenants (slug, name, status) VALUES (?, ?, ?)'); $stmt->execute(["payment-match-{$key}-{$suffix}", "Payment Match {$key}", 'active']); return (int)$pdo->lastInsertId(); } function payment_matching_insert_participant(PDO $pdo, int $tenantId, string $displayName, string $email, ?string $paypalName): int { $stmt = $pdo->prepare( 'INSERT INTO participants (tenant_id, display_name, email, email_norm, paypal_name, active) VALUES (?, ?, ?, ?, ?, 1)' ); $stmt->execute([$tenantId, $displayName, $email, strtolower($email), $paypalName]); return (int)$pdo->lastInsertId(); } function payment_matching_cleanup(PDO $pdo, array $tenantIds): void { foreach ($tenantIds as $tenantId) { $pdo->prepare( 'DELETE r FROM payment_import_rows r JOIN payment_import_batches b ON b.id = r.batch_id WHERE b.tenant_id = ?' )->execute([$tenantId]); $pdo->prepare('DELETE FROM payment_import_batches WHERE tenant_id = ?')->execute([$tenantId]); $pdo->prepare('DELETE FROM participants WHERE tenant_id = ?')->execute([$tenantId]); $pdo->prepare('DELETE FROM tenants WHERE id = ?')->execute([$tenantId]); } } $pdo = dev_pdo(); $suffix = bin2hex(random_bytes(4)); $failures = []; $passes = 0; $tenantIds = []; try { $tenantId = payment_matching_insert_tenant($pdo, $suffix, 'a'); $otherTenantId = payment_matching_insert_tenant($pdo, $suffix, 'b'); $tenantIds = [$tenantId, $otherTenantId]; $uniqueId = payment_matching_insert_participant( $pdo, $tenantId, "Eindeutig {$suffix}", "unique-{$suffix}@test.local", "PayPal Unique {$suffix}" ); payment_matching_insert_participant( $pdo, $tenantId, "Doppelt {$suffix}", "duplicate-one-{$suffix}@test.local", null ); payment_matching_insert_participant( $pdo, $tenantId, "Doppelt {$suffix}", "duplicate-two-{$suffix}@test.local", null ); payment_matching_insert_participant( $pdo, $tenantId, "Paypal A {$suffix}", "paypal-a-{$suffix}@test.local", "PayPal Doppelt {$suffix}" ); payment_matching_insert_participant( $pdo, $tenantId, "Paypal B {$suffix}", "paypal-b-{$suffix}@test.local", "PayPal Doppelt {$suffix}" ); $otherTenantUniqueId = payment_matching_insert_participant( $pdo, $otherTenantId, "Doppelt {$suffix}", "other-{$suffix}@test.local", null ); $uniqueByName = imports_find_participant($pdo, $tenantId, "Eindeutig {$suffix}"); payment_matching_assert( 'eindeutiger Anzeigename wird gefunden', $uniqueByName !== null && $uniqueByName['participant_id'] === $uniqueId, $failures, $passes ); $uniqueByPaypalName = imports_find_participant($pdo, $tenantId, "paypal unique {$suffix}"); payment_matching_assert( 'eindeutiger PayPal-Name wird case-insensitive gefunden', $uniqueByPaypalName !== null && $uniqueByPaypalName['participant_id'] === $uniqueId, $failures, $passes ); payment_matching_assert( 'doppelter Anzeigename bleibt unmatched', imports_find_participant($pdo, $tenantId, "Doppelt {$suffix}") === null, $failures, $passes ); payment_matching_assert( 'doppelter PayPal-Name bleibt unmatched', imports_find_participant($pdo, $tenantId, "PayPal Doppelt {$suffix}") === null, $failures, $passes ); $otherTenantMatch = imports_find_participant($pdo, $otherTenantId, "Doppelt {$suffix}"); payment_matching_assert( 'gleicher Name in anderem Mandanten bleibt eindeutig', $otherTenantMatch !== null && $otherTenantMatch['participant_id'] === $otherTenantUniqueId, $failures, $passes ); $suggestions = imports_suggest_participants($pdo, $tenantId, "paypal unique {$suffix}", null, 3); payment_matching_assert( 'Vorschlaege enthalten den passenden Teilnehmer', $suggestions !== [] && $suggestions[0]['participant_id'] === $uniqueId, $failures, $passes ); $batchId = imports_create_batch($pdo, $tenantId, 'matching-test.csv', hash('sha256', $suffix), null); $unmatchedRowId = imports_store_row( $pdo, $batchId, 1, null, "PayPal Unique {$suffix}", 500, date('Y-m-d H:i:s'), 'unmatched', ['row' => 1] ); $invalidRowId = imports_store_row( $pdo, $batchId, 2, null, "Kaputt {$suffix}", 0, date('Y-m-d H:i:s'), 'invalid', ['row' => 2] ); $assignResult = imports_assign_row($pdo, $tenantId, $batchId, $unmatchedRowId, $uniqueId); $stmt = $pdo->prepare('SELECT participant_id, status FROM payment_import_rows WHERE id = ?'); $stmt->execute([$unmatchedRowId]); $assignedRow = $stmt->fetch(); payment_matching_assert( 'unmatched CSV-Zeile kann manuell zugeordnet werden', $assignResult['ok'] === true && (int)$assignedRow['participant_id'] === $uniqueId && (string)$assignedRow['status'] === 'matched', $failures, $passes ); $ignoreResult = imports_ignore_row($pdo, $tenantId, $batchId, $invalidRowId); $stmt = $pdo->prepare('SELECT participant_id, status FROM payment_import_rows WHERE id = ?'); $stmt->execute([$invalidRowId]); $ignoredRow = $stmt->fetch(); payment_matching_assert( 'ungueltige CSV-Zeile kann ignoriert werden', $ignoreResult['ok'] === true && $ignoredRow['participant_id'] === null && (string)$ignoredRow['status'] === 'ignored', $failures, $passes ); } catch (Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } fwrite(STDERR, "Payment matching check failed to run: {$e->getMessage()}\n"); payment_matching_cleanup($pdo, $tenantIds); exit(1); } payment_matching_cleanup($pdo, $tenantIds); if ($failures !== []) { echo "\nPayment matching check failed with " . count($failures) . " failure(s):\n"; foreach ($failures as $failure) { echo "- {$failure}\n"; } exit(1); } echo "\nPayment matching check passed with {$passes} assertions.\n";