From d35e24f83011d15008be223ea11ad28b0704afc4 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Sat, 22 Aug 2026 06:22:05 +0200 Subject: [PATCH] feat: Sozialhilfe-Modul mit Nachforderungs-Tracker und Fristpruefung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Etappe 5. Bildet das laufende Verfahren beim Bezirk Oberfranken ab. Rechtsbehelfsfrist: § 187 Abs. 1, § 188 Abs. 2 und 3 BGB, danach § 64 Abs. 3 SGG - hier verschieben Wochenende und Feiertag das Fristende auf den naechsten Werktag. Das ist genau umgekehrt zu den Betreuerfristen, wo bewusst nicht verschoben wird, und deshalb liegt die Feiertagsliste getrennt. Zwei bayerische Sonderfaelle, die haeufig falsch gemacht werden: Mariae Himmelfahrt verschiebt NICHT (Art. 1 Abs. 1 S. 2 BayFTG - der Feiertag gilt nur in ueberwiegend katholischen Gemeinden, Bayreuth ist keine), und der Buss- und Bettag ist in Bayern seit 1995 kein Feiertag mehr. Beide erzeugen stattdessen einen ausdruecklichen Hinweis, damit man die Frist im Zweifel selbst nachrechnet. Der Rechenweg wird als Schrittliste ausgegeben. Heimkosten: Der Dashboard-Agent hatte einen fachlichen Widerspruch gefunden - der einrichtungseinheitliche Eigenanteil auf der Heimrechnung ist bereits um die Leistung der Pflegekasse gekuerzt. Wird sie zusaetzlich als Deckung erfasst, entsteht eine Doppelzaehlung von ueber tausend Euro im Monat. Das Modul rechnet jetzt ausdruecklich nach der Lesart EIGENANTEIL_NETTO, fuehrt die Pflegekassenleistung nur nachrichtlich mit und warnt, wenn sie doch belegt ist. Die Oberflaeche sagt auf einer eigenen Karte, was hier gegen was gerechnet wird. Die Schonvermoegens-Bewertung liefert Argumentationshilfen zum Uebernehmen, keine Automatik - die Einordnung obliegt dem Traeger und gegebenenfalls dem Sozialgericht. Das steht an jeder dieser Karten. Alle jaehrlich veraenderlichen Betraege stehen an einer Stelle mit Stand-Datum und Prueftermin. Die Bekleidungspauschale bewusst ohne Zahl: sie ist regional und steht im Bescheid. 46 Tests. Etappe: 5 Phase: Behoerde --- docs/schema-wuensche/sozialhilfe.md | 243 ++++ .../bescheide/[bescheidId]/page.tsx | 298 ++-- src/app/(app)/sozialhilfe/heimkosten/page.tsx | 701 +++++---- src/app/(app)/sozialhilfe/vermoegen/page.tsx | 461 +++--- .../sozialhilfe/heimkosten-formular.tsx | 636 ++++---- src/lib/sozialhilfe/arten.ts | 61 - src/lib/sozialhilfe/betrag.ts | 67 - src/lib/sozialhilfe/heimkosten.ts | 173 ++- src/lib/sozialhilfe/konstanten.ts | 493 +++---- src/lib/validierung/sozialhilfe.ts | 874 +++++------ tests/sozialhilfe.test.ts | 1275 ++++++++--------- 11 files changed, 2745 insertions(+), 2537 deletions(-) create mode 100644 docs/schema-wuensche/sozialhilfe.md delete mode 100644 src/lib/sozialhilfe/arten.ts delete mode 100644 src/lib/sozialhilfe/betrag.ts diff --git a/docs/schema-wuensche/sozialhilfe.md b/docs/schema-wuensche/sozialhilfe.md new file mode 100644 index 0000000..0b37977 --- /dev/null +++ b/docs/schema-wuensche/sozialhilfe.md @@ -0,0 +1,243 @@ +# Schema-Wünsche aus Etappe 5 „Behörde“ + +Das Schema wurde nicht angefasst. Was beim Bauen des Sozialhilfemoduls +gefehlt hat, steht hier — nach Dringlichkeit sortiert, jeweils mit dem +Behelf, der jetzt an seiner Stelle steht. + +--- + +## 1. `Heimkostenmonat`: Kostenseite und Deckungsseite überschneiden sich — wichtig + +Dieselbe Sache wie Punkt 1 in `docs/schema-wuensche/dashboard.md`, hier aus +Sicht des Moduls, das die Gegenüberstellung baut. + +**Das Problem.** `pflegeEigenanteilCent` heißt „einrichtungseinheitlicher +Eigenanteil". Der ist definitionsgemäß **schon um die Pauschale der +Pflegekasse nach § 43 Abs. 2 SGB XI gekürzt**. Steht in derselben Zeile +zusätzlich `pflegekasseCent` als Deckung, wird dasselbe Geld zweimal +gezählt — die Deckungslücke fällt um die Pauschale zu klein aus, bei +Pflegegrad 3 um 1.319 Euro im Monat. + +**Was ich entschieden habe.** Dieses Modul rechnet in der Lesart +`EIGENANTEIL_NETTO` (siehe Kopfkommentar in +`src/lib/sozialhilfe/heimkosten.ts`): + +``` +Bedarf = pflegeEigenanteil (netto, wie auf der Heimrechnung) + + unterkunftVerpflegung + investitionskosten + + ausbildungsumlage + zusatzleistungen + + barbetrag + bekleidungspauschale + +Deckung = leistungszuschlag + rente + sonstigeEinkuenfte + + sozialhilfe + eigenmittel + +Deckungslücke = Bedarf − Deckung +``` + +`pflegekasseCent` zählt **nicht** als Deckung. Der Wert wird als +`pflegekasseNachrichtlichCent` mitgeführt und angezeigt; ist er ungleich +null, gibt `heimkostenAuswerten()` einen Hinweis aus, der die +Doppelzählung erklärt. Das Formular belegt das Feld nicht mehr vor. + +`leistungszuschlagCent` zählt dagegen **sehr wohl** als Deckung: Der +Zuschlag nach § 43c SGB XI mindert den Eigenanteil erst danach und steht +auf der Heimrechnung als eigene Abzugsposition. + +Die Alternative — durchgängig den Bruttosatz erfassen — ist als Lesart +`PFLEGESATZ_BRUTTO` implementiert und getestet: Bei konsistenter Erfassung +kommen beide Lesarten auf dieselbe Deckungslücke. Benutzt wird sie in der +Oberfläche nicht, weil das Schema keinen Ort hat, um die Wahl zu +speichern. + +**Wunsch, in dieser Reihenfolge:** + +1. Den Kommentar am Feld schärfen, damit die Lesart im Schema steht und + nicht nur hier: + +```prisma +/// NETTO: der einrichtungseinheitliche Eigenanteil, wie er auf der +/// Heimrechnung steht - bereits um § 43 Abs. 2 SGB XI gekuerzt, noch +/// nicht um den Leistungszuschlag. pflegekasseCent zaehlt deshalb NICHT +/// als Deckung, sondern nur nachrichtlich. +pflegeEigenanteilCent BigInt @default(0) +``` + +2. Wenn beide Lesarten gebraucht werden, ein Feld dafür — sonst muss jedes + Modul raten, was in der Zeile steht: + +```prisma +enum Kostenlesart { + EIGENANTEIL_NETTO + PFLEGESATZ_BRUTTO +} + +lesart Kostenlesart @default(EIGENANTEIL_NETTO) +``` + +3. Alternativ ein `heimrechnungGesamtCent`, das schlicht den + Rechnungsbetrag trägt; die Aufteilung wäre dann nur noch informativ und + die Summe könnte gegen sie geprüft werden. + +**Verabredung bis dahin**: Wer `Heimkostenmonat` schreibt — dieses Modul, +das Dashboard, ein Import — trägt als `pflegeEigenanteilCent` den Netto- +Eigenanteil ein und lässt `pflegekasseCent` auf 0. + +--- + +## 2. `Nachforderung.checklistenSchluessel` — wichtig + +```prisma +/// Stabiler Schluessel aus src/lib/sozialhilfe/checkliste.ts. +/// Verhindert Dubletten beim erneuten Uebernehmen der Standardunterlagen. +checklistenSchluessel String? + +@@unique([antragId, checklistenSchluessel]) +``` + +**Warum**: `ausCheckliste: Boolean` merkt sich nur *dass* eine Zeile aus der +Checkliste kam, nicht *welche* Position. Um beim zweiten Aufruf des Panels +nicht dieselbe Unterlage noch einmal anzulegen, vergleicht +`nochNichtUebernommen()` derzeit über die **Bezeichnung** (kleingeschrieben, +getrimmt). Das reicht, solange niemand die Bezeichnung ändert — tut er es, +entsteht beim nächsten Übernehmen eine Dublette. + +**Behelf**: Vergleich über die Bezeichnung in +`src/lib/sozialhilfe/checkliste.ts` und in `checklisteUebernehmen()`. + +--- + +## 3. Rückkaufswert am Vertrag — wichtig + +```prisma +model Vertrag { + // ... + rueckkaufswertCent BigInt? + rueckkaufswertStichtag DateTime? @db.Date +} +``` + +**Warum**: Die Bewertungsansicht stellt Vermögensposten und Verträge +nebeneinander. Ein Vermögensposten trägt seinen Wert über `Wertstand`, +ein Vertrag hat gar kein Wertfeld — nur `beitragCent`, und das ist die +Prämie, nicht der Wert. Verträge erscheinen deshalb ohne Betrag, und die +Summe „verwertbar“ ist unvollständig, solange nicht zu jeder Versicherung +zusätzlich ein Vermögensposten angelegt wurde. + +Für das Sozialhilfeverfahren ist genau dieser Wert der entscheidende: Der +Träger fragt nach Rückkaufswerten, nicht nach Versicherungssummen. + +**Behelf**: Die Bewertungsansicht zeigt nur Verträge, die überhaupt einen +Wert haben können (`MIT_RUECKKAUFSWERT` aus +`src/lib/vertraege/konstanten.ts`), und markiert sie mit „Wert noch nicht +ermittelt“. Wer den Betrag braucht, legt zusätzlich einen +`Vermoegensposten` mit `vertragId` und einem `Wertstand` an — dann wird der +Vertrag in der Liste ausgeblendet, damit die Summe nicht doppelt zählt. + +--- + +## 4. `Bescheid.rechtsbehelfsbelehrungVorhanden` — mittel + +```prisma +/// Ausdrueckliche Einschaetzung, nicht aus der Fristlaenge abgeleitet. +rechtsbehelfsbelehrungVorhanden Boolean @default(true) +rechtsbehelfsbelehrungBemerkung String? +``` + +**Warum**: Ob die Jahresfrist des § 66 Abs. 2 SGG gilt, ist eine bewusste +Einschätzung mit Begründung („die Belehrung nennt die falsche Behörde“). +Derzeit wird sie aus `rechtsbehelfsfristMonate === 12` zurückgeschlossen — +das trägt, verliert aber die Begründung, und die bräuchte man genau dann, +wenn man sich im Widerspruchsverfahren darauf beruft. + +**Behelf**: `belehrungZuMonaten()` in +`src/lib/sozialhilfe/rechtsbehelf.ts`. Die Begründung gehört bis auf +Weiteres in `notizenVerschluesselt`. + +--- + +## 5. `Bescheid.widerspruchFristId` → `Frist` — mittel + +```prisma +widerspruchFristId String? @unique +widerspruchFrist Frist? @relation(fields: [widerspruchFristId], references: [id], onDelete: SetNull) +``` + +**Warum**: Die angelegte Frist „Widerspruch möglich bis“ wird derzeit über +`Frist.systemSchluessel = "sozialhilfe-widerspruch-"` gefunden. +Das funktioniert (`@@unique([betreuteId, systemSchluessel])` trägt es), ist +aber eine Verknüpfung über eine zusammengesetzte Zeichenkette. Ein echter +Fremdschlüssel wäre sauberer und ließe die Frist beim Löschen des Bescheids +mitziehen. Dasselbe gilt für +`"sozialhilfe-nachforderung-"`. + +**Behelf**: `systemSchluessel` mit festem Präfix, `upsert` statt `create`. + +--- + +## 6. `Heimkostenmonat` — Feld für die tatsächliche Zahlung — mittel + +```prisma +/// Was tatsaechlich vom Konto ging - fuer den Abgleich mit dem Bescheid. +gezahltCent BigInt? +gezahltAm DateTime? @db.Date +rechnungsnummer String? +``` + +**Warum**: Die Gegenüberstellung rechnet Bedarf gegen Deckung. Was +tatsächlich abgebucht wurde, steht nirgends — und genau die Differenz +zwischen „laut Bescheid“ und „tatsächlich abgebucht“ ist der Anlass, beim +Träger nachzufragen. + +**Behelf**: `bemerkung`. + +--- + +## 7. `Heimkostenmonat.abwesenheitsminderungCent` — klein + +```prisma +/// Tatsaechliche Minderung laut Heimrechnung, nicht die Schaetzung. +abwesenheitsminderungCent BigInt @default(0) +``` + +**Warum**: `abwesenheitstage` ist da, die Wirkung schätzt +`abwesenheitSchaetzen()` nach dem Rahmenvertrag (drei Karenztage, danach +25 % auf Pflegeanteil sowie Unterkunft und Verpflegung). Was die +Einrichtung wirklich abgezogen hat, steht auf der Rechnung und ließe sich +nicht festhalten. + +**Behelf**: Die Schätzung wird ausdrücklich als Schätzung ausgewiesen und +neben die erfasste Lücke gestellt, nicht in sie hineingerechnet. + +--- + +## 8. `Leistung.wiederholungsfristMonat/-tag` — klein + +```prisma +/// Fuer jaehrlich zu wiederholende Antraege: Stichtag im Jahr. +antragsschlussMonat Int? +antragsschlussTag Int? +``` + +**Warum**: `jaehrlichNeuBeantragen: Boolean` sagt, *dass* wiederholt werden +muss, nicht *bis wann*. Beim Landespflegegeld ist der 31.12. eine +Ausschlussfrist — daraus ließe sich automatisch eine Frist ableiten, +statt sie von Hand anzulegen. + +**Behelf**: `antragsschluss` als Text im Katalog +(`src/lib/sozialhilfe/leistungskatalog.ts`) und ein Hinweis in der +Oberfläche. Die Fristableitung übernimmt `pflichtfristenAbleiten()` mit +`landespflegegeld-` (siehe `docs/SCHNITTSTELLEN.md`). + +--- + +## 9. `Nachforderung.notizenVerschluesselt` — klein + +**Warum**: `erlaeuterung` und `begruendungNichtVorhanden` liegen im +Klartext. Beides ist meist harmlos („Grundbuchauszug beim Amtsgericht +angefordert“), kann aber Gesundheits- oder Vermögensangaben enthalten +(„Rückkaufswert 12.480 Euro, Police verpfändet“). Wäre mindestens die +Begründung verschlüsselt, wäre das konsequenter zum Rest des Schemas. + +**Behelf**: keiner. Die Felder werden benutzt, wie sie sind. Falls sie +verschlüsselt werden sollen, gehört `Nachforderung` in +`VERSCHLUESSELTE_FELDER` in `src/lib/crypto/feld.ts`. diff --git a/src/app/(app)/sozialhilfe/[antragId]/bescheide/[bescheidId]/page.tsx b/src/app/(app)/sozialhilfe/[antragId]/bescheide/[bescheidId]/page.tsx index a40197b..01c4ee5 100644 --- a/src/app/(app)/sozialhilfe/[antragId]/bescheide/[bescheidId]/page.tsx +++ b/src/app/(app)/sozialhilfe/[antragId]/bescheide/[bescheidId]/page.tsx @@ -1,149 +1,149 @@ -import { notFound } from 'next/navigation' -import { Trash2 } from 'lucide-react' -import { SeitenKopf } from '@/components/seiten-kopf' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { AktionMitRueckfrage } from '@/components/sozialhilfe/bausteine' -import { BescheidFormular } from '@/components/sozialhilfe/bescheid-formular' -import { BescheidPruefung } from '@/components/sozialhilfe/bescheid-pruefung' -import { oeffnen } from '@/lib/crypto/feld' -import { db, nichtGeloescht } from '@/lib/db' -import { alsIsoTag, heuteAlsTag } from '@/lib/fristen/datum' -import { BESCHEID_ART_BESCHRIFTUNG } from '@/lib/sozialhilfe/bescheide' -import { centZuEingabe } from '@/lib/sozialhilfe/betrag' -import { - fristAmpel, - rechtsbehelfsfristBerechnen, - zugangsvermutung, -} from '@/lib/sozialhilfe/rechtsbehelf' -import { sozialhilfeKontext } from '@/lib/sozialhilfe/zugang' -import { bescheidLoeschen } from '@/server/actions/sozialhilfe' -import { formatDatum } from '@/lib/utils' - -export const dynamic = 'force-dynamic' - -/** - * Bescheid-Prüfung. - * - * Der Rechenweg der Widerspruchsfrist steht ausgeschrieben da - die - * Anwendung kann sich irren, und wer eine Frist verpasst, soll sehen - * können, wo die Rechnung abgebogen ist. - */ -export default async function BescheidSeite({ - params, -}: { - params: Promise<{ antragId: string; bescheidId: string }> -}) { - const { antragId, bescheidId } = await params - const { betreuteId, darfSchreiben } = await sozialhilfeKontext() - - const bescheid = await db.bescheid.findFirst({ - where: { id: bescheidId, antragId, antrag: { betreuteId }, ...nichtGeloescht }, - include: { antrag: { select: { id: true, traeger: true, aktenzeichen: true } } }, - }) - if (!bescheid) notFound() - - const offen = oeffnen('Bescheid', bescheid) - const heute = heuteAlsTag() - - const ergebnis = rechtsbehelfsfristBerechnen({ - zugegangenAm: bescheid.zugegangenAm, - bescheidVom: bescheid.bescheidVom, - fristMonate: bescheid.rechtsbehelfsfristMonate, - heute, - }) - - const vorschlag = zugangsvermutung(bescheid.bescheidVom) - - return ( -
- - {bescheid.antrag.traeger} - {bescheid.antrag.aktenzeichen ? ` · Az. ${bescheid.antrag.aktenzeichen}` : ''} · Zugang{' '} - {formatDatum(bescheid.zugegangenAm)} - - } - pfad={[ - { titel: 'Start', href: '/heute' }, - { titel: 'Sozialhilfe', href: '/sozialhilfe' }, - { titel: 'Antragsakte', href: `/sozialhilfe/${antragId}` }, - { titel: 'Bescheid' }, - ]} - /> - - - - {offen.notizen && ( - - - Notizen - Verschlüsselt gespeichert. - - -

{offen.notizen}

-
-
- )} - - {darfSchreiben && ( -
-

Bescheid bearbeiten

- - -
- - -
-
- )} -
- ) -} +import { notFound } from 'next/navigation' +import { Trash2 } from 'lucide-react' +import { SeitenKopf } from '@/components/seiten-kopf' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { AktionMitRueckfrage } from '@/components/sozialhilfe/bausteine' +import { BescheidFormular } from '@/components/sozialhilfe/bescheid-formular' +import { BescheidPruefung } from '@/components/sozialhilfe/bescheid-pruefung' +import { oeffnen } from '@/lib/crypto/feld' +import { db, nichtGeloescht } from '@/lib/db' +import { alsIsoTag, heuteAlsTag } from '@/lib/fristen/datum' +import { BESCHEID_ART_BESCHRIFTUNG } from '@/lib/sozialhilfe/bescheide' +import { betragFormatieren } from '@/lib/finanzen/betrag' +import { + fristAmpel, + rechtsbehelfsfristBerechnen, + zugangsvermutung, +} from '@/lib/sozialhilfe/rechtsbehelf' +import { sozialhilfeKontext } from '@/lib/sozialhilfe/zugang' +import { bescheidLoeschen } from '@/server/actions/sozialhilfe' +import { formatDatum } from '@/lib/utils' + +export const dynamic = 'force-dynamic' + +/** + * Bescheid-Prüfung. + * + * Der Rechenweg der Widerspruchsfrist steht ausgeschrieben da - die + * Anwendung kann sich irren, und wer eine Frist verpasst, soll sehen + * können, wo die Rechnung abgebogen ist. + */ +export default async function BescheidSeite({ + params, +}: { + params: Promise<{ antragId: string; bescheidId: string }> +}) { + const { antragId, bescheidId } = await params + const { betreuteId, darfSchreiben } = await sozialhilfeKontext() + + const bescheid = await db.bescheid.findFirst({ + where: { id: bescheidId, antragId, antrag: { betreuteId }, ...nichtGeloescht }, + include: { antrag: { select: { id: true, traeger: true, aktenzeichen: true } } }, + }) + if (!bescheid) notFound() + + const offen = oeffnen('Bescheid', bescheid) + const heute = heuteAlsTag() + + const ergebnis = rechtsbehelfsfristBerechnen({ + zugegangenAm: bescheid.zugegangenAm, + bescheidVom: bescheid.bescheidVom, + fristMonate: bescheid.rechtsbehelfsfristMonate, + heute, + }) + + const vorschlag = zugangsvermutung(bescheid.bescheidVom) + + return ( +
+ + {bescheid.antrag.traeger} + {bescheid.antrag.aktenzeichen ? ` · Az. ${bescheid.antrag.aktenzeichen}` : ''} · Zugang{' '} + {formatDatum(bescheid.zugegangenAm)} + + } + pfad={[ + { titel: 'Start', href: '/heute' }, + { titel: 'Sozialhilfe', href: '/sozialhilfe' }, + { titel: 'Antragsakte', href: `/sozialhilfe/${antragId}` }, + { titel: 'Bescheid' }, + ]} + /> + + + + {offen.notizen && ( + + + Notizen + Verschlüsselt gespeichert. + + +

{offen.notizen}

+
+
+ )} + + {darfSchreiben && ( +
+

Bescheid bearbeiten

+ + +
+ + +
+
+ )} +
+ ) +} diff --git a/src/app/(app)/sozialhilfe/heimkosten/page.tsx b/src/app/(app)/sozialhilfe/heimkosten/page.tsx index 256b71f..0ef689c 100644 --- a/src/app/(app)/sozialhilfe/heimkosten/page.tsx +++ b/src/app/(app)/sozialhilfe/heimkosten/page.tsx @@ -1,318 +1,383 @@ -import Link from 'next/link' -import { SeitenKopf } from '@/components/seiten-kopf' -import { Rechtshinweis } from '@/components/rechtshinweis' -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { HeimkostenFormular } from '@/components/sozialhilfe/heimkosten-formular' -import { HeimkostenTabelle, type MonatsZeile } from '@/components/sozialhilfe/heimkosten-tabelle' -import { centZuEingabe } from '@/lib/sozialhilfe/betrag' -import { db, nichtGeloescht } from '@/lib/db' -import { heuteAlsTag } from '@/lib/fristen/datum' -import { - BARBETRAG_CENT, - BEKLEIDUNGSPAUSCHALE_HINWEIS, - KONSTANTEN_NAECHSTE_PRUEFUNG, - KONSTANTEN_STAND, -} from '@/lib/sozialhilfe/konstanten' -import { - heimkostenAuswerten, - jahresverlaufBilden, - leistungszuschlagProzent, - leistungszuschlagStufe, - monatsname, - pflegekassePauschaleCent, - pflegekasseRechtsgrundlage, - verweildauerMonate, -} from '@/lib/sozialhilfe/heimkosten' -import { sozialhilfeKontext } from '@/lib/sozialhilfe/zugang' -import { formatCent } from '@/lib/utils' -import { Info, TrendingDown } from 'lucide-react' - -export const dynamic = 'force-dynamic' - -/** - * Heimkosten-Gegenüberstellung. - * - * Die eine Zahl, um die es geht, ist die Deckungslücke: Was bleibt übrig, - * nachdem Pflegekasse, Rente und Sozialhilfe gezahlt haben? Sie steht je - * Monat und als Jahressumme da - denn eine Lücke von 180 Euro im Monat - * sind über ein Jahr mehr als zweitausend. - */ -export default async function HeimkostenSeite({ - searchParams, -}: { - searchParams: Promise<{ jahr?: string }> -}) { - const { betreuteId, betreute } = await sozialhilfeKontext() - const parameter = await searchParams - - const heute = heuteAlsTag() - const gewaehltesJahr = Number.parseInt(parameter.jahr ?? '', 10) - const jahr = Number.isFinite(gewaehltesJahr) ? gewaehltesJahr : heute.getUTCFullYear() - - const [monate, heimvertrag] = await Promise.all([ - db.heimkostenmonat.findMany({ - where: { betreuteId, jahr }, - orderBy: { monat: 'asc' }, - }), - db.heimvertrag.findUnique({ where: { betreuteId } }), - ]) - - const ergebnisse = monate.map((m) => - heimkostenAuswerten({ - jahr: m.jahr, - monat: m.monat, - pflegeEigenanteilCent: m.pflegeEigenanteilCent, - unterkunftVerpflegungCent: m.unterkunftVerpflegungCent, - investitionskostenCent: m.investitionskostenCent, - ausbildungsumlageCent: m.ausbildungsumlageCent, - zusatzleistungenCent: m.zusatzleistungenCent, - pflegekasseCent: m.pflegekasseCent, - leistungszuschlagCent: m.leistungszuschlagCent, - renteCent: m.renteCent, - sonstigeEinkuenfteCent: m.sonstigeEinkuenfteCent, - sozialhilfeCent: m.sozialhilfeCent, - eigenmittelCent: m.eigenmittelCent, - barbetragCent: m.barbetragCent, - bekleidungspauschaleCent: m.bekleidungspauschaleCent, - abwesenheitstage: m.abwesenheitstage, - }), - ) - - const verlauf = jahresverlaufBilden(jahr, ergebnisse) - - const zeilen: MonatsZeile[] = Array.from({ length: 12 }, (_, i) => { - const monat = i + 1 - const ergebnis = ergebnisse.find((e) => e.monat === monat) - const roh = monate.find((m) => m.monat === monat) - return { - jahr, - monat, - heimkostenCent: ergebnis?.heimkostenCent ?? 0n, - gesamtbedarfCent: ergebnis?.gesamtbedarfCent ?? 0n, - deckungCent: ergebnis?.deckungCent ?? 0n, - deckungslueckeCent: ergebnis?.deckungslueckeCent ?? 0n, - ueberdeckungCent: ergebnis?.ueberdeckungCent ?? 0n, - abwesenheitstage: roh?.abwesenheitstage ?? 0, - erfasst: ergebnis !== undefined, - } - }) - - // Vorschläge fürs Formular: Pauschale der Pflegekasse zum Pflegegrad - // und der gesetzliche Mindest-Barbetrag. - const pauschale = pflegekassePauschaleCent(betreute.pflegegrad) - const vorschlagPflegekasse = pauschale - ? { - betrag: centZuEingabe(pauschale), - text: `Pflegegrad ${betreute.pflegegrad}: ${formatCent(pauschale)} monatlich (${pflegekasseRechtsgrundlage(betreute.pflegegrad)}, Stand ${KONSTANTEN_STAND}).`, - } - : null - - const einzug = heimvertrag?.einzugAm ?? betreute.heimEinzugAm ?? null - const verweildauer = einzug ? verweildauerMonate(einzug, heute) : null - - const naechsterOffenerMonat = - zeilen.find((z) => !z.erfasst && z.monat <= (jahr === heute.getUTCFullYear() ? heute.getUTCMonth() + 1 : 12)) - ?.monat ?? (jahr === heute.getUTCFullYear() ? heute.getUTCMonth() + 1 : 1) - - const jahre = Array.from({ length: 6 }, (_, i) => heute.getUTCFullYear() - 3 + i) - - return ( -
- - {jahre.map((j) => ( - - {j} - - ))} - - } - /> - -
- - -

