Files
betreuungs-cockpit/src/components/vertraege/rueckkaufswert.tsx
T
Florian Wilde c21d03abf6 Schreiben erzeugen, ausgeben und ablegen
Die Anschreiben gab es bisher nur als Text zum Kopieren. Danach war
Handarbeit: in Word nachbauen, ausdrucken, abschicken - und noch einmal
von Hand als Dokument in die Akte legen. Der letzte Schritt wurde am
haeufigsten vergessen, und dann fehlt der Nachweis im Bericht.

Neu unter /schreiben/neu: bearbeiten, als Word-Datei herunterladen, in
die Akte legen, Wiedervorlage setzen - in einem Zug. Eine Seite fuer alle
Arten und alle drei Empfaengerwege, statt die Bearbeitung dreimal an
Vertrag, Vorgang und Freitext nachzubauen.

Der dritte Weg ist der eigentliche Zugewinn: Schreiben an eine Stelle,
zu der es noch keinen Datensatz gibt. Am Anfang einer Betreuung ist das
der Normalfall - man fragt ja an, um zu erfahren, ob dort ueberhaupt
etwas besteht. Aus der freien Anschrift laesst sich mit einem Klick ein
Kontakt anlegen.

Zwei Entscheidungen, die erklaert gehoeren:

Die Akte bekommt ein PDF, heruntergeladen wird .docx. Der
Objektspeicher nimmt nur PDF und Bilder an, geprueft an den Magic Bytes.
Eine .docx ist ein ZIP-Archiv; sie durchzulassen hiesse jedes ZIP
durchzulassen, und diese Pruefung ist eine bewusste Zusage der
Anwendung. Sachlich ist PDF fuer den Nachweis ohnehin richtiger: Es
aendert sich nicht mehr. Die Oberflaeche sagt dazu, dass eine spaeter in
Word geaenderte Fassung von der Akte abweicht.

Der Berichtssatz in berichte/docx.ts bleibt unangetastet. Er setzt einen
Bericht mit Kopfzeilen und Unterschriftenblock; ein Brief braucht
Absender, Anschriftenfeld, Datumszeile und Grussformel. Beides in eine
Funktion zu zwingen haette beide Ausgaben schlechter gemacht. Der
Umsetzungshinweis im Issue ("muss nur angebunden werden") war insoweit
falsch - von mir selbst geschrieben.

Die vorhandenen Erzeuger (Rueckkaufswert, Kuendigung, Sachstandsanfrage)
bleiben unveraendert; ihr Wortlaut ist getestet und erprobt.

Geprueft: zehn Einheitstests, darunter der Gleichschritt der
Wiedervorlagefristen ueber zwei Module hinweg. Fuenf End-to-End-Tests
auf beiden Geraeteprofilen: Entwurf, Vertragsbezug, Word-Download,
Ablage samt Wiedervorlage, Kontakt aus freier Anschrift.

Refs #4
2026-08-23 10:14:59 +02:00

