diff --git a/.eslintrc.json b/.eslintrc.json index 3722418..1fa88a3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,13 @@ { - "extends": ["next/core-web-vitals", "next/typescript"] + "extends": ["next/core-web-vitals", "next/typescript"], + "rules": { + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ] + } } diff --git a/src/app/(app)/page.tsx b/src/app/(app)/page.tsx deleted file mode 100644 index 5476df0..0000000 --- a/src/app/(app)/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { redirect } from 'next/navigation' - -/** - * Die Wurzel der Anwendung hat keinen eigenen Inhalt: Wer angemeldet ist, - * will wissen, was heute zu tun ist. - */ -export default function StartPage() { - redirect('/heute') -} diff --git a/src/app/(app)/sozialhilfe/heimkosten/page.tsx b/src/app/(app)/sozialhilfe/heimkosten/page.tsx index 0ef689c..25fe8f4 100644 --- a/src/app/(app)/sozialhilfe/heimkosten/page.tsx +++ b/src/app/(app)/sozialhilfe/heimkosten/page.tsx @@ -1,383 +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 { 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) -} +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 } 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/page.tsx b/src/app/page.tsx index 0c09ecf..cf20950 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,6 +3,12 @@ import { redirect } from 'next/navigation' /** * Einstiegspunkt. Wer angemeldet ist, landet auf dem Tagesdashboard; * alles Weitere entscheidet der Anwendungsrahmen unter (app). + * + * Diese Weiterleitung liegt bewusst AUSSERHALB der Gruppe (app). Eine + * page.tsx innerhalb einer Routengruppe loest denselben Pfad "/" auf wie + * diese hier - zwei Dateien fuer eine Adresse. Next.js uebersetzt das + * zwar noch, erzeugt beim Standalone-Build aber ein unvollstaendiges + * Manifest, und genau dieses Verzeichnis kopiert das Docker-Abbild. */ export default function Startseite() { redirect('/heute') diff --git a/src/components/sozialhilfe/nachforderung-tracker.tsx b/src/components/sozialhilfe/nachforderung-tracker.tsx index f06ed27..1157b44 100644 --- a/src/components/sozialhilfe/nachforderung-tracker.tsx +++ b/src/components/sozialhilfe/nachforderung-tracker.tsx @@ -15,7 +15,6 @@ import { NachforderungAbzeichen } from '@/components/sozialhilfe/abzeichen' import { LEERE_NACHFORDERUNG, NachforderungFormular, - type NachforderungWerte, } from '@/components/sozialhilfe/nachforderung-formular' import { fristAmpel, trackerFortschritt } from '@/lib/sozialhilfe/nachforderungen' import { formatDatum } from '@/lib/utils' diff --git a/src/server/actions/kontakte.ts b/src/server/actions/kontakte.ts index fdff566..4a02885 100644 --- a/src/server/actions/kontakte.ts +++ b/src/server/actions/kontakte.ts @@ -33,7 +33,6 @@ export type KontaktZustand = { werte?: Record } -const LEER: KontaktZustand = {} function felderAusZod(fehler: { fieldErrors: Record }) { const felder: Record = {} diff --git a/src/server/actions/sozialhilfe.ts b/src/server/actions/sozialhilfe.ts index 8d99838..f364c12 100644 --- a/src/server/actions/sozialhilfe.ts +++ b/src/server/actions/sozialhilfe.ts @@ -51,7 +51,6 @@ export type Formularzustand = { erfolg?: string } -const LEER: Formularzustand = {} function pfadeErneuern(...pfade: string[]): void { for (const pfad of pfade) revalidatePath(pfad)