import 'server-only' import { db, nichtGeloescht } from '@/lib/db' /** * Gehoert ein verknuepfter Datensatz zu dieser Akte? * * Wenn ein Formular einen Fremdschluessel mitschickt - Konto, Vertrag, * Immobilie, Dokument, Vorgang, Kontakt -, dann kommt der aus einer * Auswahlliste im Browser. Der Server darf sich darauf nicht verlassen: * Eine veraenderte Anfrage kann jede beliebige Id enthalten. * * Ohne die Pruefung entstuende eine Verknuepfung ueber Aktengrenzen * hinweg. Was daran gefaehrlich ist, sieht man erst an den Folgen: Der * Rueckkaufswert der einen Akte haengt am Vertrag der anderen, ein * fremder Name steht im Briefkopf, ein fremder Beschluss in der * Genehmigungskette. * * Solange nur eine betreute Person angelegt ist, ist all das folgenlos. * Die Pruefung gehoert trotzdem hin - sie kostet eine Abfrage, und die * zweite betreute Person kommt frueher, als man denkt. * * Bewusst KEINE Ausnahme geworfen: Formularaktionen geben Fehler zurueck, * damit die Eingaben des Benutzers nicht verlorengehen. */ /** Die Tabellen, auf die ein Formular verweisen darf. */ export type Verweisziel = | 'konto' | 'vertrag' | 'immobilie' | 'dokument' | 'vorgang' | 'kontakt' | 'vermoegensposten' | 'buchung' type Sucher = (id: string, betreuteId: string) => Promise<{ id: string } | null> /** * Je Ziel eine Abfrage, ausgeschrieben statt dynamisch zusammengesetzt. * * Das ist ein paar Zeilen laenger als ein Zugriff ueber den Tabellennamen - * dafuer ist die Liste der erlaubten Ziele eine bewusste Aufzaehlung und * keine Nebenwirkung, und der Typpruefer sieht jede Abfrage. */ const SUCHER: Record = { konto: (id, betreuteId) => db.konto.findFirst({ where: { id, betreuteId, ...nichtGeloescht }, select: { id: true } }), vertrag: (id, betreuteId) => db.vertrag.findFirst({ where: { id, betreuteId, ...nichtGeloescht }, select: { id: true } }), immobilie: (id, betreuteId) => db.immobilie.findFirst({ where: { id, betreuteId, ...nichtGeloescht }, select: { id: true } }), dokument: (id, betreuteId) => db.dokument.findFirst({ where: { id, betreuteId, ...nichtGeloescht }, select: { id: true } }), vorgang: (id, betreuteId) => db.vorgang.findFirst({ where: { id, betreuteId, ...nichtGeloescht }, select: { id: true } }), kontakt: (id, betreuteId) => db.kontakt.findFirst({ where: { id, betreuteId, ...nichtGeloescht }, select: { id: true } }), vermoegensposten: (id, betreuteId) => db.vermoegensposten.findFirst({ where: { id, betreuteId, ...nichtGeloescht }, select: { id: true }, }), buchung: (id, betreuteId) => db.buchung.findFirst({ where: { id, betreuteId, ...nichtGeloescht }, select: { id: true } }), } const BESCHRIFTUNG: Record = { konto: 'Dieses Konto', vertrag: 'Dieser Vertrag', immobilie: 'Diese Immobilie', dokument: 'Dieses Dokument', vorgang: 'Dieser Vorgang', kontakt: 'Dieser Kontakt', vermoegensposten: 'Dieser Vermögensposten', buchung: 'Diese Buchung', } /** * Prueft einen einzelnen Verweis. * * Ein leerer Verweis ist in Ordnung - "keine Verknuepfung" ist ein * gueltiger Zustand und keine Verletzung. * * @returns null, wenn alles stimmt; sonst die fertige Fehlermeldung. */ export async function verweisPruefen( ziel: Verweisziel, id: string | null | undefined, betreuteId: string, ): Promise { if (!id) return null const treffer = await SUCHER[ziel](id, betreuteId) return treffer ? null : `${BESCHRIFTUNG[ziel]} gehört nicht zu dieser betreuten Person.` } /** * Prueft mehrere Verweise auf einmal und meldet den ersten Verstoss. * * Die Abfragen laufen nebeneinander: Bei vier Verweisen ist das eine * Wartezeit statt vier. * * @example * const fehler = await verweisePruefen(betreuteId, { * konto: werte.kontoId, * vertrag: werte.vertragId, * }) * if (fehler) return { fehler } */ export async function verweisePruefen( betreuteId: string, verweise: Partial>, ): Promise { const eintraege = Object.entries(verweise) as [Verweisziel, string | null | undefined][] const ergebnisse = await Promise.all( eintraege.map(([ziel, id]) => verweisPruefen(ziel, id, betreuteId)), ) return ergebnisse.find((meldung) => meldung !== null) ?? null }