feat: Immobilie, Versicherungen und Heim mit COPD-Verlauf

Etappe 6. Das leerstehende Haus, die Vertraege und der
Gesundheitsverlauf - die drei Bereiche, in denen fuer diesen Fall das
meiste Geld und das meiste Risiko liegt.

Leerstands-Checkliste mit zehn Punkten, jeder mit Begruendung. Der
wichtigste: den Gebaeudeversicherer ueber den Leerstand informieren -
sonst droht Leistungsfreiheit, und zwar genau dann, wenn es brennt oder
eine Leitung platzt. Dazu Verkehrssicherungspflicht, Frostschutz zum
Oktober, Kontrollgaenge, Zaehlerstaende, Schluesselverzeichnis.

Der Kuendigungsrechner nennt den spaetesten ABSENDETAG, nicht den
Fristablauf: eine Kuendigung wirkt mit dem Zugang, nicht mit dem
Abschicken. Drei Tage Postlaufpuffer sind einkalkuliert.

Der Kuendigungs-Assistent beantwortet die Genehmigungsfrage nach
§ 1850 BGB nicht - er stellt sie und verlangt den Gang zum
Rechtspfleger. Der Versandnachweis ist Pflichtfeld: ohne ihn kommt die
Kuendigung nicht durch die Pruefung.

Leistungszuschlag nach § 43c SGB XI mit Stand 01.01.2024
(15/30/50/75 Prozent nach Verweildauer, erst ab Pflegegrad 2). Die
Oberflaeche zeigt, ab wann die naechste Stufe greift - das ist bares
Geld und wird regelmaessig uebersehen.

COPD-Schnellerfassung fuer Exazerbation, Sauerstoff, Sturz,
Krankenhaus, Hilfsmittel und Impfungen, mit STIKO-Plan fuer COPD.
Beschreibungen verschluesselt. Die Jahresauswertung ueber Exazerbationen
und Krankenhaustage wandert in den Jahresbericht und begruendet eine
Hoeherstufung.

74 Tests.

