Vor Vermoegensverzeichnis und Jahresbericht braucht es von jeder Lebens- und Rentenversicherung den Rueckkaufswert zum Stichtag - jedes Mal dieselben fuenf bis zehn Briefe. Neu unter Vertraege: Stichtag waehlen, Liste pruefen, ein Klick. Heraus kommt ein ZIP mit einer Word-Datei je Gesellschaft plus Begleitblatt; je Vertrag entstehen nebenbei die PDF-Fassung in der Akte, die Wiedervorlage nach 28 Tagen und der Anfragevermerk. Aufbau: - Auswahllogik als reine Funktionen in vertraege/serienabfrage.ts: Vorauswahl, Hindernisse mit Begruendung statt stummem Ausblenden, 28-Tage-Sperre im Gleichschritt mit der Wiedervorlage. - Der Lauf in vertraege/serienlauf.ts: je Vertrag das volle Paket aus Issue #4, aber in einem Durchgang. Kein Alles-oder-nichts - ein gescheiterter Vertrag steht mit Grund im Begleitblatt, die uebrigen laufen durch. Eine Transaktion ueber alle waere die falsche Zusage. - Dublettenschutz tagesgenau: Der Doppelklick erzeugt nichts Doppeltes, die bewusste zweite Anfrage nach einer Woche bleibt moeglich. - Die Anzahl steht vor dem Klick auf dem Knopf. Gekuendigte Vertraege bleiben anfragbar: Bis zum Wirksamwerden besteht der Vertrag, und genau dann will der Sozialhilfetraeger den Wert sehen. Geprueft: zwoelf Einheitstests, drei End-to-End-Tests inklusive der Nebenwirkungen in der Datenbank und des Dublettenschutzes. 602 Unit-Tests, 87 e2e, null Typfehler. Refs #6
186 lines
6.6 KiB
TypeScript
186 lines
6.6 KiB
TypeScript
import { expect, test } from '@playwright/test'
|
|
import { PrismaClient } from '@prisma/client'
|
|
import { anmelden } from './hilfen'
|
|
|
|
/**
|
|
* Serienabfrage der Rückkaufswerte — gegen die echte Anwendung.
|
|
*
|
|
* Geprüft wird der ganze Lauf: Vorauswahl, ZIP-Download, die
|
|
* Nebenwirkungen (Anfragedatum, Wiedervorlage, Ausgangsdokumente) und der
|
|
* Dublettenschutz beim zweiten Lauf am selben Tag.
|
|
*/
|
|
|
|
const KENNUNG = 'Prüfhausen Serie'
|
|
|
|
async function mitDb<T>(arbeit: (db: PrismaClient) => Promise<T>): Promise<T> {
|
|
const db = new PrismaClient()
|
|
try {
|
|
return await arbeit(db)
|
|
} finally {
|
|
await db.$disconnect()
|
|
}
|
|
}
|
|
|
|
async function aufraeumen(): Promise<void> {
|
|
await mitDb(async (db) => {
|
|
await db.dokument.deleteMany({ where: { empfaenger: { contains: KENNUNG } } })
|
|
await db.frist.deleteMany({ where: { titel: { contains: KENNUNG } } })
|
|
await db.vertrag.deleteMany({ where: { bezeichnung: { contains: KENNUNG } } })
|
|
})
|
|
}
|
|
|
|
test.describe('Serienabfrage', () => {
|
|
test.beforeAll(async () => {
|
|
await aufraeumen()
|
|
await mitDb(async (db) => {
|
|
const betreute = await db.betreute.findMany({
|
|
where: { geloeschtAm: null },
|
|
select: { id: true },
|
|
})
|
|
for (const person of betreute) {
|
|
// Zwei anfragbare, eine ohne Rueckkaufswert, eine ohne Gesellschaft.
|
|
await db.vertrag.createMany({
|
|
data: [
|
|
{
|
|
betreuteId: person.id,
|
|
bezeichnung: `${KENNUNG} Leben`,
|
|
gesellschaft: `${KENNUNG} Lebensversicherung`,
|
|
art: 'LEBENSVERSICHERUNG',
|
|
status: 'AKTIV',
|
|
},
|
|
{
|
|
betreuteId: person.id,
|
|
bezeichnung: `${KENNUNG} Sterbegeld`,
|
|
gesellschaft: `${KENNUNG} Sterbekasse`,
|
|
art: 'STERBEGELDVERSICHERUNG',
|
|
status: 'AKTIV',
|
|
},
|
|
{
|
|
betreuteId: person.id,
|
|
bezeichnung: `${KENNUNG} Haftpflicht`,
|
|
gesellschaft: `${KENNUNG} Allgemeine`,
|
|
art: 'HAFTPFLICHT',
|
|
status: 'AKTIV',
|
|
},
|
|
{
|
|
betreuteId: person.id,
|
|
bezeichnung: `${KENNUNG} Ohne Gesellschaft`,
|
|
gesellschaft: null,
|
|
art: 'LEBENSVERSICHERUNG',
|
|
status: 'AKTIV',
|
|
},
|
|
],
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
test.afterAll(async () => {
|
|
await aufraeumen()
|
|
})
|
|
|
|
test('wählt richtig vor und benennt die Hindernisse', async ({ page }) => {
|
|
await anmelden(page)
|
|
await page.goto('/vertraege/serienabfrage/seite')
|
|
|
|
// Die beiden anfragbaren sind vorausgewählt …
|
|
await expect(
|
|
page.locator('li', { hasText: `${KENNUNG} Leben` }).locator('input[type="checkbox"]'),
|
|
).toBeChecked()
|
|
await expect(
|
|
page.locator('li', { hasText: `${KENNUNG} Sterbegeld` }).locator('input[type="checkbox"]'),
|
|
).toBeChecked()
|
|
|
|
// … die ohne Gesellschaft steht mit Begründung da, ohne Haken …
|
|
const ohne = page.locator('li', { hasText: `${KENNUNG} Ohne Gesellschaft` })
|
|
await expect(ohne).toContainText('nachtragen')
|
|
await expect(ohne.locator('input[type="checkbox"]')).toHaveCount(0)
|
|
|
|
// … und die Haftpflicht taucht gar nicht erst auf.
|
|
await expect(page.locator('li', { hasText: `${KENNUNG} Haftpflicht` })).toHaveCount(0)
|
|
|
|
// Die Zusammenfassung steht auf dem Knopf, BEVOR etwas entsteht -
|
|
// und sie stimmt mit der Zahl der gesetzten Haken ueberein. Auf die
|
|
// absolute Zahl laesst sich nicht bauen: Der Demofall bringt eigene
|
|
// Vertraege mit.
|
|
const haken = await page.locator('input[type="checkbox"]:checked').count()
|
|
expect(haken).toBeGreaterThanOrEqual(2)
|
|
await expect(
|
|
page.getByRole('button', { name: new RegExp(`${haken} Schreiben erzeugen`) }),
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('führt den Lauf durch: ZIP, Akte, Wiedervorlagen, Anfragedatum', async ({ page }) => {
|
|
await anmelden(page)
|
|
await page.goto('/vertraege/serienabfrage/seite')
|
|
|
|
const [download] = await Promise.all([
|
|
page.waitForEvent('download'),
|
|
page.getByRole('button', { name: /Schreiben erzeugen und herunterladen/ }).click(),
|
|
])
|
|
expect(download.suggestedFilename()).toMatch(/^\d{4}-\d{2}-\d{2}_Rueckkaufswert-Anfragen\.zip$/)
|
|
|
|
// Nebenwirkungen in der Datenbank: Anfragedatum, Dokumente, Fristen.
|
|
const stand = await mitDb(async (db) => {
|
|
const angefragt = await db.vertrag.count({
|
|
where: {
|
|
bezeichnung: { in: [`${KENNUNG} Leben`, `${KENNUNG} Sterbegeld`] },
|
|
rueckkaufswertAngefragtAm: { not: null },
|
|
},
|
|
})
|
|
const dokumente = await db.dokument.count({
|
|
where: { empfaenger: { contains: KENNUNG }, richtung: 'AUSGANG' },
|
|
})
|
|
const fristen = await db.frist.count({
|
|
where: { titel: { contains: KENNUNG }, typ: 'WIEDERVORLAGE' },
|
|
})
|
|
return { angefragt, dokumente, fristen }
|
|
})
|
|
|
|
// Beide betreute Personen haben die Verträge - angefragt wird aber nur
|
|
// bei der aktiven. Deshalb: genau 2 von 4.
|
|
expect(stand.angefragt).toBe(2)
|
|
expect(stand.dokumente).toBe(2)
|
|
expect(stand.fristen).toBe(2)
|
|
|
|
// Auf der Seite stehen die beiden jetzt als frisch angefragt.
|
|
await page.goto('/vertraege/serienabfrage/seite')
|
|
await expect(page.getByText(/bereits angefragt/).first()).toBeVisible()
|
|
})
|
|
|
|
test('erzeugt beim zweiten Lauf am selben Tag keine Dubletten', async ({ page }) => {
|
|
await anmelden(page)
|
|
await page.goto('/vertraege/serienabfrage/seite')
|
|
|
|
// Alles abwaehlen, dann NUR den frisch angefragten Vertrag anwaehlen.
|
|
// So prueft der Lauf genau den Dublettenschutz und fasst die uebrigen
|
|
// Vertraege nicht an.
|
|
const angehakte = page.locator('input[type="checkbox"]:checked')
|
|
while ((await angehakte.count()) > 0) {
|
|
await angehakte.first().uncheck()
|
|
}
|
|
const leben = page.locator('li', { hasText: `${KENNUNG} Leben` })
|
|
await leben.locator('input[type="checkbox"]').check()
|
|
|
|
const [download] = await Promise.all([
|
|
page.waitForEvent('download'),
|
|
page.getByRole('button', { name: /Schreiben erzeugen und herunterladen/ }).click(),
|
|
])
|
|
expect(download.suggestedFilename()).toContain('.zip')
|
|
|
|
// Der Dublettenschutz greift: weiterhin genau ein Dokument und eine
|
|
// Frist für diesen Vertrag.
|
|
const stand = await mitDb(async (db) => {
|
|
const dokumente = await db.dokument.count({
|
|
where: { empfaenger: `${KENNUNG} Lebensversicherung` },
|
|
})
|
|
const fristen = await db.frist.count({
|
|
where: { titel: { contains: `${KENNUNG} Leben` }, typ: 'WIEDERVORLAGE' },
|
|
})
|
|
return { dokumente, fristen }
|
|
})
|
|
expect(stand.dokumente).toBe(1)
|
|
expect(stand.fristen).toBe(1)
|
|
})
|
|
})
|