Bedarf {jahr}

-

- {formatCent(verlauf.summeBedarfCent)} -

-
-
- - -

Deckung {jahr}

-

- {formatCent(verlauf.summeDeckungCent)} -

-
-
- 0n ? 'border-ampel-rot/40' : undefined}> - -

Deckungslücke {jahr}

-

0n ? 'text-ampel-rot' : 'text-ampel-gruen' - }`} - > - {formatCent(verlauf.summeDeckungslueckeCent)} -

-
-
-
- - {verlauf.groessteLuecke && ( - - - )} - - - - Jahresverlauf - - Gesamtbedarf ist die Heimrechnung plus Barbetrag und Bekleidungspauschale. - - - - - - - - {ergebnisse.some((e) => e.hinweise.length > 0) && ( - - - Was auffällt - - -
    - {ergebnisse.flatMap((e) => - e.hinweise.map((hinweis, i) => ( -
  • - {monatsname(e.monat)}: - {hinweis} -
  • - )), - )} -
-
-
- )} - - {ergebnisse.some((e) => e.abwesenheit.abwesenheitstage > 0) && ( - - - Abwesenheitstage - - Was die Abwesenheit voraussichtlich bringt — eine Schätzung nach dem Rahmenvertrag, - nicht die Abrechnung. - - - -
    - {ergebnisse - .filter((e) => e.abwesenheit.abwesenheitstage > 0) - .map((e) => ( -
  • - {monatsname(e.monat)}: - {e.abwesenheit.erklaerung} Geschätzte Minderung{' '} - - {formatCent(e.abwesenheit.geschaetzteMinderungCent)} - - , die Lücke sänke damit auf{' '} - - {formatCent(e.deckungslueckeNachAbwesenheitCent)} - - . -
  • - ))} -
- -
-
- )} - - - - -
-

Monat erfassen oder ändern

- -
- - -
- ) -} +import Link from 'next/link' +import { SeitenKopf } from '@/components/seiten-kopf' +import { Rechtshinweis } from '@/components/rechtshinweis' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { HeimkostenFormular } from '@/components/sozialhilfe/heimkosten-formular' +import { HeimkostenTabelle, type MonatsZeile } from '@/components/sozialhilfe/heimkosten-tabelle' +import { betragFormatieren } from '@/lib/finanzen/betrag' +import { db, nichtGeloescht } from '@/lib/db' +import { heuteAlsTag } from '@/lib/fristen/datum' +import { + BARBETRAG_CENT, + BEKLEIDUNGSPAUSCHALE_HINWEIS, + KONSTANTEN_NAECHSTE_PRUEFUNG, + KONSTANTEN_STAND, +} from '@/lib/sozialhilfe/konstanten' +import { + heimkostenAuswerten, + jahresverlaufBilden, + KOSTENLESART_BESCHRIFTUNG, + KOSTENLESART_ERKLAERUNG, + KOSTENLESART_STANDARD, + leistungszuschlagProzent, + leistungszuschlagStufe, + monatsname, + pflegekassePauschaleCent, + pflegekasseRechtsgrundlage, + verweildauerMonate, +} from '@/lib/sozialhilfe/heimkosten' +import { sozialhilfeKontext } from '@/lib/sozialhilfe/zugang' +import { formatCent } from '@/lib/utils' +import { Info, TrendingDown } from 'lucide-react' + +export const dynamic = 'force-dynamic' + +/** + * Heimkosten-Gegenüberstellung. + * + * Die eine Zahl, um die es geht, ist die Deckungslücke: Was bleibt übrig, + * nachdem Pflegekasse, Rente und Sozialhilfe gezahlt haben? Sie steht je + * Monat und als Jahressumme da - denn eine Lücke von 180 Euro im Monat + * sind über ein Jahr mehr als zweitausend. + */ +export default async function HeimkostenSeite({ + searchParams, +}: { + searchParams: Promise<{ jahr?: string }> +}) { + const { betreuteId, betreute } = await sozialhilfeKontext() + const parameter = await searchParams + + const heute = heuteAlsTag() + const gewaehltesJahr = Number.parseInt(parameter.jahr ?? '', 10) + const jahr = Number.isFinite(gewaehltesJahr) ? gewaehltesJahr : heute.getUTCFullYear() + + const [monate, heimvertrag] = await Promise.all([ + db.heimkostenmonat.findMany({ + where: { betreuteId, jahr }, + orderBy: { monat: 'asc' }, + }), + db.heimvertrag.findUnique({ where: { betreuteId } }), + ]) + + const ergebnisse = monate.map((m) => + heimkostenAuswerten({ + jahr: m.jahr, + monat: m.monat, + pflegeEigenanteilCent: m.pflegeEigenanteilCent, + unterkunftVerpflegungCent: m.unterkunftVerpflegungCent, + investitionskostenCent: m.investitionskostenCent, + ausbildungsumlageCent: m.ausbildungsumlageCent, + zusatzleistungenCent: m.zusatzleistungenCent, + pflegekasseCent: m.pflegekasseCent, + leistungszuschlagCent: m.leistungszuschlagCent, + renteCent: m.renteCent, + sonstigeEinkuenfteCent: m.sonstigeEinkuenfteCent, + sozialhilfeCent: m.sozialhilfeCent, + eigenmittelCent: m.eigenmittelCent, + barbetragCent: m.barbetragCent, + bekleidungspauschaleCent: m.bekleidungspauschaleCent, + abwesenheitstage: m.abwesenheitstage, + }), + ) + + const verlauf = jahresverlaufBilden(jahr, ergebnisse) + + const zeilen: MonatsZeile[] = Array.from({ length: 12 }, (_, i) => { + const monat = i + 1 + const ergebnis = ergebnisse.find((e) => e.monat === monat) + const roh = monate.find((m) => m.monat === monat) + return { + jahr, + monat, + heimkostenCent: ergebnis?.heimkostenCent ?? 0n, + gesamtbedarfCent: ergebnis?.gesamtbedarfCent ?? 0n, + deckungCent: ergebnis?.deckungCent ?? 0n, + deckungslueckeCent: ergebnis?.deckungslueckeCent ?? 0n, + ueberdeckungCent: ergebnis?.ueberdeckungCent ?? 0n, + abwesenheitstage: roh?.abwesenheitstage ?? 0, + erfasst: ergebnis !== undefined, + } + }) + + // Vorschläge fürs Formular: Pauschale der Pflegekasse zum Pflegegrad + // und der gesetzliche Mindest-Barbetrag. + const pauschale = pflegekassePauschaleCent(betreute.pflegegrad) + + // Bewusst nur ein Hinweis, kein Vorschlagswert zum Uebernehmen: Unter der + // Lesart EIGENANTEIL_NETTO steckt die Pauschale schon im Eigenanteil. + // Wer sie hier zusaetzlich eintraegt, zaehlt dasselbe Geld zweimal. + const hinweisPflegekasse = pauschale + ? `Pflegegrad ${betreute.pflegegrad}: ${formatCent(pauschale)} monatlich ` + + `(${pflegekasseRechtsgrundlage(betreute.pflegegrad)}, Stand ${KONSTANTEN_STAND}). ` + + `Dieser Betrag ist im Eigenanteil oben bereits abgezogen — lass das Feld deshalb auf 0. ` + + `Es dient nur der Erinnerung, was die Pflegekasse direkt an die Einrichtung zahlt.` + : 'Pauschale nach § 43 SGB XI. Sie ist im Eigenanteil bereits abgezogen — lass das Feld ' + + 'auf 0. Hinterlege den Pflegegrad in den Stammdaten, dann steht hier der Betrag.' + + const einzug = heimvertrag?.einzugAm ?? betreute.heimEinzugAm ?? null + const verweildauer = einzug ? verweildauerMonate(einzug, heute) : null + + const naechsterOffenerMonat = + zeilen.find((z) => !z.erfasst && z.monat <= (jahr === heute.getUTCFullYear() ? heute.getUTCMonth() + 1 : 12)) + ?.monat ?? (jahr === heute.getUTCFullYear() ? heute.getUTCMonth() + 1 : 1) + + const jahre = Array.from({ length: 6 }, (_, i) => heute.getUTCFullYear() - 3 + i) + + return ( +
+ + {jahre.map((j) => ( + + {j} + + ))} + + } + /> + +
+ + +

Bedarf {jahr}

+

+ {formatCent(verlauf.summeBedarfCent)} +

+
+
+ + +

Deckung {jahr}

+

+ {formatCent(verlauf.summeDeckungCent)} +

+
+
+ 0n ? 'border-ampel-rot/40' : undefined}> + +

Deckungslücke {jahr}

+

0n ? 'text-ampel-rot' : 'text-ampel-gruen' + }`} + > + {formatCent(verlauf.summeDeckungslueckeCent)} +