Etappe: 6
Phase: Substanz
This commit is contained in:
Florian Wilde
2026-08-22 06:22:24 +02:00
parent d35e24f830
commit b69a12da8f
28 changed files with 4309 additions and 917 deletions
@@ -0,0 +1,273 @@
'use client'
import { useActionState } from 'react'
import { CheckCircle2, FileWarning, Gavel, Scale, TriangleAlert } from 'lucide-react'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Checkbox } from '@/components/ui/checkbox'
import { Input } from '@/components/ui/input'
import { Select } from '@/components/ui/select'
import { Textarea } from '@/components/ui/textarea'
import { Feld } from '@/components/feld'
import { Rechtshinweis } from '@/components/rechtshinweis'
import { Absendeknopf, Textbaustein, Zustandsmeldung } from '@/components/substanz-formular'
import { kuendigungVermerken } from '@/server/actions/vertraege'
import { formatDatum } from '@/lib/utils'
import type { Formularzustand } from '@/lib/validierung/substanz'
export type AssistentDaten = {
schreiben: string
warnungen: string[]
genehmigung: {
stellen: boolean
frage: string
rechtsgrundlage: string
hinweis: string
}
versandnachweis: {
moeglichkeiten: string[]
hinweis: string
}
plan: {
berechenbar: boolean
naechsterTermin: string | null
zugangSpaetestensAm: string | null
spaetesterAbsendetag: string | null
tageBisAbsendetag: number | null
verpasst: boolean
hinweise: string[]
}
}
export function KuendigungsAssistent({
betreuteId,
vertragId,
daten,
heuteIso,
darfSchreiben,
}: {
betreuteId: string
vertragId: string
daten: AssistentDaten
heuteIso: string
darfSchreiben: boolean
}) {
const [zustand, aktion] = useActionState<Formularzustand, FormData>(kuendigungVermerken, {})
return (
<div className="space-y-6">
{daten.warnungen.length > 0 && (
<Alert variant="warnung">
<TriangleAlert aria-hidden="true" />
<AlertTitle>Vor dem Absenden klären</AlertTitle>
<AlertDescription>
<ul className="ml-4 list-disc space-y-1.5">
{daten.warnungen.map((warnung) => (
<li key={warnung}>{warnung}</li>
))}
</ul>
</AlertDescription>
</Alert>
)}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Scale className="size-5" aria-hidden="true" />
Frist geprüft
</CardTitle>
<CardDescription>
Zu diesem Termin kann als Nächstes gekündigt werden — und bis wann der Brief dafür
raus muss.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{daten.plan.berechenbar ? (
<dl className="grid gap-3 sm:grid-cols-3">
<div className="rounded-md border p-3">
<dt className="text-sm text-muted-foreground">Kündigung zum</dt>
<dd className="text-lg font-semibold">
{formatDatum(daten.plan.naechsterTermin)}
</dd>
</div>
<div className="rounded-md border p-3">
<dt className="text-sm text-muted-foreground">Zugang spätestens</dt>
<dd className="text-lg font-semibold">
{formatDatum(daten.plan.zugangSpaetestensAm)}
</dd>
</div>
<div
className={
daten.plan.verpasst || (daten.plan.tageBisAbsendetag ?? 99) <= 30
? 'rounded-md border border-ampel-rot/40 bg-ampel-rot/5 p-3'
: 'rounded-md border p-3'
}
>
<dt className="text-sm text-muted-foreground">Brief spätestens absenden</dt>
<dd className="text-lg font-semibold">
{formatDatum(daten.plan.spaetesterAbsendetag)}
</dd>
{daten.plan.tageBisAbsendetag !== null && (
<dd className="text-sm text-muted-foreground">
{daten.plan.tageBisAbsendetag < 0
? `vor ${Math.abs(daten.plan.tageBisAbsendetag)} Tagen`
: `in ${daten.plan.tageBisAbsendetag} Tagen`}
</dd>
)}
</div>
</dl>
) : (
<p className="text-sm text-muted-foreground">
Ohne Laufzeitbeginn und Kündigungsfrist lässt sich der Termin nicht berechnen.
</p>
)}
<ul className="space-y-1.5 text-sm text-muted-foreground">
{daten.plan.hinweise.map((hinweis) => (
<li key={hinweis}>{hinweis}</li>
))}
</ul>
</CardContent>
</Card>
<Card
className={
daten.genehmigung.stellen ? 'border-ampel-orange/50 bg-ampel-orange/5' : undefined
}
>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Gavel className="size-5" aria-hidden="true" />
Genehmigungsfrage
</CardTitle>
<CardDescription>{daten.genehmigung.frage}</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-sm">{daten.genehmigung.hinweis}</p>
<Rechtshinweis rechtsgrundlage={daten.genehmigung.rechtsgrundlage} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Kündigungsschreiben</CardTitle>
<CardDescription>
Entwurf zum Kopieren und Bearbeiten. Die App verschickt nichts.
</CardDescription>
</CardHeader>
<CardContent>
<Textbaustein
text={daten.schreiben}
beschriftung="Kündigungsschreiben"
hoehe="min-h-[36rem]"
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileWarning className="size-5" aria-hidden="true" />
Versandnachweis
</CardTitle>
<CardDescription>{daten.versandnachweis.hinweis}</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<ul className="ml-4 list-disc space-y-1 text-sm">
{daten.versandnachweis.moeglichkeiten.map((moeglichkeit) => (
<li key={moeglichkeit}>{moeglichkeit}</li>
))}
</ul>
{darfSchreiben && (
<form action={aktion} 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={zustand} />
<div className="grid gap-4 sm:grid-cols-2">
<Feld
id="k-gekuendigtAm"
beschriftung="Versandt am"
fehler={zustand.felder?.gekuendigtAm}
pflicht
>
<Input name="gekuendigtAm" type="date" defaultValue={heuteIso} required />
</Feld>
<Feld
id="k-kuendigungZum"
beschriftung="Gekündigt zum"
erklaerung="Der Termin aus der Berechnung oben."
fehler={zustand.felder?.kuendigungZum}
>
<Input
name="kuendigungZum"
type="date"
defaultValue={daten.plan.naechsterTermin ?? ''}
/>
</Feld>
<Feld
id="k-versandart"
beschriftung="Versandart"
fehler={zustand.felder?.versandart}
pflicht
className="sm:col-span-2"
>
<Select name="versandart" defaultValue={daten.versandnachweis.moeglichkeiten[0]}>
{daten.versandnachweis.moeglichkeiten.map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</Select>
</Feld>
</div>
<label className="flex items-start gap-2.5">
<Checkbox name="nachweisVorhanden" className="mt-0.5" />
<span className="text-sm">
Der Versandnachweis liegt vor und ist als Dokument hochgeladen.
{zustand.felder?.nachweisVorhanden && (
<span className="mt-1 block font-medium text-destructive">
{zustand.felder.nachweisVorhanden}
</span>
)}
</span>
</label>
<label className="flex items-start gap-2.5">
<Checkbox name="genehmigungGeklaert" className="mt-0.5" />
<span className="text-sm">
Die Genehmigungsfrage ist mit dem Rechtspfleger geklärt (§ 1850 BGB).
</span>
</label>
<Feld id="k-bemerkung" beschriftung="Bemerkung" fehler={zustand.felder?.bemerkung}>
<Textarea
name="bemerkung"
rows={2}
maxLength={2000}
placeholder="z. B. Einlieferungsnummer des Einschreibens"
/>
</Feld>
<Absendeknopf>
<span className="flex items-center gap-2">
<CheckCircle2 className="size-4" aria-hidden="true" />
Kündigung als versandt vermerken
</span>
</Absendeknopf>
<p className="text-sm text-muted-foreground">
Setzt den Status auf „gekündigt“ und legt eine Wiedervorlage an, damit die
Bestätigung der Gesellschaft nicht vergessen wird.
</p>
</form>
)}
</CardContent>
</Card>
</div>
)
}