Ausschliesslich Zeilenenden. Nachgewiesen mit "git diff --cached --ignore-cr-at-eol": Der Diff ist leer, es hat sich kein Zeichen Inhalt geaendert. Deshalb ein eigener Commit: 7448 geaenderte Zeilen in einem Commit mit echten Aenderungen zu verstecken, macht die Historie an dieser Stelle unlesbar. So laesst er sich beim Durchsehen ueberspringen. Refs #26
233 lines
8.5 KiB
TypeScript
233 lines
8.5 KiB
TypeScript
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 (
|
|
<div className="space-y-8">
|
|
<SeitenKopf
|
|
titel="Vermögen bewerten"
|
|
beschreibung="Jeder Posten bekommt eine Einschätzung und — das ist der wichtige Teil — eine Begründung. Ohne Begründung hilft die Einordnung im Widerspruchsverfahren nicht weiter."
|
|
pfad={[
|
|
{ titel: 'Start', href: '/heute' },
|
|
{ titel: 'Sozialhilfe', href: '/sozialhilfe' },
|
|
{ titel: 'Vermögen' },
|
|
]}
|
|
/>
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
<Summenkachel
|
|
titel={BEWERTUNG_BESCHRIFTUNG.VERWERTBAR}
|
|
betrag={uebersicht.jeBewertung.VERWERTBAR.summeCent}
|
|
anzahl={uebersicht.jeBewertung.VERWERTBAR.anzahl}
|
|
ohneWert={uebersicht.jeBewertung.VERWERTBAR.ohneWert}
|
|
farbe="text-ampel-rot"
|
|
/>
|
|
<Summenkachel
|
|
titel={BEWERTUNG_BESCHRIFTUNG.GESCHUETZT}
|
|
betrag={uebersicht.jeBewertung.GESCHUETZT.summeCent}
|
|
anzahl={uebersicht.jeBewertung.GESCHUETZT.anzahl}
|
|
ohneWert={uebersicht.jeBewertung.GESCHUETZT.ohneWert}
|
|
farbe="text-ampel-gruen"
|
|
/>
|
|
<Summenkachel
|
|
titel={BEWERTUNG_BESCHRIFTUNG.STRITTIG}
|
|
betrag={uebersicht.jeBewertung.STRITTIG.summeCent}
|
|
anzahl={uebersicht.jeBewertung.STRITTIG.anzahl}
|
|
ohneWert={uebersicht.jeBewertung.STRITTIG.ohneWert}
|
|
farbe="text-ampel-orange"
|
|
/>
|
|
<Summenkachel
|
|
titel={BEWERTUNG_BESCHRIFTUNG.UNGEPRUEFT}
|
|
betrag={uebersicht.jeBewertung.UNGEPRUEFT.summeCent}
|
|
anzahl={uebersicht.jeBewertung.UNGEPRUEFT.anzahl}
|
|
ohneWert={uebersicht.jeBewertung.UNGEPRUEFT.ohneWert}
|
|
farbe="text-muted-foreground"
|
|
/>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardContent className="space-y-2 pt-5">
|
|
<div className="flex flex-wrap items-baseline justify-between gap-3">
|
|
<span className="text-sm text-muted-foreground">
|
|
Vermögensfreibetrag (§ 90 Abs. 2 Nr. 9 SGB XII)
|
|
</span>
|
|
<span className="text-lg font-semibold tabular-nums">
|
|
{formatCent(vergleich.freibetragCent)}
|
|
</span>
|
|
</div>
|
|
<div className="flex flex-wrap items-baseline justify-between gap-3">
|
|
<span className="text-sm text-muted-foreground">Über dem Freibetrag</span>
|
|
<span
|
|
className={`text-lg font-semibold tabular-nums ${
|
|
vergleich.ausgeschoepft ? 'text-ampel-rot' : 'text-ampel-gruen'
|
|
}`}
|
|
>
|
|
{formatCent(vergleich.ueberFreibetragCent)}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">{vergleich.hinweis}</p>
|
|
<Rechtshinweis rechtsgrundlage="§ 90 SGB XII" kurz />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{uebersicht.ungeprueft.length > 0 && (
|
|
<Alert variant="warnung">
|
|
<AlertCircle aria-hidden="true" />
|
|
<AlertTitle>
|
|
{uebersicht.ungeprueft.length}{' '}
|
|
{uebersicht.ungeprueft.length === 1 ? 'Posten ist' : 'Posten sind'} noch ungeprüft
|
|
</AlertTitle>
|
|
<AlertDescription>
|
|
Vor dem Antrag sollte hier nichts mehr stehen — der Träger fragt sonst nach, und das
|
|
kostet Wochen.
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
{uebersicht.ohneBegruendung.length > 0 && (
|
|
<Alert variant="warnung">
|
|
<AlertCircle aria-hidden="true" />
|
|
<AlertTitle>Bewertung ohne Begründung</AlertTitle>
|
|
<AlertDescription>
|
|
{uebersicht.ohneBegruendung.map((p) => p.bezeichnung).join(', ')} — trag nach, worauf
|
|
sich die Einschätzung stützt. Im Widerspruchsverfahren zählt nur, was begründet ist.
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
<Vermoegensliste posten={zeilen} darfSchreiben={darfSchreiben} />
|
|
|
|
<Rechtshinweis rechtsgrundlage="§ 90 SGB XII, § 91 SGB XII" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function Summenkachel({
|
|
titel,
|
|
betrag,
|
|
anzahl,
|
|
ohneWert,
|
|
farbe,
|
|
}: {
|
|
titel: string
|
|
betrag: bigint
|
|
anzahl: number
|
|
ohneWert: number
|
|
farbe: string
|
|
}) {
|
|
return (
|
|
<Card>
|
|
<CardContent className="pt-5">
|
|
<p className="text-sm text-muted-foreground">{titel}</p>
|
|
<p className={`text-xl font-semibold tabular-nums ${farbe}`}>{formatCent(betrag)}</p>
|
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
{anzahl} {anzahl === 1 ? 'Posten' : 'Posten'}
|
|
{ohneWert > 0 && `, davon ${ohneWert} ohne Wert`}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|