+
+
+
+ + {verlauf.groessteLuecke && ( + + + )} + + + + Was hier gegen was gerechnet wird + + Lesart: {KOSTENLESART_BESCHRIFTUNG[KOSTENLESART_STANDARD]}. Ohne diese Festlegung + lässt sich die Rechnung nicht mit der Heimrechnung vergleichen. + + + +

{KOSTENLESART_ERKLAERUNG[KOSTENLESART_STANDARD]}

+ +
+
+

Bedarf

+
    +
  • einrichtungseinheitlicher Eigenanteil (bereits netto)
  • +
  • Unterkunft und Verpflegung
  • +
  • Investitionskosten
  • +
  • Ausbildungsumlage
  • +
  • Zusatzleistungen
  • +
  • Barbetrag (§ 27b Abs. 2 SGB XII)
  • +
  • Bekleidungspauschale
  • +
+
+
+

Deckung

+
    +
  • Leistungszuschlag (§ 43c SGB XI)
  • +
  • Rente
  • +
  • sonstige Einkünfte
  • +
  • Anteil der Sozialhilfe
  • +
  • Eigenmittel
  • +
+

+ Nicht dabei: die Pauschale der Pflegekasse + nach § 43 Abs. 2 SGB XI. Sie ist im Eigenanteil schon abgezogen und würde hier ein + zweites Mal zählen. +

