import { expect, test } from '@playwright/test' import { PrismaClient } from '@prisma/client' import { anmelden } from './hilfen' /** * Wiederkehrende Zahlungen gegen die echte Anwendung. * * Die Einheitstests nageln die Erkennung fest (tests/wiederkehrend.test.ts). * Hier geht es um das, was sie nicht sehen können: Kommt die Auswertung mit * Daten aus der Datenbank überein, führt die Zeile wirklich ins * Vertragsformular, und überlebt ein Abhaken das Neuladen? * * Der Test legt sich seine Buchungen selbst an und räumt sie hinterher weg. * Er hängt damit nicht am Demofall — der hat heute gar keine Buchungen, und * ein Test, der auf fremde Daten hofft, wird irgendwann grundlos rot. */ /** Eigener Name, damit das Aufräumen nichts Fremdes trifft. */ const EMPFAENGER = 'Prüfhausen Lebensversicherung AG' const EMPFAENGER_ZWEITE_SCHREIBWEISE = 'PRUEFHAUSEN LEBENSVERSICHERUNG AG' const EINZELNE = 'Prüfhausen Feuersozietät' async function mitDb(arbeit: (db: PrismaClient) => Promise): Promise { const db = new PrismaClient() try { return await arbeit(db) } finally { await db.$disconnect() } } /** * Legt für JEDE betreute Person dieselben Buchungen an. * * Welche Person nach dem Anmelden aktiv ist, hängt an einem Cookie. Statt * das im Test nachzubauen, bekommen alle dieselben Daten — es sind ein paar * Zeilen, und der Test wird dadurch unabhängig von der Reihenfolge. */ async function testdatenAnlegen(): Promise { await mitDb(async (db) => { const betreute = await db.betreute.findMany({ where: { geloeschtAm: null }, select: { id: true }, }) for (const person of betreute) { const konto = await db.konto.findFirst({ where: { betreuteId: person.id, geloeschtAm: null }, select: { id: true }, }) const gemeinsam = { betreuteId: person.id, kontoId: konto?.id ?? null, richtung: 'AUSGABE' as const, kategorie: 'VERSICHERUNG' as const, betrifftBetreute: true, } // Sechs monatliche Abbuchungen, zwei davon in anderer Schreibweise. for (let monat = 1; monat <= 6; monat++) { await db.buchung.create({ data: { ...gemeinsam, datum: new Date(Date.UTC(2026, monat - 1, 1)), betragCent: 4250n, gegenstelle: monat > 4 ? EMPFAENGER_ZWEITE_SCHREIBWEISE : EMPFAENGER, }, }) } // Eine einzelne größere Zahlung - der Fall "Jahresbeitrag". await db.buchung.create({ data: { ...gemeinsam, datum: new Date(Date.UTC(2026, 2, 15)), betragCent: 48_000n, gegenstelle: EINZELNE, }, }) } }) } async function testdatenEntfernen(): Promise { await mitDb(async (db) => { await db.buchung.deleteMany({ where: { gegenstelle: { in: [EMPFAENGER, EMPFAENGER_ZWEITE_SCHREIBWEISE, EINZELNE] } }, }) await db.zahlungsgruppeGeklaert.deleteMany({ where: { anzeigename: { contains: 'Prüfhausen' } }, }) }) } test.describe('Wiederkehrende Zahlungen', () => { test.beforeAll(async () => { await testdatenEntfernen() await testdatenAnlegen() }) test.afterAll(async () => { await testdatenEntfernen() }) test('findet die monatliche Abbuchung und fasst die Schreibweisen zusammen', async ({ page, }) => { await anmelden(page) await page.goto('/finanzen/wiederkehrend') const zeile = page.locator('tr', { hasText: 'Prüfhausen Lebensversicherung' }).first() await expect(zeile).toBeVisible() // Erkannter Rhythmus samt Begründung - nicht nur das Ergebnis. await expect(zeile).toContainText('monatlich') await expect(zeile).toContainText('6 Zahlungen') // 42,50 EUR mal zwölf. await expect(zeile).toContainText('510,00') // Zwei Schreibweisen desselben Empfängers, in einer Zeile. await expect(zeile).toContainText('Schreibweisen zusammengefasst') // Und angezeigt wird die lesbare, nicht die geschriene aus dem Auszug. await expect(zeile).toContainText(EMPFAENGER) }) test('listet die einzelne größere Zahlung getrennt', async ({ page }) => { // Die Lücke der Gruppierung: Ein Jahresbeitrag erscheint nur einmal. await anmelden(page) await page.goto('/finanzen/wiederkehrend') // Die Überschrift steht, und die Zahlung dazu in einer eigenen Zeile. await expect(page.getByText('Einzelne größere Zahlungen')).toBeVisible() const zeile = page.locator('tr', { hasText: EINZELNE }) await expect(zeile).toContainText('480,00') // Ohne Vertrag - genau deshalb steht sie hier. await expect(zeile).toContainText('keiner') }) test('übernimmt Gesellschaft und Beitrag ins Vertragsformular', async ({ page }) => { await anmelden(page) await page.goto('/finanzen/wiederkehrend') const zeile = page.locator('tr', { hasText: 'Prüfhausen Lebensversicherung' }).first() await zeile.getByRole('link', { name: 'Vertrag anlegen' }).click() await page.waitForURL('**/vertraege/neu**') await expect(page.getByText('aus den wiederkehrenden Zahlungen')).toBeVisible() // Was aus dem Auszug ablesbar ist, steht drin. await expect(page.locator('input[name="gesellschaft"]')).toHaveValue( /Prüfhausen Lebensversicherung/, ) await expect(page.locator('input[name="beitrag"]')).toHaveValue('42,50') // Was NICHT ablesbar ist, bleibt leer - eine geratene Vorbelegung // würde übersehen statt korrigiert. await expect(page.locator('input[name="bezeichnung"]')).toHaveValue('') }) test('hakt eine Gruppe ab und holt sie wieder zurück', async ({ page }) => { await anmelden(page) await page.goto('/finanzen/wiederkehrend') const zeile = page.locator('tr', { hasText: 'Prüfhausen Lebensversicherung' }).first() await zeile.getByRole('button', { name: 'Kein Vertrag' }).click() await zeile.getByPlaceholder('Warum kein Vertrag?').fill('Nur zum Prüfen') await zeile.getByRole('button', { name: 'Abhaken', exact: true }).click() // Aus der Kandidatenliste verschwunden, unter „abgehakt" wieder da. await expect(page.getByRole('cell', { name: /Prüfhausen Lebensversicherung/ })).toHaveCount(0) await expect(page.getByText('Nur zum Prüfen')).toBeVisible() // Und die Entscheidung überlebt das Neuladen. await page.reload() await expect(page.getByText('Nur zum Prüfen')).toBeVisible() await page.getByRole('button', { name: 'Zurückholen' }).first().click() await expect(page.getByRole('cell', { name: /Prüfhausen Lebensversicherung/ })).toHaveCount(1) }) })