Rueckkaufswerte aller Versicherungen in einem Zug anfragen
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
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { AlertCircle, FileArchive, Info } from 'lucide-react'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Feld } from '@/components/feld'
|
||||
|
||||
export type SerienZeile = {
|
||||
vertragId: string
|
||||
bezeichnung: string
|
||||
gesellschaft: string | null
|
||||
artBeschriftung: string
|
||||
moeglich: boolean
|
||||
hindernis: string | null
|
||||
vorausgewaehlt: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Auswahl und Start der Serienabfrage.
|
||||
*
|
||||
* Die Zusammenfassung ("Es entstehen N Schreiben") steht direkt auf dem
|
||||
* Knopf. Man soll den Fehlgriff bemerken, BEVOR zehn Briefe entstehen —
|
||||
* nicht danach beim Aufräumen.
|
||||
*
|
||||
* Der Start läuft als gewöhnliches Formular-POST gegen die Route: Der
|
||||
* Browser bekommt das ZIP als Download, und die Nebenwirkungen (Akte,
|
||||
* Fristen, Anfragedatum) sind serverseitig schon passiert. Nach dem
|
||||
* Klick wird die Seite neu geladen, damit die Sperrvermerke stimmen.
|
||||
*/
|
||||
export function SerienabfrageFormular({
|
||||
betreuteId,
|
||||
zeilen,
|
||||
stichtagVorschlag,
|
||||
}: {
|
||||
betreuteId: string
|
||||
zeilen: SerienZeile[]
|
||||
stichtagVorschlag: string
|
||||
}) {
|
||||
const router = useRouter()
|
||||
const [gewaehlt, setGewaehlt] = React.useState<Set<string>>(
|
||||
() => new Set(zeilen.filter((z) => z.vorausgewaehlt).map((z) => z.vertragId)),
|
||||
)
|
||||
const [stichtag, setStichtag] = React.useState(stichtagVorschlag)
|
||||
const [gestartet, setGestartet] = React.useState(false)
|
||||
|
||||
function umschalten(id: string) {
|
||||
setGewaehlt((bisher) => {
|
||||
const neu = new Set(bisher)
|
||||
if (neu.has(id)) neu.delete(id)
|
||||
else neu.add(id)
|
||||
return neu
|
||||
})
|
||||
}
|
||||
|
||||
const anzahl = gewaehlt.size
|
||||
|
||||
function beimAbsenden() {
|
||||
// Das Formular lädt als Download - die Seite bleibt stehen. Nach
|
||||
// kurzer Zeit neu laden, damit Sperrvermerke und "Wartet auf andere"
|
||||
// den neuen Stand zeigen.
|
||||
setGestartet(true)
|
||||
window.setTimeout(() => {
|
||||
router.refresh()
|
||||
setGestartet(false)
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
action="/vertraege/serienabfrage"
|
||||
method="post"
|
||||
onSubmit={beimAbsenden}
|
||||
className="space-y-5"
|
||||
>
|
||||
<input type="hidden" name="betreuteId" value={betreuteId} />
|
||||
|
||||
<Feld
|
||||
id="stichtag"
|
||||
beschriftung="Stichtag der Auskünfte"
|
||||
erklaerung="Zu diesem Tag sollen die Gesellschaften den Rückkaufswert mitteilen — üblicherweise der Stichtag des Vermögensverzeichnisses oder der Beginn des Betreuungsjahres."
|
||||
pflicht
|
||||
>
|
||||
<Input
|
||||
id="stichtag"
|
||||
name="stichtag"
|
||||
type="date"
|
||||
value={stichtag}
|
||||
onChange={(ereignis) => setStichtag(ereignis.target.value)}
|
||||
required
|
||||
/>
|
||||
</Feld>
|
||||
|
||||
<ul className="divide-y rounded-md border">
|
||||
{zeilen.map((zeile) => (
|
||||
<li key={zeile.vertragId} className="flex items-start gap-3 p-3">
|
||||
{zeile.moeglich ? (
|
||||
<Checkbox
|
||||
id={`vertrag-${zeile.vertragId}`}
|
||||
name="vertragId"
|
||||
value={zeile.vertragId}
|
||||
checked={gewaehlt.has(zeile.vertragId)}
|
||||
onChange={() => umschalten(zeile.vertragId)}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
) : (
|
||||
<span className="mt-0.5 size-4 shrink-0" aria-hidden="true" />
|
||||
)}
|
||||
|
||||
<label
|
||||
htmlFor={zeile.moeglich ? `vertrag-${zeile.vertragId}` : undefined}
|
||||
className={`min-w-0 flex-1 ${zeile.moeglich ? 'cursor-pointer' : 'opacity-60'}`}
|
||||
>
|
||||
<span className="flex flex-wrap items-center gap-2">
|
||||
<span className="font-medium">{zeile.bezeichnung}</span>
|
||||
<Badge variant="secondary">{zeile.artBeschriftung}</Badge>
|
||||
</span>
|
||||
<span className="block text-sm text-muted-foreground">
|
||||
{zeile.gesellschaft ?? 'ohne Gesellschaft'}
|
||||
</span>
|
||||
{zeile.hindernis && (
|
||||
<span className="mt-0.5 block text-xs text-ampel-orange">{zeile.hindernis}</span>
|
||||
)}
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{zeilen.length === 0 && (
|
||||
<Alert variant="hinweis">
|
||||
<Info aria-hidden="true" />
|
||||
<AlertDescription>
|
||||
Es gibt keine Verträge, die für eine Rückkaufswert-Anfrage in Frage kommen. Lege
|
||||
zuerst die Versicherungen unter <em>Verträge</em> an — oder finde sie über{' '}
|
||||
<em>Vermögen & Kasse → Wiederkehrende Zahlungen</em>.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{anzahl === 0 && zeilen.length > 0 && (
|
||||
<Alert variant="hinweis">
|
||||
<AlertCircle aria-hidden="true" />
|
||||
<AlertDescription>Kein Vertrag gewählt — es entsteht nichts.</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Button type="submit" disabled={anzahl === 0 || gestartet} size="lg">
|
||||
<FileArchive aria-hidden="true" />
|
||||
{gestartet
|
||||
? 'Wird erzeugt …'
|
||||
: `${anzahl} Schreiben erzeugen und herunterladen`}
|
||||
</Button>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Je Vertrag entsteht eine Word-Datei im ZIP, eine PDF-Fassung in der Akte, eine
|
||||
Wiedervorlage nach 28 Tagen — und der Vertrag gilt als angefragt.
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user