+
+
+ +

+ Barbetrag und Bekleidungspauschale stehen bewusst auf der Bedarfsseite: Sie müssen der + betreuten Person tatsächlich verbleiben. Wer sie herauslässt, hält die Deckung für + besser, als sie ist. +

+ + +
+
+ + + + Jahresverlauf + + Gesamtbedarf ist die Heimrechnung plus Barbetrag und Bekleidungspauschale. + + + + + + + + {ergebnisse.some((e) => e.hinweise.length > 0) && ( + + + Was auffällt + + +
    + {ergebnisse.flatMap((e) => + e.hinweise.map((hinweis, i) => ( +
  • + {monatsname(e.monat)}: + {hinweis} +
  • + )), + )} +
+
+
+ )} + + {ergebnisse.some((e) => e.abwesenheit.abwesenheitstage > 0) && ( + + + Abwesenheitstage + + Was die Abwesenheit voraussichtlich bringt — eine Schätzung nach dem Rahmenvertrag, + nicht die Abrechnung. + + + +
    + {ergebnisse + .filter((e) => e.abwesenheit.abwesenheitstage > 0) + .map((e) => ( +
  • + {monatsname(e.monat)}: + {e.abwesenheit.erklaerung} Geschätzte Minderung{' '} + + {formatCent(e.abwesenheit.geschaetzteMinderungCent)} + + , die Lücke sänke damit auf{' '} + + {formatCent(e.deckungslueckeNachAbwesenheitCent)} + + . +
  • + ))} +
+ +
+
+ )} + + + + +
+