254 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import Link from 'next/link'
import { useActionState, useState } from 'react'
import { Coins, FileDown, Mail, Plus } from 'lucide-react'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Select } from '@/components/ui/select'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Textarea } from '@/components/ui/textarea'
import { Feld } from '@/components/feld'
import { Leerzustand } from '@/components/seiten-kopf'
import { Absendeknopf, Textbaustein, Zustandsmeldung } from '@/components/substanz-formular'
import { WERTQUELLEN_VERTRAG } from '@/lib/vertraege/konstanten'
import { rueckkaufswertAnfordern, rueckkaufswertErfassen } from '@/server/actions/vertraege'
import { formatCent, formatDatum } from '@/lib/utils'
import type { Formularzustand } from '@/lib/validierung/substanz'
export type RueckkaufswertZeile = {
id: string
stichtag: string
wertCent: number
wertquelle: string
bemerkung: string | null
}
export function Rueckkaufswert({
betreuteId,
vertragId,
werte,
angefragtAm,
erinnerungFaellig,
tageSeitAnfrage,
anschreiben,
stichtagVorschlag,
darfSchreiben,
}: {
betreuteId: string
vertragId: string
werte: RueckkaufswertZeile[]
angefragtAm: string | null
erinnerungFaellig: boolean
tageSeitAnfrage: number | null
anschreiben: string
stichtagVorschlag: string
darfSchreiben: boolean
}) {
const [erfassen, erfassenAktion] = useActionState<Formularzustand, FormData>(
rueckkaufswertErfassen,
{},
)
const [anfrage, anfrageAktion] = useActionState<Formularzustand, FormData>(
rueckkaufswertAnfordern,
{},
)
const [briefOffen, setBriefOffen] = useState(false)
const [erfassenOffen, setErfassenOffen] = useState(false)
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Coins className="size-5" aria-hidden="true" />
Rückkaufswert
</CardTitle>
<CardDescription>
Der Bezirk verlangt zu jedem Versicherungsvertrag den Rückkaufswert zu einem
Stichtag — schriftlich von der Gesellschaft, nicht geschätzt. Die Antwort dauert
erfahrungsgemäß drei bis sechs Wochen.
</CardDescription>
</CardHeader>
<CardContent className="space-y-5">
{angefragtAm && (
<Alert variant={erinnerungFaellig ? 'warnung' : 'hinweis'}>
<Mail aria-hidden="true" />
<AlertTitle>
Angefragt am {formatDatum(angefragtAm)}
{tageSeitAnfrage !== null && ` — vor ${tageSeitAnfrage} Tagen`}
</AlertTitle>
<AlertDescription>
{erinnerungFaellig
? 'Die vier Wochen sind um. Ruf an und halte das Gespräch im Kontaktprotokoll fest — schriftlich nachfassen dauert wieder vier Wochen.'
: 'Die Wiedervorlage steht in den Fristen. Kommt die Antwort früher, hak die Frist dort ab.'}
</AlertDescription>
</Alert>
)}
{werte.length === 0 ? (
<Leerzustand
titel="Noch kein Rückkaufswert erfasst"
beschreibung="Fordere ihn bei der Gesellschaft an. Frag im selben Schreiben nach der beitragsfreien Versicherungssumme — sonst musst du für den Vergleich ein zweites Mal schreiben."
icon={Coins}
aktion={
darfSchreiben && (
<Button type="button" onClick={() => setBriefOffen(true)}>
<Mail aria-hidden="true" />
Anschreiben erzeugen
</Button>
)
}
/>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Stichtag</TableHead>
<TableHead className="text-right">Rückkaufswert</TableHead>
<TableHead>Quelle</TableHead>
<TableHead>Bemerkung</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{werte.map((zeile) => (
<TableRow key={zeile.id}>
<TableCell className="whitespace-nowrap">{formatDatum(zeile.stichtag)}</TableCell>
<TableCell className="text-right font-medium tabular-nums">
{formatCent(zeile.wertCent)}
</TableCell>
<TableCell>{zeile.wertquelle}</TableCell>
<TableCell className="text-sm text-muted-foreground">
{zeile.bemerkung ?? '–'}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
{darfSchreiben && (
<div className="flex flex-wrap gap-2">
<Button type="button" variant="outline" onClick={() => setBriefOffen((o) => !o)}>
<Mail aria-hidden="true" />
{briefOffen ? 'Anschreiben schließen' : 'Rückkaufswert anfordern'}
</Button>
<Button type="button" variant="outline" onClick={() => setErfassenOffen((o) => !o)}>
<Plus aria-hidden="true" />
{erfassenOffen ? 'Eingabe schließen' : 'Mitgeteilten Wert eintragen'}
</Button>
</div>
)}
{darfSchreiben && briefOffen && (
<div className="space-y-4 rounded-md border p-4">
<Textbaustein
text={anschreiben}
beschriftung="Anschreiben an die Gesellschaft"
hoehe="min-h-[32rem]"
/>
{/* Der Weg, der nicht beim Kopieren endet: bearbeiten,
Word-Datei, Ablage in der Akte, Wiedervorlage. */}
<Button asChild variant="outline">
<Link href={`/schreiben/neu?art=RUECKKAUFSWERT&vertragId=${vertragId}`}>
<FileDown aria-hidden="true" />
Bearbeiten, als Word-Datei und in die Akte
</Link>
</Button>
<form action={anfrageAktion} className="space-y-4" noValidate>
<input type="hidden" name="betreuteId" value={betreuteId} />
<input type="hidden" name="vertragId" value={vertragId} />
<Zustandsmeldung zustand={anfrage} />
<div className="grid gap-4 sm:grid-cols-2">
<Feld
id="anfrage-stichtag"
beschriftung="Stichtag der Anfrage"
erklaerung="Der Tag, zu dem der Wert mitgeteilt werden soll. Für die Sozialhilfe meist der Tag der Antragstellung."
fehler={anfrage.felder?.stichtag}
pflicht
>
<Input
name="stichtag"
type="date"
defaultValue={stichtagVorschlag}
required
/>
</Feld>
</div>
<Absendeknopf>Anfrage vermerken und Wiedervorlage anlegen</Absendeknopf>
<p className="text-sm text-muted-foreground">
Setzt das Datum der Anfrage am Vertrag und legt eine Wiedervorlage in vier
Wochen an. Den Brief selbst schickst du von Hand ab — die App verschickt nichts.
</p>
</form>
</div>
)}
{darfSchreiben && erfassenOffen && (
<form action={erfassenAktion} className="space-y-4 rounded-md border p-4" noValidate>
<input type="hidden" name="betreuteId" value={betreuteId} />
<input type="hidden" name="vertragId" value={vertragId} />
<Zustandsmeldung zustand={erfassen} />
<div className="grid gap-4 sm:grid-cols-2">
<Feld
id="rkw-stichtag"
beschriftung="Stichtag"
fehler={erfassen.felder?.stichtag}
pflicht
>
<Input name="stichtag" type="date" required />
</Feld>
<Feld
id="rkw-wert"
beschriftung="Rückkaufswert"
fehler={erfassen.felder?.wert}
pflicht
>
<Input name="wert" inputMode="decimal" placeholder="4.812,55" required />
</Feld>
<Feld
id="rkw-quelle"
beschriftung="Quelle"
erklaerung="Woher stammt die Zahl? Eine telefonische Auskunft ist besser als nichts, aber kein Nachweis."
fehler={erfassen.felder?.wertquelle}
pflicht
className="sm:col-span-2"
>
<Select name="wertquelle" defaultValue={WERTQUELLEN_VERTRAG[0]}>
{WERTQUELLEN_VERTRAG.map((q) => (
<option key={q} value={q}>
{q}
</option>
))}
</Select>
</Feld>
</div>
<Feld
id="rkw-bemerkung"
beschriftung="Bemerkung"
erklaerung="z. B. die beitragsfreie Versicherungssumme, wenn sie mitgeteilt wurde."
fehler={erfassen.felder?.bemerkung}
>
<Textarea name="bemerkung" rows={2} maxLength={1000} />
</Feld>
<Absendeknopf>Wert aufnehmen</Absendeknopf>
</form>
)}
</CardContent>
</Card>
)
}