Etappe 4. Aus diesen Daten entstehen das Vermoegensverzeichnis nach § 1835 BGB und die Rechnungslegung nach §§ 1859, 1865 BGB - beides geht ans Betreuungsgericht, beides ohne dass eine Zahl abgetippt wird. Betreuungsjahr n laeuft von bestellungAm + (n-1) Jahre bis bestellungAm + n Jahre - 1 Tag. Bei Bestellung am 29. Februar endet Jahr 1 am 27.02. und Jahr 2 beginnt am 28.02.: lueckenlos und ohne Ueberschneidung, per Test abgesichert. Rechenprobe der Rechnungslegung: Der Anfangsbestand ist der juengste Saldostand bis zum VORTAG des Zeitraumbeginns - ein Saldostand gilt als Bestand am Tagesende, sonst zaehlte der erste Tag doppelt. Geht die Probe nicht auf, wird die Differenz im PDF ausgewiesen und jeder fehlende Saldostand namentlich benannt. Nie geglaettet: eine still korrigierte Rechnungslegung ist gegenueber dem Gericht wertlos. Posten ohne Wertstand zum Stichtag erscheinen als Luecke, nicht als 0,00 EUR - und Werte aus der Zukunft werden nie herangezogen. Konten kommen ueber ihren Saldostand ins Verzeichnis; Vermoegensposten der Art KONTO werden dort uebersprungen, sonst stuende das Geld doppelt drin. Der CSV- und CAMT-Import raet KEINE Kategorien, sondern bucht alles auf "Sonstige". Eine falsch geratene Kategorie faellt in der Jahresuebersicht niemandem auf und verfaelscht den Bericht. Fremdwaehrung wird abgelehnt statt umgerechnet. Der XML-Leser weist DOCTYPE ab (XXE). 68 Tests, darunter echte IBAN-Pruefziffern und die Rundung: es darf nirgends ein Cent verschwinden. Etappe: 4 Phase: Geld
260 lines
9.6 KiB
TypeScript
260 lines
9.6 KiB
TypeScript
import Link from 'next/link'
|
||
import { Boxes, Plus } from 'lucide-react'
|
||
import { Badge } from '@/components/ui/badge'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||
import { SeitenKopf, Leerzustand } from '@/components/seiten-kopf'
|
||
import { Rechtshinweis } from '@/components/rechtshinweis'
|
||
import { Betrag, Hinweisliste, Kennzahl, Summenzeile } from '@/components/finanzen/bausteine'
|
||
import { Input } from '@/components/ui/input'
|
||
import { centZuZahl } from '@/lib/db'
|
||
import { formatCent, formatDatum } from '@/lib/utils'
|
||
import { finanzKontext } from '@/lib/finanzen/seite'
|
||
import { heute, tagAlsText, tagAusText } from '@/lib/finanzen/datum'
|
||
import {
|
||
SOZIALHILFE_BEWERTUNG_BESCHRIFTUNG,
|
||
VERMOEGENS_ART_BESCHRIFTUNG,
|
||
VERMOEGENS_ART_EINZAHL,
|
||
} from '@/lib/finanzen/kategorien'
|
||
import { vermoegenLaden } from '@/lib/berichte/gemeinsam'
|
||
import type { SozialhilfeBewertung } from '@prisma/client'
|
||
|
||
export const metadata = { title: 'Vermögen' }
|
||
|
||
/**
|
||
* Vermögensposten mit ihrem Wert an einem frei wählbaren Stichtag.
|
||
*
|
||
* Der Stichtag ist hier kein Beiwerk: Genau so entsteht später das
|
||
* Vermögensverzeichnis. Wer den Stichtag zurücksetzt, sieht das
|
||
* Vermögen so, wie es damals im Verzeichnis stand – und nicht so, wie
|
||
* man es heute gerne hätte.
|
||
*/
|
||
export default async function VermoegenSeite({
|
||
searchParams,
|
||
}: {
|
||
searchParams: Promise<Record<string, string | string[] | undefined>>
|
||
}) {
|
||
const parameter = await searchParams
|
||
const { betreute, darfSchreiben } = await finanzKontext()
|
||
const schreiben = darfSchreiben('FINANZEN')
|
||
|
||
const gewaehlt =
|
||
typeof parameter.stichtag === 'string' ? tagAusText(parameter.stichtag) : null
|
||
const stichtag = gewaehlt ?? heute()
|
||
|
||
const bild = await vermoegenLaden(betreute.id, stichtag)
|
||
|
||
const hinweise = bild.ohneWert.map(
|
||
(posten) =>
|
||
`„${posten.bezeichnung}“ hat zum ${formatDatum(stichtag)} keinen Wert. ` +
|
||
(posten.spaetereWertstaende > 0
|
||
? 'Es gibt nur Wertstände nach diesem Stichtag – die dürfen hier nicht verwendet werden.'
|
||
: 'Es ist noch kein Wertstand erfasst.'),
|
||
)
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<SeitenKopf
|
||
titel="Vermögen"
|
||
beschreibung="Grundbesitz, Versicherungen, Fahrzeuge, Forderungen und Verbindlichkeiten. Jeder Posten wird über Wertstände bewertet – nie über ein einzelnes Feld, das überschrieben wird."
|
||
pfad={[{ titel: 'Vermögen & Kasse', href: '/finanzen' }, { titel: 'Vermögen' }]}
|
||
aktionen={
|
||
schreiben ? (
|
||
<Button asChild>
|
||
<Link href="/finanzen/vermoegen/neu">
|
||
<Plus aria-hidden="true" />
|
||
Posten anlegen
|
||
</Link>
|
||
</Button>
|
||
) : undefined
|
||
}
|
||
/>
|
||
|
||
<form method="get" className="flex flex-wrap items-end gap-3 rounded-lg border p-4">
|
||
<div className="min-w-[14rem]">
|
||
<label htmlFor="stichtag" className="mb-1.5 block text-sm font-medium">
|
||
Vermögen zum Stichtag
|
||
</label>
|
||
<Input id="stichtag" type="date" name="stichtag" defaultValue={tagAlsText(stichtag)} />
|
||
</div>
|
||
<Button type="submit" variant="secondary">
|
||
Anzeigen
|
||
</Button>
|
||
<p className="w-full text-sm text-muted-foreground">
|
||
Es wird je Posten der jüngste Wertstand verwendet, der nicht nach dem Stichtag liegt.
|
||
Spätere Bewertungen bleiben außen vor – auch dann, wenn sie genauer wären.
|
||
</p>
|
||
</form>
|
||
|
||
<div className="grid gap-4 sm:grid-cols-3">
|
||
<Kennzahl titel="Aktiva" wert={formatCent(centZuZahl(bild.summeAktivaCent))} />
|
||
<Kennzahl titel="Verbindlichkeiten" wert={formatCent(centZuZahl(bild.summePassivaCent))} />
|
||
<Kennzahl
|
||
titel="Reinvermögen"
|
||
wert={formatCent(centZuZahl(bild.reinvermoegenCent))}
|
||
hinweis="ohne Kontoguthaben – die stehen unter „Konten“"
|
||
/>
|
||
</div>
|
||
|
||
<Hinweisliste hinweise={hinweise} titel="Diese Posten fehlen im Verzeichnis" />
|
||
|
||
{bild.aktivaGruppen.length === 0 && bild.passiva.length === 0 && bild.ohneWert.length === 0 ? (
|
||
<Leerzustand
|
||
icon={Boxes}
|
||
titel="Noch kein Vermögensposten erfasst"
|
||
beschreibung="Das Hausgrundstück und die Versicherungen gehören hierher – sie machen im Vermögensverzeichnis den größten Teil aus."
|
||
aktion={
|
||
schreiben ? (
|
||
<Button asChild>
|
||
<Link href="/finanzen/vermoegen/neu">
|
||
<Plus aria-hidden="true" />
|
||
Posten anlegen
|
||
</Link>
|
||
</Button>
|
||
) : undefined
|
||
}
|
||
/>
|
||
) : (
|
||
<div className="space-y-6">
|
||
{bild.aktivaGruppen.map((gruppe) => (
|
||
<Card key={gruppe.art}>
|
||
<CardHeader>
|
||
<CardTitle>{VERMOEGENS_ART_BESCHRIFTUNG[gruppe.art]}</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Postentabelle posten={gruppe.posten} />
|
||
<Summenzeile
|
||
beschriftung={`Summe ${VERMOEGENS_ART_BESCHRIFTUNG[gruppe.art]}`}
|
||
cent={centZuZahl(gruppe.summeCent) ?? 0}
|
||
betont
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
))}
|
||
|
||
{bild.passiva.length > 0 && (
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Verbindlichkeiten</CardTitle>
|
||
<CardDescription>
|
||
Werden mit positivem Betrag geführt; dass sie das Vermögen mindern, ergibt sich
|
||
aus der Art des Postens.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Postentabelle posten={bild.passiva} />
|
||
<Summenzeile
|
||
beschriftung="Summe Verbindlichkeiten"
|
||
cent={centZuZahl(bild.summePassivaCent) ?? 0}
|
||
betont
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
|
||
{bild.ohneWert.length > 0 && (
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Ohne Bewertung zum Stichtag</CardTitle>
|
||
<CardDescription>
|
||
Diese Posten sind vorhanden, gehen aber in keine Summe ein.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<ul className="space-y-2">
|
||
{bild.ohneWert.map((posten) => (
|
||
<li key={posten.id} className="flex flex-wrap items-center gap-2">
|
||
<Link
|
||
href={`/finanzen/vermoegen/${posten.id}`}
|
||
className="font-medium hover:underline"
|
||
>
|
||
{posten.bezeichnung}
|
||
</Link>
|
||
<Badge variant="gelb">{VERMOEGENS_ART_EINZAHL[posten.art]}</Badge>
|
||
<span className="text-sm text-muted-foreground">
|
||
{posten.spaetereWertstaende > 0
|
||
? `${posten.spaetereWertstaende} Wertstand/Wertstände, alle nach dem Stichtag`
|
||
: 'noch kein Wertstand'}
|
||
</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
<Rechtshinweis rechtsgrundlage="§ 1835 BGB, § 90 SGB XII" />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function Postentabelle({
|
||
posten,
|
||
}: {
|
||
posten: {
|
||
id: string
|
||
bezeichnung: string
|
||
wertCent: bigint
|
||
bewertetAm: Date
|
||
wertquelle: string
|
||
alterTage: number
|
||
nachweisTitel: string | null
|
||
bewertungSozialhilfe: string | null
|
||
}[]
|
||
}) {
|
||
return (
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>Posten</TableHead>
|
||
<TableHead>Wertquelle</TableHead>
|
||
<TableHead>Bewertet am</TableHead>
|
||
<TableHead>Sozialhilfe</TableHead>
|
||
<TableHead className="text-right">Wert</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{posten.map((eintrag) => (
|
||
<TableRow key={eintrag.id}>
|
||
<TableCell>
|
||
<Link
|
||
href={`/finanzen/vermoegen/${eintrag.id}`}
|
||
className="font-medium hover:underline"
|
||
>
|
||
{eintrag.bezeichnung}
|
||
</Link>
|
||
</TableCell>
|
||
<TableCell className="text-sm text-muted-foreground">
|
||
<span className="block">{eintrag.wertquelle}</span>
|
||
{eintrag.nachweisTitel && (
|
||
<span className="block">Beleg: {eintrag.nachweisTitel}</span>
|
||
)}
|
||
</TableCell>
|
||
<TableCell className="whitespace-nowrap text-sm text-muted-foreground">
|
||
{formatDatum(eintrag.bewertetAm)}
|
||
{eintrag.alterTage > 365 && (
|
||
<Badge variant="gelb" className="ml-2">
|
||
über ein Jahr alt
|
||
</Badge>
|
||
)}
|
||
</TableCell>
|
||
<TableCell className="text-sm text-muted-foreground">
|
||
{eintrag.bewertungSozialhilfe
|
||
? SOZIALHILFE_BEWERTUNG_BESCHRIFTUNG[
|
||
eintrag.bewertungSozialhilfe as SozialhilfeBewertung
|
||
]
|
||
: '–'}
|
||
</TableCell>
|
||
<TableCell className="text-right">
|
||
<Betrag cent={centZuZahl(eintrag.wertCent)} fett />
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
)
|
||
}
|