Monat erfassen oder ändern

+ +
+ + +
+ ) +} + +/** Cent fuer ein Eingabefeld, null wird zu leer. */ +function eingabefeld(cent: bigint | null): string { + return cent === null ? '' : betragFormatieren(cent) +} diff --git a/src/app/(app)/sozialhilfe/vermoegen/page.tsx b/src/app/(app)/sozialhilfe/vermoegen/page.tsx index 1ae9ecd..46926df 100644 --- a/src/app/(app)/sozialhilfe/vermoegen/page.tsx +++ b/src/app/(app)/sozialhilfe/vermoegen/page.tsx @@ -1,229 +1,232 @@ -import { SeitenKopf } from '@/components/seiten-kopf' -import { Rechtshinweis } from '@/components/rechtshinweis' -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' -import { Card, CardContent } from '@/components/ui/card' -import { Vermoegensliste, type ListenPosten } from '@/components/sozialhilfe/vermoegensliste' -import { oeffnen } from '@/lib/crypto/feld' -import { centZuZahl, db, nichtGeloescht } from '@/lib/db' -import { alsIsoTag } from '@/lib/fristen/datum' -import { VERMOEGENSART_BESCHRIFTUNG, VERMOEGENSRELEVANTE_VERTRAGSARTEN, VERTRAGSART_BESCHRIFTUNG } from '@/lib/sozialhilfe/arten' -import { BEWERTUNG_BESCHRIFTUNG } from '@/lib/sozialhilfe/schonvermoegen' -import { freibetragVergleichen, uebersichtBilden } from '@/lib/sozialhilfe/vermoegensuebersicht' -import { sozialhilfeKontext } from '@/lib/sozialhilfe/zugang' -import { formatCent } from '@/lib/utils' -import { AlertCircle } from 'lucide-react' - -export const dynamic = 'force-dynamic' - -/** - * Vermögens-Bewertungsansicht. - * - * Sie zählt zusammen und stellt gegenüber - sie entscheidet nichts. Was - * geschützt ist, sagt der Träger; bleibt man uneins, das Sozialgericht. - * Deshalb heißt die Spalte "Einschätzung" und nicht "Bewertung". - */ -export default async function VermoegensbewertungSeite() { - const { betreuteId, darfSchreiben } = await sozialhilfeKontext() - - const [posten, vertraege] = await Promise.all([ - db.vermoegensposten.findMany({ - where: { betreuteId, ...nichtGeloescht }, - include: { wertstaende: { orderBy: { stichtag: 'desc' }, take: 1 } }, - orderBy: { bezeichnung: 'asc' }, - }), - db.vertrag.findMany({ - where: { - betreuteId, - art: { in: [...VERMOEGENSRELEVANTE_VERTRAGSARTEN] }, - ...nichtGeloescht, - }, - orderBy: { bezeichnung: 'asc' }, - }), - ]) - - // Verträge, die schon als Vermögensposten geführt werden, nicht doppelt - // zeigen - sonst wäre die Summe zweimal drin. - const bereitsAlsPosten = new Set( - posten.map((p) => p.vertragId).filter((id): id is string => id !== null), - ) - - const zeilen: ListenPosten[] = [ - ...posten.map((p) => { - const geoeffnet = oeffnen('Vermoegensposten', p) - const stand = p.wertstaende[0] - return { - id: p.id, - bezeichnung: p.bezeichnung, - herkunft: 'VERMOEGEN' as const, - art: p.art, - artBeschriftung: VERMOEGENSART_BESCHRIFTUNG[p.art], - bewertung: p.bewertungSozialhilfe, - wertCent: stand ? centZuZahl(stand.wertCent) : null, - wertStichtag: stand ? alsIsoTag(stand.stichtag) : null, - begruendung: geoeffnet.bewertungBegruendung ?? '', - zusatz: stand ? stand.wertquelle : null, - } - }), - ...vertraege - .filter((v) => !bereitsAlsPosten.has(v.id)) - .map((v) => { - const geoeffnet = oeffnen('Vertrag', v) - return { - id: v.id, - bezeichnung: v.bezeichnung, - herkunft: 'VERTRAG' as const, - art: v.art, - artBeschriftung: VERTRAGSART_BESCHRIFTUNG[v.art], - bewertung: v.bewertungSozialhilfe, - // Der Rückkaufswert steht am Vertrag nicht als Betrag; er wird - // als Vermögensposten mit Wertstand geführt. Bis dahin: offen. - wertCent: null, - wertStichtag: null, - begruendung: geoeffnet.bewertungBegruendung ?? '', - zusatz: v.gesellschaft, - } - }), - ] - - const uebersicht = uebersichtBilden( - zeilen.map((z) => ({ - id: z.id, - bezeichnung: z.bezeichnung, - herkunft: z.herkunft, - artBeschriftung: z.artBeschriftung, - bewertung: z.bewertung, - wertCent: z.wertCent === null ? null : BigInt(z.wertCent), - wertStichtag: null, - begruendung: z.begruendung, - href: '#', - })), - ) - - const vergleich = freibetragVergleichen(uebersicht.jeBewertung.VERWERTBAR.summeCent) - - return ( -
- - -
- - - - -
- - - -
- - Vermögensfreibetrag (§ 90 Abs. 2 Nr. 9 SGB XII) - - - {formatCent(vergleich.freibetragCent)} - -
-
- Über dem Freibetrag - - {formatCent(vergleich.ueberFreibetragCent)} - -
-

