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
75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import { erfordereZugang, ZugriffVerweigert } from '@/lib/auth/sitzung'
|
|
import { serienlaufDurchfuehren } from '@/lib/vertraege/serienlauf'
|
|
import { tagAusText } from '@/lib/finanzen/datum'
|
|
|
|
/**
|
|
* Fuehrt die Serienabfrage durch und liefert das ZIP.
|
|
*
|
|
* POST wie bei den anderen Downloads: Die Auswahl kommt mit, und der Lauf
|
|
* hat Nebenwirkungen (Dokumente, Fristen, Anfragedatum) - ein GET, das
|
|
* schreibt, waere eine Falle fuer jeden Vorabruf durch den Browser.
|
|
*
|
|
* Die Nebenwirkungen passieren VOR der Auslieferung. Faellt danach noch
|
|
* etwas beim Herunterladen aus, sind Akte und Fristen trotzdem korrekt -
|
|
* und die Schreiben liegen als PDF ohnehin in der Akte.
|
|
*/
|
|
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
export async function POST(anfrage: Request): Promise<Response> {
|
|
let formular: FormData
|
|
try {
|
|
formular = await anfrage.formData()
|
|
} catch {
|
|
return NextResponse.json({ fehler: 'Ungültige Anfrage.' }, { status: 400 })
|
|
}
|
|
|
|
const betreuteId = String(formular.get('betreuteId') ?? '')
|
|
const stichtagRoh = String(formular.get('stichtag') ?? '').trim()
|
|
const vertragIds = formular
|
|
.getAll('vertragId')
|
|
.map((wert) => String(wert).trim())
|
|
.filter(Boolean)
|
|
|
|
if (!betreuteId || vertragIds.length === 0) {
|
|
return NextResponse.json({ fehler: 'Es ist kein Vertrag ausgewählt.' }, { status: 400 })
|
|
}
|
|
// Obergrenze als Schutz vor Unsinn - real sind es fuenf bis zehn.
|
|
if (vertragIds.length > 100) {
|
|
return NextResponse.json({ fehler: 'Zu viele Verträge auf einmal.' }, { status: 400 })
|
|
}
|
|
|
|
const stichtag = tagAusText(stichtagRoh)
|
|
if (!stichtag) {
|
|
return NextResponse.json({ fehler: 'Bitte einen gültigen Stichtag angeben.' }, { status: 400 })
|
|
}
|
|
|
|
let benutzer
|
|
try {
|
|
const zugang = await erfordereZugang(betreuteId, 'VERTRAEGE', 'schreiben')
|
|
benutzer = zugang.benutzer
|
|
} catch (ausnahme) {
|
|
if (ausnahme instanceof ZugriffVerweigert) {
|
|
return NextResponse.json({ fehler: ausnahme.message }, { status: 403 })
|
|
}
|
|
throw ausnahme
|
|
}
|
|
|
|
const ergebnis = await serienlaufDurchfuehren({
|
|
betreuteId,
|
|
vertragIds,
|
|
stichtag,
|
|
benutzer: { id: benutzer.id, name: benutzer.name, email: benutzer.email },
|
|
})
|
|
|
|
return new NextResponse(new Uint8Array(ergebnis.zip), {
|
|
headers: {
|
|
'Content-Type': 'application/zip',
|
|
'Content-Disposition': `attachment; filename="${ergebnis.dateiname}"`,
|
|
'Content-Length': String(ergebnis.zip.length),
|
|
'Cache-Control': 'no-store, must-revalidate',
|
|
},
|
|
})
|
|
}
|