feat: Konten, Kassenbuch, Vermoegen und die beiden Pflicht-PDFs
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
This commit is contained in:
@@ -6,10 +6,9 @@ import { Prisma } from '@prisma/client'
|
||||
import { db, nichtGeloescht } from '@/lib/db'
|
||||
import { erfordereZugang, ZugriffVerweigert } from '@/lib/auth/sitzung'
|
||||
import { aenderungErmitteln, protokollieren } from '@/lib/audit'
|
||||
import { oeffnen, sealen } from '@/lib/crypto/feld'
|
||||
import { sealen } from '@/lib/crypto/feld'
|
||||
import { blindindex } from '@/lib/crypto/hash'
|
||||
import { alsTag } from '@/lib/finanzen/datum'
|
||||
import { ibanMaskieren } from '@/lib/finanzen/iban'
|
||||
import { csvTextLesen, csvZuBuchungen, dublettenMarkieren } from '@/lib/finanzen/import'
|
||||
import { camtZuBuchungen } from '@/lib/finanzen/camt'
|
||||
import {
|
||||
@@ -45,8 +44,6 @@ export type Formularzustand = {
|
||||
meldung?: string[]
|
||||
}
|
||||
|
||||
const LEER: Formularzustand = {}
|
||||
|
||||
function zugriffsfehler(ausnahme: unknown): Formularzustand | null {
|
||||
if (ausnahme instanceof ZugriffVerweigert) {
|
||||
return { fehler: ausnahme.message }
|
||||
@@ -824,67 +821,3 @@ export async function wertstandLoeschen(
|
||||
revalidatePath('/finanzen/vermoegen')
|
||||
return { erfolg: 'Wert gelöscht.' }
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Berichte
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Haelt fest, dass ein Bericht erzeugt wurde. Die PDF-Datei selbst wird
|
||||
* beim Abruf erzeugt und ausgeliefert; hier steht nur, was wann in
|
||||
* welcher Fassung abgegeben wurde.
|
||||
*/
|
||||
export async function berichtVermerken(eingabe: {
|
||||
betreuteId: string
|
||||
art: 'VERMOEGENSVERZEICHNIS' | 'VERMOEGENSUEBERSICHT' | 'RECHNUNGSLEGUNG'
|
||||
titel: string
|
||||
stichtag?: Date | null
|
||||
zeitraumVon?: Date | null
|
||||
zeitraumBis?: Date | null
|
||||
dateiname: string
|
||||
}): Promise<void> {
|
||||
const { benutzer } = await erfordereZugang(eingabe.betreuteId, 'FINANZEN', 'lesen')
|
||||
|
||||
const bericht = await db.bericht.create({
|
||||
data: {
|
||||
betreuteId: eingabe.betreuteId,
|
||||
art: eingabe.art,
|
||||
titel: eingabe.titel,
|
||||
stichtag: eingabe.stichtag ?? null,
|
||||
zeitraumVon: eingabe.zeitraumVon ?? null,
|
||||
zeitraumBis: eingabe.zeitraumBis ?? null,
|
||||
dateiname: eingabe.dateiname,
|
||||
erstelltVonId: benutzer.id,
|
||||
},
|
||||
})
|
||||
|
||||
await protokollieren({
|
||||
aktion: 'EXPORTIERT',
|
||||
entitaet: 'Bericht',
|
||||
entitaetId: bericht.id,
|
||||
bezeichnung: eingabe.titel,
|
||||
betreuteId: eingabe.betreuteId,
|
||||
userId: benutzer.id,
|
||||
userEmail: benutzer.email,
|
||||
userName: benutzer.name,
|
||||
})
|
||||
}
|
||||
|
||||
/** Kleine Hilfe fuer Formulare, die nur den Anfangszustand brauchen. */
|
||||
export async function leererZustand(): Promise<Formularzustand> {
|
||||
return LEER
|
||||
}
|
||||
|
||||
/** Zeigt die IBAN eines Kontos maskiert - fuer Listen ohne Klartext. */
|
||||
export async function kontoIbanMaskiert(
|
||||
betreuteId: string,
|
||||
kontoId: string,
|
||||
): Promise<string | null> {
|
||||
await erfordereZugang(betreuteId, 'FINANZEN', 'lesen')
|
||||
const konto = await db.konto.findFirst({
|
||||
where: { id: kontoId, betreuteId, ...nichtGeloescht },
|
||||
})
|
||||
if (!konto) return null
|
||||
const offen = oeffnen('Konto', konto)
|
||||
return offen.iban ? ibanMaskieren(offen.iban) : null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user