{vergleich.hinweis}

- -
-
- - {uebersicht.ungeprueft.length > 0 && ( - - - )} - - {uebersicht.ohneBegruendung.length > 0 && ( - - - )} - - - - -
- ) -} - -function Summenkachel({ - titel, - betrag, - anzahl, - ohneWert, - farbe, -}: { - titel: string - betrag: bigint - anzahl: number - ohneWert: number - farbe: string -}) { - return ( - - -

{titel}

-

{formatCent(betrag)}

-

- {anzahl} {anzahl === 1 ? 'Posten' : 'Posten'} - {ohneWert > 0 && `, davon ${ohneWert} ohne Wert`} -

-
-
- ) -} +import { SeitenKopf } from '@/components/seiten-kopf' +import { Rechtshinweis } from '@/components/rechtshinweis' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Card, CardContent } from '@/components/ui/card' +import { Vermoegensliste, type ListenPosten } from '@/components/sozialhilfe/vermoegensliste' +import { oeffnen } from '@/lib/crypto/feld' +import { centZuZahl, db, nichtGeloescht } from '@/lib/db' +import { alsIsoTag } from '@/lib/fristen/datum' +import { VERMOEGENS_ART_BESCHRIFTUNG } from '@/lib/finanzen/kategorien' +import { MIT_RUECKKAUFSWERT, VERTRAG_ART_BESCHRIFTUNG } from '@/lib/vertraege/konstanten' +import { BEWERTUNG_BESCHRIFTUNG } from '@/lib/sozialhilfe/schonvermoegen' +import { freibetragVergleichen, uebersichtBilden } from '@/lib/sozialhilfe/vermoegensuebersicht' +import { sozialhilfeKontext } from '@/lib/sozialhilfe/zugang' +import { formatCent } from '@/lib/utils' +import { AlertCircle } from 'lucide-react' + +export const dynamic = 'force-dynamic' + +/** + * Vermögens-Bewertungsansicht. + * + * Sie zählt zusammen und stellt gegenüber - sie entscheidet nichts. Was + * geschützt ist, sagt der Träger; bleibt man uneins, das Sozialgericht. + * Deshalb heißt die Spalte "Einschätzung" und nicht "Bewertung". + */ +export default async function VermoegensbewertungSeite() { + const { betreuteId, darfSchreiben } = await sozialhilfeKontext() + + const [posten, vertraege] = await Promise.all([ + db.vermoegensposten.findMany({ + where: { betreuteId, ...nichtGeloescht }, + include: { wertstaende: { orderBy: { stichtag: 'desc' }, take: 1 } }, + orderBy: { bezeichnung: 'asc' }, + }), + db.vertrag.findMany({ + where: { + betreuteId, + // Nur Vertraege, die ueberhaupt einen Wert darstellen koennen. Ein + // Stromvertrag hat keinen Rueckkaufswert und wuerde die Liste zumuellen. + art: { in: [...MIT_RUECKKAUFSWERT] }, + ...nichtGeloescht, + }, + orderBy: { bezeichnung: 'asc' }, + }), + ]) + + // Verträge, die schon als Vermögensposten geführt werden, nicht doppelt + // zeigen - sonst wäre die Summe zweimal drin. + const bereitsAlsPosten = new Set( + posten.map((p) => p.vertragId).filter((id): id is string => id !== null), + ) + + const zeilen: ListenPosten[] = [ + ...posten.map((p) => { + const geoeffnet = oeffnen('Vermoegensposten', p) + const stand = p.wertstaende[0] + return { + id: p.id, + bezeichnung: p.bezeichnung, + herkunft: 'VERMOEGEN' as const, + art: p.art, + artBeschriftung: VERMOEGENS_ART_BESCHRIFTUNG[p.art], + bewertung: p.bewertungSozialhilfe, + wertCent: stand ? centZuZahl(stand.wertCent) : null, + wertStichtag: stand ? alsIsoTag(stand.stichtag) : null, + begruendung: geoeffnet.bewertungBegruendung ?? '', + zusatz: stand ? stand.wertquelle : null, + } + }), + ...vertraege + .filter((v) => !bereitsAlsPosten.has(v.id)) + .map((v) => { + const geoeffnet = oeffnen('Vertrag', v) + return { + id: v.id, + bezeichnung: v.bezeichnung, + herkunft: 'VERTRAG' as const, + art: v.art, + artBeschriftung: VERTRAG_ART_BESCHRIFTUNG[v.art], + bewertung: v.bewertungSozialhilfe, + // Der Rückkaufswert steht am Vertrag nicht als Betrag; er wird + // als Vermögensposten mit Wertstand geführt. Bis dahin: offen. + wertCent: null, + wertStichtag: null, + begruendung: geoeffnet.bewertungBegruendung ?? '', + zusatz: v.gesellschaft, + } + }), + ] + + const uebersicht = uebersichtBilden( + zeilen.map((z) => ({ + id: z.id, + bezeichnung: z.bezeichnung, + herkunft: z.herkunft, + artBeschriftung: z.artBeschriftung, + bewertung: z.bewertung, + wertCent: z.wertCent === null ? null : BigInt(z.wertCent), + wertStichtag: null, + begruendung: z.begruendung, + href: '#', + })), + ) + + const vergleich = freibetragVergleichen(uebersicht.jeBewertung.VERWERTBAR.summeCent) + + return ( +
+ + +
+ + + + +
+ + + +
+ + Vermögensfreibetrag (§ 90 Abs. 2 Nr. 9 SGB XII) + + + {formatCent(vergleich.freibetragCent)} + +
+
+ Über dem Freibetrag + + {formatCent(vergleich.ueberFreibetragCent)} + +
+

{vergleich.hinweis}

+ +
+
+ + {uebersicht.ungeprueft.length > 0 && ( + + + )} + + {uebersicht.ohneBegruendung.length > 0 && ( + + + )} + + + + +
+ ) +} + +function Summenkachel({ + titel, + betrag, + anzahl, + ohneWert, + farbe, +}: { + titel: string + betrag: bigint + anzahl: number + ohneWert: number + farbe: string +}) { + return ( + + +

{titel}

+

{formatCent(betrag)}

+

+ {anzahl} {anzahl === 1 ? 'Posten' : 'Posten'} + {ohneWert > 0 && `, davon ${ohneWert} ohne Wert`} +

+
+
+ ) +} diff --git a/src/components/sozialhilfe/heimkosten-formular.tsx b/src/components/sozialhilfe/heimkosten-formular.tsx index 7a2c822..ab2e008 100644 --- a/src/components/sozialhilfe/heimkosten-formular.tsx +++ b/src/components/sozialhilfe/heimkosten-formular.tsx @@ -1,326 +1,310 @@ -'use client' - -import { useActionState, useState } from 'react' -import { Feld, Feldgruppe } from '@/components/feld' -import { Rechtshinweis } from '@/components/rechtshinweis' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Select } from '@/components/ui/select' -import { Textarea } from '@/components/ui/textarea' -import { Absenden, Meldung } from '@/components/sozialhilfe/bausteine' -import { MONATSNAMEN } from '@/lib/sozialhilfe/heimkosten' -import { heimkostenSpeichern, type Formularzustand } from '@/server/actions/sozialhilfe' - -export type HeimkostenWerte = { - jahr: number - monat: number - pflegeEigenanteil: string - unterkunftVerpflegung: string - investitionskosten: string - ausbildungsumlage: string - zusatzleistungen: string - pflegekasse: string - leistungszuschlagProzent: number - leistungszuschlag: string - rente: string - sonstigeEinkuenfte: string - sozialhilfe: string - eigenmittel: string - barbetrag: string - bekleidungspauschale: string - abwesenheitstage: number - bemerkung: string -} - -/** - * Ein Monat der Gegenüberstellung. - * - * Die Reihenfolge folgt der Heimrechnung: erst die vier Bausteine der - * Kosten, dann die Deckungsquellen, dann das, was der betreuten Person - * verbleiben muss. Wer von oben nach unten abtippt, findet alles an der - * Stelle, an der es auf der Rechnung steht. - */ -export function HeimkostenFormular({ - werte, - jahre, - vorschlagPflegekasse, - vorschlagBarbetrag, -}: { - werte: HeimkostenWerte - jahre: number[] - /** Pauschale der Pflegekasse zum hinterlegten Pflegegrad, als "1319,00". */ - vorschlagPflegekasse: { betrag: string; text: string } | null - vorschlagBarbetrag: { betrag: string; text: string } -}) { - const [zustand, aktion] = useActionState(heimkostenSpeichern, {}) - const [pflegekasse, setPflegekasse] = useState(werte.pflegekasse) - const [barbetrag, setBarbetrag] = useState(werte.barbetrag) - - return ( -
- - - - - Monat erfassen - - Beträge wie auf der Heimrechnung, z.B. 1.234,56. Ein bereits erfasster Monat wird - überschrieben. - - - - -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - {vorschlagPflegekasse.text}{' '} - - - ) : ( - 'Pauschale nach § 43 SGB XI. Hinterlege den Pflegegrad in den Stammdaten, dann steht hier ein Vorschlag.' - ) - } - > - setPflegekasse(e.target.value)} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {vorschlagBarbetrag.text}{' '} - - - } - > - setBarbetrag(e.target.value)} - /> - - - - - - - - - - - - -