Zwei Felder fuer zwei Luecken, in einer Migration: benutzer.totpLetzterSchritt haelt das zuletzt akzeptierte TOTP-Fenster fest. Ohne das gilt ein Code bis zu 90 Sekunden lang beliebig oft. Die Tabelle ersteinrichtung hat genau eine Zeile mit fester Id. Das ist kein Protokoll, sondern eine Sperre: Bisher entschied "gibt es schon einen Benutzer?" und danach wurde angelegt - zwei Anfragen gleichzeitig kamen beide durch. Jetzt entscheidet der Primaerschluessel. Enthalten ist eine Datenmigration: Wo schon ein Zugang besteht, wird die Sperrzeile gesetzt. Ohne sie stuende die Einrichtungsseite auf jedem bestehenden System nach dem Update wieder offen. Die DROP-INDEX-Zeilen zu den Trigramm-Indizes hat das Skript aus #36 wieder entfernt; "npm run db:pruefen" bestaetigt, dass beide stehen. Refs #38
1796 lines
53 KiB
Plaintext
1796 lines
53 KiB
Plaintext
// =============================================================================
|
|
// Betreuungs-Cockpit - Datenmodell
|
|
// =============================================================================
|
|
//
|
|
// Konventionen in diesem Schema:
|
|
//
|
|
// 1. Geldbetraege liegen IMMER als BigInt in Cent vor ("...Cent"). Kein Float,
|
|
// keine Decimal-Konvertierung im Anzeigepfad - die Rechnungslegung nach
|
|
// § 1865 BGB darf keine Rundungsfehler erben.
|
|
//
|
|
// 2. Felder mit dem Suffix "Verschluesselt" enthalten einen AES-256-GCM-
|
|
// Umschlag (siehe src/lib/crypto/feld.ts), niemals Klartext. Sie lassen
|
|
// sich nicht filtern oder sortieren. Wo trotzdem gesucht werden muss,
|
|
// steht daneben ein "...Index"-Feld mit einem HMAC-Blindindex.
|
|
//
|
|
// 3. Alles, was fachlich zu einer betreuten Person gehoert, traegt eine
|
|
// betreuteId. Der Zugriffsschutz prueft grundsaetzlich gegen dieses Feld
|
|
// (Row-Level-Pruefung), nie nur im UI.
|
|
//
|
|
// 4. Loeschen ist ein Papierkorb: "geloeschtAm" wird gesetzt, der Datensatz
|
|
// bleibt 30 Tage bestehen und wird danach hart entfernt.
|
|
//
|
|
// 5. Datumsfelder ohne Uhrzeitbezug (Fristen, Bescheide, Stichtage) sind
|
|
// @db.Date - sonst verschiebt die Zeitzone eine Frist um einen Tag.
|
|
//
|
|
// =============================================================================
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Benutzer, Rollen, Zugriff
|
|
// =============================================================================
|
|
|
|
enum Rolle {
|
|
BETREUER // voller Zugriff auf die zugeordneten Betreuten
|
|
MITLESER // nur lesend, je Modul freigegeben (z.B. Geschwister)
|
|
ADMIN // Benutzerverwaltung, Systemeinstellungen
|
|
}
|
|
|
|
enum Modul {
|
|
STAMMDATEN
|
|
VORGAENGE
|
|
FRISTEN
|
|
DOKUMENTE
|
|
FINANZEN
|
|
SOZIALHILFE
|
|
IMMOBILIE
|
|
VERTRAEGE
|
|
GESUNDHEIT
|
|
KONTAKTE
|
|
BERICHTE
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
name String
|
|
|
|
// Argon2id. Die Parameter stehen im Hash-String selbst.
|
|
passwortHash String
|
|
passwortGeaendertAm DateTime @default(now())
|
|
|
|
// TOTP ist Pflicht. Das Secret liegt verschluesselt, nicht im Klartext.
|
|
totpSecretVerschluesselt String?
|
|
totpAktiviertAm DateTime?
|
|
/// Der zuletzt akzeptierte TOTP-Zeitschritt (Unixzeit / 30).
|
|
///
|
|
/// Ein TOTP-Code gilt 30 Sekunden, mit Toleranz bis zu 90. In dieser Zeit
|
|
/// liesse sich derselbe Code ein zweites Mal einloesen - abgelesen ueber
|
|
/// die Schulter oder abgefangen. Deshalb wird der Schritt festgehalten
|
|
/// und nur noch ein groesserer akzeptiert.
|
|
totpLetzterSchritt Int?
|
|
// Einmal-Wiederherstellungscodes, einzeln Argon2id-gehasht.
|
|
wiederherstellungscodes String[] @default([])
|
|
|
|
rolle Rolle @default(BETREUER)
|
|
aktiv Boolean @default(true)
|
|
letzteAnmeldung DateTime?
|
|
|
|
// Wird hochgezaehlt, um alle bestehenden Sitzungen ungueltig zu machen
|
|
// (Passwortwechsel, Rollenaenderung, Verdacht auf Kompromittierung).
|
|
sitzungsGeneration Int @default(0)
|
|
|
|
/// Die eigene Anschrift - Absenderblock und Ort in der Datumszeile
|
|
/// jedes erzeugten Schreibens. Sie gehoert zum Betreuer und nicht zur
|
|
/// betreuten Person: Sie gilt fuer alle Akten.
|
|
strasse String?
|
|
plz String?
|
|
ort String?
|
|
|
|
// Oberflaechen-Einstellungen
|
|
theme String @default("system") // "system" | "hell" | "dunkel"
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
zugaenge BetreuungsZugang[]
|
|
passkeys Passkey[]
|
|
auditEintraege AuditLog[]
|
|
angelegteVorgaenge Vorgang[] @relation("VorgangAngelegtVon")
|
|
berichte Bericht[]
|
|
|
|
@@index([email])
|
|
@@map("benutzer")
|
|
}
|
|
|
|
/// Verbindet einen Benutzer mit einer betreuten Person und legt fest,
|
|
/// welche Module ein Mitleser sehen darf.
|
|
model BetreuungsZugang {
|
|
id String @id @default(cuid())
|
|
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
rolle Rolle @default(BETREUER)
|
|
|
|
/// Nur fuer MITLESER ausgewertet. BETREUER sieht immer alles.
|
|
freigegebeneModule Modul[] @default([])
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
|
|
@@unique([userId, betreuteId])
|
|
@@index([betreuteId])
|
|
@@map("betreuungs_zugang")
|
|
}
|
|
|
|
/// Haelt fest, dass die Ersteinrichtung stattgefunden hat.
|
|
///
|
|
/// Die Tabelle hat genau eine Zeile mit fester Id. Das ist kein Protokoll,
|
|
/// sondern eine Sperre: Vorher stand im Code "wenn es noch keinen Benutzer
|
|
/// gibt, lege einen an" - zwei Anfragen gleichzeitig kamen beide durch die
|
|
/// Pruefung und legten beide einen Administrator an. Ueber den
|
|
/// Primaerschluessel entscheidet das jetzt die Datenbank, und zwar fuer
|
|
/// genau einen Gewinner.
|
|
model Ersteinrichtung {
|
|
/// Immer "einmalig". Der feste Wert ist die Sperre.
|
|
id String @id
|
|
|
|
abgeschlossenAm DateTime @default(now())
|
|
/// Wer den Zugang angelegt hat - nur zum Nachvollziehen.
|
|
userId String?
|
|
|
|
@@map("ersteinrichtung")
|
|
}
|
|
|
|
/// Fehlgeschlagene und erfolgreiche Anmeldeversuche - Grundlage fuer das
|
|
/// Rate-Limiting am Login. Wird nach 30 Tagen aufgeraeumt.
|
|
model LoginVersuch {
|
|
id String @id @default(cuid())
|
|
email String
|
|
ipHash String?
|
|
erfolg Boolean
|
|
grund String?
|
|
zeitpunkt DateTime @default(now())
|
|
|
|
@@index([email, zeitpunkt])
|
|
@@index([zeitpunkt])
|
|
@@map("login_versuch")
|
|
}
|
|
|
|
/// Ein registrierter Passkey (WebAuthn/FIDO2).
|
|
///
|
|
/// Der private Schluessel verlaesst das Geraet nie - hier liegt nur der
|
|
/// oeffentliche Teil. Ein gestohlener Datenbankabzug erlaubt deshalb keine
|
|
/// Anmeldung, anders als bei einem Passworthash, den man offline angreifen
|
|
/// kann.
|
|
///
|
|
/// Ein Passkey zaehlt bei uns als ZWEI Faktoren: Besitz des Geraets plus
|
|
/// die Nutzerverifikation darauf (Fingerabdruck, Gesicht oder Geraete-PIN).
|
|
/// Deshalb wird bei der Anmeldung "userVerified" erzwungen - siehe
|
|
/// src/lib/auth/passkey.ts. Ohne diese Pruefung waere es nur ein Faktor.
|
|
model Passkey {
|
|
id String @id @default(cuid())
|
|
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
/// Kennung des Authenticators, base64url. Eindeutig ueber alle Benutzer -
|
|
/// derselbe Schluessel darf nicht an zwei Zugaengen haengen.
|
|
credentialId String @unique
|
|
|
|
/// Oeffentlicher Schluessel im COSE-Format. Kein Geheimnis.
|
|
publicKey Bytes
|
|
|
|
/// Signaturzaehler des Authenticators. Zaehlt er nicht hoch, obwohl er
|
|
/// es vorher tat, ist der Schluessel womoeglich geklont worden.
|
|
/// BigInt, weil der Zaehler bis 2^32-1 geht.
|
|
counter BigInt @default(0)
|
|
|
|
/// "usb", "nfc", "ble", "internal", "hybrid" - hilft dem Browser beim
|
|
/// naechsten Mal, das richtige Geraet vorzuschlagen.
|
|
transports String[] @default([])
|
|
|
|
/// Vom Benutzer vergeben ("iPhone von Florian"). Bei mehreren Geraeten
|
|
/// ist das die einzige Moeglichkeit, sie auseinanderzuhalten.
|
|
name String
|
|
|
|
/// "singleDevice" = liegt nur auf diesem Geraet, "multiDevice" = wird vom
|
|
/// Schluesselbund synchronisiert. Steht in der Uebersicht, weil es den
|
|
/// Unterschied macht, ob ein verlorenes Geraet den Zugang mitnimmt.
|
|
geraeteArt String?
|
|
|
|
/// Ob der Anbieter den Schluessel gesichert hat (iCloud, Google, Bitwarden).
|
|
gesichert Boolean @default(false)
|
|
|
|
erstelltAm DateTime @default(now())
|
|
zuletztBenutztAm DateTime?
|
|
|
|
@@index([userId])
|
|
@@map("passkey")
|
|
}
|
|
|
|
enum PasskeyAnfrageArt {
|
|
REGISTRIERUNG
|
|
ANMELDUNG
|
|
}
|
|
|
|
/// Kurzlebiger Speicher fuer die Challenge einer laufenden Passkey-Aktion.
|
|
///
|
|
/// Die Challenge muss serverseitig liegen und genau einmal gelten, sonst
|
|
/// liesse sich eine mitgeschnittene Antwort erneut einspielen. Verbraucht
|
|
/// wird sie durch DELETE - dass genau eine Zeile geloescht wurde, ist der
|
|
/// Beweis, dass sie noch nicht benutzt war.
|
|
///
|
|
/// Bewusst ohne Fremdschluessel auf User: die Zeile lebt fuenf Minuten und
|
|
/// ist kein Fachdatum. Der Wartungslauf raeumt Liegengebliebenes weg.
|
|
model PasskeyAnfrage {
|
|
id String @id @default(cuid())
|
|
|
|
/// base64url, zufaellig. Eindeutig, damit das Verbrauchen atomar ist.
|
|
challenge String @unique
|
|
|
|
art PasskeyAnfrageArt
|
|
|
|
/// Bei der Registrierung bekannt. Bei der Anmeldung nicht - dort weiss
|
|
/// der Server erst nach der Antwort, wer sich anmeldet.
|
|
userId String?
|
|
|
|
ablaufAm DateTime
|
|
erstelltAm DateTime @default(now())
|
|
|
|
@@index([ablaufAm])
|
|
@@map("passkey_anfrage")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Betreute Person
|
|
// =============================================================================
|
|
|
|
enum Aufgabenkreis {
|
|
VERMOEGENSSORGE
|
|
GESUNDHEITSSORGE
|
|
AUFENTHALTSBESTIMMUNG
|
|
WOHNUNGSANGELEGENHEITEN
|
|
BEHOERDENANGELEGENHEITEN
|
|
POST_UND_FERNMELDEVERKEHR
|
|
RECHTS_ANTRAGS_UND_BEHOERDENVERTRETUNG
|
|
ENTSCHEIDUNG_UEBER_FREIHEITSENTZIEHENDE_MASSNAHMEN
|
|
SONSTIGES
|
|
}
|
|
|
|
model Betreute {
|
|
id String @id @default(cuid())
|
|
|
|
// --- Person -------------------------------------------------------------
|
|
vorname String
|
|
nachname String
|
|
geburtsname String?
|
|
geburtsdatum DateTime @db.Date
|
|
geburtsort String?
|
|
anrede String? // "Frau", "Herr", ...
|
|
|
|
// --- Aufenthalt / Heim ---------------------------------------------------
|
|
heimName String?
|
|
heimStrasse String?
|
|
heimPlz String?
|
|
heimOrt String?
|
|
heimZimmer String?
|
|
heimTelefon String?
|
|
heimEinzugAm DateTime? @db.Date
|
|
|
|
// Letzte eigene Wohnanschrift - bleibt fuer Behoerden relevant.
|
|
fruehereStrasse String?
|
|
frueherePlz String?
|
|
fruehererOrt String?
|
|
|
|
// --- Gesundheit (besondere Kategorie nach Art. 9 DSGVO) ------------------
|
|
krankenkasse String?
|
|
krankenversichertennummerVerschluesselt String?
|
|
pflegekasse String?
|
|
pflegegrad Int?
|
|
pflegegradSeit DateTime? @db.Date
|
|
pflegegradBescheidVom DateTime? @db.Date
|
|
schwerbehinderungGdB Int?
|
|
schwerbehinderungMerkzeichen String?
|
|
diagnosenVerschluesselt String?
|
|
|
|
/// Der nach § 1821 BGB zu ermittelnde Wille der betreuten Person.
|
|
/// Seit der Reform 2023 Leitschnur des Betreuerhandelns.
|
|
ermittelterWilleVerschluesselt String?
|
|
|
|
patientenverfuegungVorhanden Boolean @default(false)
|
|
vorsorgevollmachtVorhanden Boolean @default(false)
|
|
|
|
// --- Betreuungsgericht ---------------------------------------------------
|
|
betreuungsgericht String?
|
|
aktenzeichenGericht String?
|
|
/// Steuert das Betreuungsjahr und damit fast alle gesetzlichen Fristen.
|
|
bestellungAm DateTime? @db.Date
|
|
bestellungBis DateTime? @db.Date
|
|
aufgabenkreise Aufgabenkreis[] @default([])
|
|
aufgabenkreiseFreitext String?
|
|
|
|
/// Befreiter Betreuer nach § 1859 BGB (Angehoerige) - steuert, ob die
|
|
/// knappe Vermoegensuebersicht oder die volle Rechnungslegung faellig ist.
|
|
befreiterBetreuer Boolean @default(true)
|
|
ehrenamtlich Boolean @default(true)
|
|
|
|
// --- Sozialleistungstraeger ---------------------------------------------
|
|
aktenzeichenBezirk String?
|
|
aktenzeichenSozialamt String?
|
|
rentenversicherungsnummerVerschluesselt String?
|
|
steuerIdVerschluesselt String?
|
|
|
|
notizenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
zugaenge BetreuungsZugang[]
|
|
vorgaenge Vorgang[]
|
|
fristen Frist[]
|
|
dokumente Dokument[]
|
|
konten Konto[]
|
|
buchungen Buchung[]
|
|
zahlungsgruppen ZahlungsgruppeGeklaert[]
|
|
schreibvorlagen Schreibvorlage[]
|
|
vermoegensposten Vermoegensposten[]
|
|
immobilien Immobilie[]
|
|
vertraege Vertrag[]
|
|
kontakte Kontakt[]
|
|
kontaktprotokolle Kontaktprotokoll[]
|
|
gesundheitsereignisse Gesundheitsereignis[]
|
|
genehmigungen Genehmigung[]
|
|
sozialhilfeAntraege SozialhilfeAntrag[]
|
|
leistungen Leistung[]
|
|
heimkosten Heimkostenmonat[]
|
|
heimvertrag Heimvertrag?
|
|
tags Tag[]
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
auditEintraege AuditLog[]
|
|
berichte Bericht[]
|
|
|
|
@@index([nachname, vorname])
|
|
@@map("betreute")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Vorgaenge (Fallakte) und Genehmigungen
|
|
// =============================================================================
|
|
|
|
enum VorgangKategorie {
|
|
GERICHT
|
|
SOZIALHILFE
|
|
IMMOBILIE
|
|
VERSICHERUNG
|
|
HEIM_PFLEGE
|
|
VERTRAEGE
|
|
STEUERN
|
|
SONSTIGES
|
|
}
|
|
|
|
enum VorgangStatus {
|
|
OFFEN
|
|
IN_ARBEIT
|
|
WARTET_AUF_DRITTE
|
|
WARTET_AUF_GENEHMIGUNG
|
|
ERLEDIGT
|
|
}
|
|
|
|
model Vorgang {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
titel String
|
|
beschreibungVerschluesselt String?
|
|
kategorie VorgangKategorie
|
|
status VorgangStatus @default(OFFEN)
|
|
prioritaet Int @default(2) // 1 hoch, 2 normal, 3 niedrig
|
|
|
|
/// Behoerde oder Gegenstelle, die am Zug ist.
|
|
gegenstelle String?
|
|
aktenzeichen String?
|
|
|
|
/// Die Person bei der Gegenstelle, die den Vorgang bearbeitet. Ein
|
|
/// namentlich adressiertes Schreiben landet bei ihr statt in der
|
|
/// Poststelle - beim Bezirk ist das der Unterschied zwischen zwei
|
|
/// Wochen und zwei Monaten. SetNull: Ein Vorgang darf nicht daran
|
|
/// haengen, dass es den Kontakt noch gibt.
|
|
ansprechpartnerId String?
|
|
ansprechpartner Kontakt? @relation("VorgangAnsprechpartner", fields: [ansprechpartnerId], references: [id], onDelete: SetNull)
|
|
|
|
verantwortlich String?
|
|
angelegtVonId String?
|
|
angelegtVon User? @relation("VorgangAngelegtVon", fields: [angelegtVonId], references: [id], onDelete: SetNull)
|
|
|
|
/// "Was ist der naechste konkrete Handgriff?" - treibt das Dashboard.
|
|
naechsterSchritt String?
|
|
faelligAm DateTime? @db.Date
|
|
|
|
/// Wann wurde zuletzt mit der Gegenstelle gesprochen/geschrieben?
|
|
/// Grundlage fuer die Kachel "wartet auf andere" und die Sachstandsanfrage.
|
|
letzterKontaktAm DateTime? @db.Date
|
|
|
|
/// Loest die feste Ablaufkette Mitteilung -> Antrag -> Anhoerung ->
|
|
/// Beschluss -> Rechtskraft -> Vollzug aus.
|
|
genehmigungspflichtig Boolean @default(false)
|
|
|
|
/// Aus welcher Vorlage wurde der Vorgang erzeugt (fuer Auswertung)?
|
|
vorlageSchluessel String?
|
|
|
|
erledigtAm DateTime?
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
fristen Frist[]
|
|
buchungen Buchung[]
|
|
genehmigungen Genehmigung[]
|
|
kontaktprotokolle Kontaktprotokoll[]
|
|
aufgaben Aufgabe[]
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
tags Tag[] @relation("VorgangTags")
|
|
kontakte Kontakt[] @relation("VorgangKontakte")
|
|
|
|
@@index([betreuteId, status])
|
|
@@index([betreuteId, kategorie])
|
|
@@index([faelligAm])
|
|
@@map("vorgang")
|
|
}
|
|
|
|
/// Einzelner Haken innerhalb eines Vorgangs. Vorgangs-Vorlagen bringen
|
|
/// ihre Aufgabenliste vorbefuellt mit.
|
|
model Aufgabe {
|
|
id String @id @default(cuid())
|
|
|
|
vorgangId String
|
|
vorgang Vorgang @relation(fields: [vorgangId], references: [id], onDelete: Cascade)
|
|
|
|
titel String
|
|
beschreibung String?
|
|
erledigt Boolean @default(false)
|
|
erledigtAm DateTime?
|
|
faelligAm DateTime? @db.Date
|
|
reihenfolge Int @default(0)
|
|
|
|
erstelltAm DateTime @default(now())
|
|
|
|
@@index([vorgangId])
|
|
@@map("aufgabe")
|
|
}
|
|
|
|
enum GenehmigungStatus {
|
|
GEPLANT // erkannt, noch nichts unternommen
|
|
MITGETEILT // Mitteilung ans Gericht raus
|
|
BEANTRAGT
|
|
ANGEHOERT // Anhoerung der Betreuten / des Verfahrenspflegers erfolgt
|
|
ERTEILT
|
|
VERSAGT
|
|
RECHTSKRAEFTIG
|
|
VOLLZOGEN // erst moeglich, wenn ein Beschlussdokument hinterlegt ist
|
|
}
|
|
|
|
model Genehmigung {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
vorgangId String
|
|
vorgang Vorgang @relation(fields: [vorgangId], references: [id], onDelete: Cascade)
|
|
|
|
titel String
|
|
|
|
/// Freitext, z.B. "§ 1850 Nr. 1 BGB" - bewusst nicht als Enum, damit
|
|
/// auch seltene Tatbestaende erfasst werden koennen.
|
|
rechtsgrundlage String
|
|
status GenehmigungStatus @default(GEPLANT)
|
|
|
|
mitteilungAm DateTime? @db.Date
|
|
antragAm DateTime? @db.Date
|
|
anhoerungAm DateTime? @db.Date
|
|
beschlussAm DateTime? @db.Date
|
|
rechtskraftAm DateTime? @db.Date
|
|
vollzugAm DateTime? @db.Date
|
|
|
|
/// Ohne dieses Beschlussdokument darf der Status nicht auf VOLLZOGEN
|
|
/// wechseln (§ 1858 BGB: Genehmigung vor dem Rechtsgeschaeft).
|
|
beschlussDokumentId String? @unique
|
|
beschlussDokument Dokument? @relation("Beschlussdokument", fields: [beschlussDokumentId], references: [id], onDelete: SetNull)
|
|
|
|
notizenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
|
|
@@index([betreuteId, status])
|
|
@@index([vorgangId])
|
|
@@map("genehmigung")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Fristen
|
|
// =============================================================================
|
|
|
|
enum FristTyp {
|
|
GESETZLICH // z.B. Jahresbericht § 1863 BGB
|
|
BEHOERDLICH // vom Bezirk oder Gericht gesetzt
|
|
SELBST_GESETZT
|
|
WIEDERVORLAGE
|
|
}
|
|
|
|
enum FristStatus {
|
|
OFFEN
|
|
ERLEDIGT
|
|
VERSCHOBEN
|
|
ENTFALLEN
|
|
}
|
|
|
|
model Frist {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
vorgangId String?
|
|
vorgang Vorgang? @relation(fields: [vorgangId], references: [id], onDelete: SetNull)
|
|
|
|
titel String
|
|
beschreibung String?
|
|
datum DateTime @db.Date
|
|
typ FristTyp @default(SELBST_GESETZT)
|
|
status FristStatus @default(OFFEN)
|
|
|
|
/// Freitext, z.B. "§ 1863 Abs. 3 BGB".
|
|
rechtsgrundlage String?
|
|
|
|
/// Tage vor dem Termin, an denen gewarnt wird.
|
|
vorwarnungTage Int[] @default([60, 30, 7, 1])
|
|
|
|
/// iCalendar-RRULE, z.B. "FREQ=YEARLY;INTERVAL=1". Beim Erledigen einer
|
|
/// wiederkehrenden Frist wird daraus die naechste erzeugt.
|
|
wiederholungRrule String?
|
|
|
|
/// Zeigt auf die Frist, aus der diese hervorgegangen ist.
|
|
vorgaengerFristId String? @unique
|
|
vorgaengerFrist Frist? @relation("Fristkette", fields: [vorgaengerFristId], references: [id], onDelete: SetNull)
|
|
nachfolgerFrist Frist? @relation("Fristkette")
|
|
|
|
/// Ausschlussfrist: laeuft sie ab, ist der Anspruch weg
|
|
/// (z.B. Aufwandspauschale § 1878 BGB). Wird deutlich rot markiert.
|
|
ausschlussfrist Boolean @default(false)
|
|
|
|
/// Automatisch aus dem Bestellungsdatum abgeleitete Pflichtfrist.
|
|
/// Verhindert doppeltes Anlegen beim erneuten Ableiten.
|
|
systemSchluessel String?
|
|
|
|
/// Wann wurde zuletzt eine Erinnerung verschickt (Vorwarnstufe in Tagen)?
|
|
letzteErinnerungStufe Int?
|
|
|
|
erledigtAm DateTime?
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
|
|
@@unique([betreuteId, systemSchluessel])
|
|
@@index([betreuteId, status, datum])
|
|
@@index([datum])
|
|
@@map("frist")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Dokumente
|
|
// =============================================================================
|
|
|
|
enum DokumentRichtung {
|
|
EINGANG
|
|
AUSGANG
|
|
INTERN
|
|
}
|
|
|
|
enum DokumentArt {
|
|
BESCHEID
|
|
ANTRAG
|
|
SCHREIBEN
|
|
RECHNUNG
|
|
KONTOAUSZUG
|
|
VERTRAG
|
|
BESCHLUSS
|
|
URKUNDE
|
|
GUTACHTEN
|
|
AUSWEIS
|
|
PROTOKOLL
|
|
QUITTUNG
|
|
MAHNUNG
|
|
VOLLMACHT
|
|
ATTEST
|
|
GRUNDBUCHAUSZUG
|
|
FOTO
|
|
SONSTIGES
|
|
}
|
|
|
|
enum PosteingangStatus {
|
|
NEU
|
|
GESICHTET
|
|
ZUGEORDNET
|
|
BEANTWORTET
|
|
}
|
|
|
|
model Dokument {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
titel String
|
|
art DokumentArt @default(SONSTIGES)
|
|
richtung DokumentRichtung @default(EINGANG)
|
|
|
|
/// Datum des Schreibens (steht oben auf dem Brief).
|
|
schreibenVom DateTime? @db.Date
|
|
/// Wann es bei mir eingegangen ist - massgeblich fuer Rechtsbehelfsfristen.
|
|
eingangAm DateTime? @db.Date
|
|
versandAm DateTime? @db.Date
|
|
|
|
absender String?
|
|
empfaenger String?
|
|
aktenzeichen String?
|
|
|
|
// --- Datei im Objektspeicher --------------------------------------------
|
|
/// Schluessel im S3-Bucket. Ausgeliefert wird nur ueber kurzlebige
|
|
/// signierte URLs, niemals oeffentlich.
|
|
objektSchluessel String?
|
|
dateiname String?
|
|
mimeTyp String?
|
|
groesseBytes Int?
|
|
/// SHA-256 der Datei - Nachweis der Unveraendertheit gegenueber Gericht.
|
|
pruefsumme String?
|
|
seitenzahl Int?
|
|
|
|
/// Volltext aus der OCR (Tesseract, deutsch). Bleibt im Klartext, weil er
|
|
/// sonst nicht durchsuchbar waere; die Datei selbst liegt verschluesselt
|
|
/// im Objektspeicher. In der README als bewusste Abwaegung dokumentiert.
|
|
ocrText String?
|
|
ocrStatus String? // "ausstehend" | "laeuft" | "fertig" | "fehlgeschlagen" | "nicht_verfuegbar"
|
|
|
|
/// Von Postgres per Trigger gepflegter Suchvektor (deutsche Konfiguration).
|
|
suchvektor Unsupported("tsvector")?
|
|
|
|
posteingangStatus PosteingangStatus @default(NEU)
|
|
|
|
/// Wo liegt das Original im Papierordner? Papier bleibt Papier.
|
|
ablageortPapier String?
|
|
|
|
/// Wiedervorlage bzw. Ablaufdatum (z.B. Gueltigkeit Betreuerausweis).
|
|
wiedervorlageAm DateTime? @db.Date
|
|
ablaufAm DateTime? @db.Date
|
|
|
|
notizenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
belegFuer Buchung[] @relation("Buchungsbeleg")
|
|
beschlussFuer Genehmigung? @relation("Beschlussdokument")
|
|
nachweisFuer Wertstand[] @relation("Wertnachweis")
|
|
tags Tag[] @relation("DokumentTags")
|
|
|
|
@@index([betreuteId, posteingangStatus])
|
|
@@index([betreuteId, eingangAm])
|
|
@@index([suchvektor], type: Gin)
|
|
@@map("dokument")
|
|
}
|
|
|
|
/// Verbindet ein Dokument mit genau einer Fachentitaet. Bewusst als eigene
|
|
/// Tabelle mit echten Fremdschluesseln - eine Akte muss referenzsicher sein.
|
|
/// Die Anwendung stellt sicher, dass genau eine Ziel-Spalte gesetzt ist.
|
|
model DokumentVerknuepfung {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
dokumentId String
|
|
dokument Dokument @relation(fields: [dokumentId], references: [id], onDelete: Cascade)
|
|
|
|
vorgangId String?
|
|
vorgang Vorgang? @relation(fields: [vorgangId], references: [id], onDelete: Cascade)
|
|
fristId String?
|
|
frist Frist? @relation(fields: [fristId], references: [id], onDelete: Cascade)
|
|
buchungId String?
|
|
buchung Buchung? @relation(fields: [buchungId], references: [id], onDelete: Cascade)
|
|
vermoegenspostenId String?
|
|
vermoegensposten Vermoegensposten? @relation(fields: [vermoegenspostenId], references: [id], onDelete: Cascade)
|
|
immobilieId String?
|
|
immobilie Immobilie? @relation(fields: [immobilieId], references: [id], onDelete: Cascade)
|
|
vertragId String?
|
|
vertrag Vertrag? @relation(fields: [vertragId], references: [id], onDelete: Cascade)
|
|
kontaktprotokollId String?
|
|
kontaktprotokoll Kontaktprotokoll? @relation(fields: [kontaktprotokollId], references: [id], onDelete: Cascade)
|
|
gesundheitsereignisId String?
|
|
gesundheitsereignis Gesundheitsereignis? @relation(fields: [gesundheitsereignisId], references: [id], onDelete: Cascade)
|
|
genehmigungId String?
|
|
genehmigung Genehmigung? @relation(fields: [genehmigungId], references: [id], onDelete: Cascade)
|
|
nachforderungId String?
|
|
nachforderung Nachforderung? @relation(fields: [nachforderungId], references: [id], onDelete: Cascade)
|
|
bescheidId String?
|
|
bescheid Bescheid? @relation(fields: [bescheidId], references: [id], onDelete: Cascade)
|
|
kontaktId String?
|
|
kontakt Kontakt? @relation(fields: [kontaktId], references: [id], onDelete: Cascade)
|
|
|
|
bemerkung String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
|
|
@@index([dokumentId])
|
|
@@index([betreuteId])
|
|
@@index([vorgangId])
|
|
@@map("dokument_verknuepfung")
|
|
}
|
|
|
|
model Tag {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
name String
|
|
farbe String @default("slate")
|
|
|
|
erstelltAm DateTime @default(now())
|
|
|
|
dokumente Dokument[] @relation("DokumentTags")
|
|
vorgaenge Vorgang[] @relation("VorgangTags")
|
|
|
|
@@unique([betreuteId, name])
|
|
@@map("tag")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Geld: Konten, Kassenbuch, Vermoegen
|
|
// =============================================================================
|
|
|
|
enum KontoArt {
|
|
GIRO
|
|
SPAR
|
|
TAGESGELD
|
|
FESTGELD
|
|
DEPOT
|
|
VERWAHRGELD_HEIM // Taschengeldkonto in der Einrichtung
|
|
BARKASSE
|
|
SONSTIGES
|
|
}
|
|
|
|
model Konto {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
bezeichnung String
|
|
bank String?
|
|
art KontoArt @default(GIRO)
|
|
|
|
ibanVerschluesselt String?
|
|
/// HMAC-Blindindex der IBAN - erlaubt "gibt es dieses Konto schon?",
|
|
/// ohne die IBAN im Klartext zu speichern.
|
|
ibanIndex String?
|
|
bic String?
|
|
|
|
/// Konto des Betreuers, nicht der Betreuten. Buchungen darauf werden
|
|
/// als Auslage gefuehrt und loesen eine Warnung aus.
|
|
istBetreuerkonto Boolean @default(false)
|
|
|
|
eroeffnetAm DateTime? @db.Date
|
|
geschlossenAm DateTime? @db.Date
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
buchungen Buchung[]
|
|
saldostaende Saldostand[]
|
|
vermoegensposten Vermoegensposten[]
|
|
|
|
@@index([betreuteId])
|
|
@@index([ibanIndex])
|
|
@@map("konto")
|
|
}
|
|
|
|
/// Kontostand zu einem Stichtag. Wird nie ueberschrieben - jede
|
|
/// Vermoegensuebersicht muss spaeter reproduzierbar bleiben.
|
|
model Saldostand {
|
|
id String @id @default(cuid())
|
|
|
|
kontoId String
|
|
konto Konto @relation(fields: [kontoId], references: [id], onDelete: Cascade)
|
|
|
|
stichtag DateTime @db.Date
|
|
betragCent BigInt
|
|
quelle String? // "Kontoauszug Nr. 3", "Online-Banking", ...
|
|
|
|
erstelltAm DateTime @default(now())
|
|
|
|
@@unique([kontoId, stichtag])
|
|
@@index([kontoId, stichtag])
|
|
@@map("saldostand")
|
|
}
|
|
|
|
enum BuchungRichtung {
|
|
EINNAHME
|
|
AUSGABE
|
|
UMBUCHUNG
|
|
}
|
|
|
|
enum BuchungKategorie {
|
|
RENTE
|
|
PFLEGEKASSE
|
|
SOZIALHILFE
|
|
LANDESPFLEGEGELD
|
|
ZINSEN
|
|
MIETEINNAHME
|
|
SONSTIGE_EINNAHME
|
|
HEIMKOSTEN
|
|
BARBETRAG
|
|
BEKLEIDUNG
|
|
ARZT_UND_MEDIKAMENTE
|
|
ZUZAHLUNG
|
|
VERSICHERUNG
|
|
IMMOBILIE_BETRIEBSKOSTEN
|
|
IMMOBILIE_INSTANDHALTUNG
|
|
GRUNDSTEUER
|
|
STEUERN
|
|
GEBUEHREN_GERICHT
|
|
PORTO_UND_KOPIEN
|
|
FAHRTKOSTEN
|
|
TELEFON_INTERNET
|
|
RUNDFUNKBEITRAG
|
|
BESTATTUNGSVORSORGE
|
|
BANKGEBUEHREN
|
|
SONSTIGE_AUSGABE
|
|
UMBUCHUNG
|
|
}
|
|
|
|
/// Eine Gruppe wiederkehrender Zahlungen, zu der bewusst KEIN Vertrag
|
|
/// gefuehrt wird - Miete, Strom, Rundfunkbeitrag, Finanzamt.
|
|
///
|
|
/// Die Entscheidung haengt am normalisierten Namen der Gegenstelle, nicht
|
|
/// an den Buchungen. Nur so ueberlebt sie den naechsten Kontoauszug: Beim
|
|
/// Import entstehen neue Buchungszeilen, der Empfaenger bleibt derselbe.
|
|
///
|
|
/// ACHTUNG: Der Schluessel wird von normalisieren() in
|
|
/// src/lib/finanzen/wiederkehrend.ts gebildet. Aendert sich diese Funktion,
|
|
/// passen bestehende Schluessel nicht mehr und abgehakte Gruppen tauchen
|
|
/// wieder auf. Das ist unangenehm, aber ungefaehrlich - schlimmer waere,
|
|
/// eine Gruppe stillschweigend weiter zu verbergen, die inzwischen anders
|
|
/// zugeschnitten ist.
|
|
/// Eine eigene Schreibvorlage.
|
|
///
|
|
/// Die mitgelieferten Vorlagen (Rueckkaufswert, Kuendigung,
|
|
/// Sachstandsanfrage) stehen bewusst NICHT in dieser Tabelle - sie sind
|
|
/// Code, getestet und unveraenderlich. "Wiederherstellen" heisst: die
|
|
/// mitgelieferte Fassung als neue Kopie hierher holen.
|
|
///
|
|
/// Der Text traegt Platzhalter im Format {{name}} - aufgeloest in
|
|
/// src/lib/schreiben/platzhalter.ts. Er ist bewusst NICHT verschluesselt:
|
|
/// Eine Vorlage ist ein Textbaustein ohne Fallbezug; die Fachdaten kommen
|
|
/// erst beim Erzeugen hinein.
|
|
model Schreibvorlage {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
name String
|
|
betreff String @default("")
|
|
text String
|
|
|
|
/// Beim Speichern bestaetigt, dass der fehlende Bestellungsabsatz
|
|
/// Absicht ist - dann wird nicht bei jedem Bearbeiten erneut gewarnt.
|
|
ohneBestellungsabsatz Boolean @default(false)
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
@@index([betreuteId])
|
|
@@map("schreibvorlage")
|
|
}
|
|
|
|
model ZahlungsgruppeGeklaert {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
/// Normalisierter Name der Gegenstelle.
|
|
schluessel String
|
|
|
|
/// Die Schreibweise, wie sie beim Abhaken auf dem Bildschirm stand -
|
|
/// damit spaeter noch erkennbar ist, worum es ging.
|
|
anzeigename String
|
|
|
|
/// Warum kein Vertrag? Freiwillig, aber in einem Jahr Gold wert.
|
|
begruendung String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
|
|
@@unique([betreuteId, schluessel])
|
|
@@index([betreuteId])
|
|
@@map("zahlungsgruppe_geklaert")
|
|
}
|
|
|
|
model Buchung {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
kontoId String?
|
|
konto Konto? @relation(fields: [kontoId], references: [id], onDelete: SetNull)
|
|
|
|
vorgangId String?
|
|
vorgang Vorgang? @relation(fields: [vorgangId], references: [id], onDelete: SetNull)
|
|
|
|
datum DateTime @db.Date
|
|
wertstellung DateTime? @db.Date
|
|
betragCent BigInt
|
|
richtung BuchungRichtung
|
|
kategorie BuchungKategorie @default(SONSTIGE_AUSGABE)
|
|
gegenstelle String?
|
|
verwendungszweckVerschluesselt String?
|
|
|
|
belegId String?
|
|
beleg Dokument? @relation("Buchungsbeleg", fields: [belegId], references: [id], onDelete: SetNull)
|
|
/// Buchung ohne Beleg wird in der Liste markiert - Belegzwang.
|
|
belegFehltBegruendung String?
|
|
|
|
/// Dauerzahlung zu einem Vertrag. Bei einem Dauerschuldverhaeltnis IST
|
|
/// der Vertrag der Beleg - fuer zwoelf gleiche Lastschriften gibt es
|
|
/// keine zwoelf Quittungen, sondern eine Police.
|
|
///
|
|
/// onDelete: SetNull ist Absicht und Teil der Zusage: Verschwindet der
|
|
/// Vertrag, faellt die Buchung zurueck in "Beleg fehlt". Ein Nachweis,
|
|
/// der auf etwas zeigt, das es nicht mehr gibt, waere schlimmer als
|
|
/// gar keiner.
|
|
vertragId String?
|
|
vertrag Vertrag? @relation("Vertragszahlung", fields: [vertragId], references: [id], onDelete: SetNull)
|
|
|
|
/// Wurde die Zuordnung beim Import automatisch gesetzt? Dann ist sie
|
|
/// eine Vermutung der Maschine und in der Liste als solche erkennbar.
|
|
vertragAutomatisch Boolean @default(false)
|
|
|
|
/// false = Auslage des Betreuers, die erstattet werden muss.
|
|
betrifftBetreute Boolean @default(true)
|
|
/// Verschiebt das Vermoegen (z.B. Verkaufserloes), nicht nur laufende
|
|
/// Einnahme/Ausgabe. Relevant fuer die Vermoegensuebersicht.
|
|
vermoegenswirksam Boolean @default(false)
|
|
|
|
/// Herkunft eines CSV-/CAMT-Imports, verhindert Doppelbuchungen.
|
|
importReferenz String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
|
|
@@unique([kontoId, importReferenz])
|
|
@@index([betreuteId, datum])
|
|
@@index([betreuteId, kategorie])
|
|
@@index([vertragId])
|
|
@@map("buchung")
|
|
}
|
|
|
|
enum VermoegensArt {
|
|
KONTO
|
|
IMMOBILIE
|
|
VERSICHERUNG
|
|
FAHRZEUG
|
|
WERTGEGENSTAND
|
|
FORDERUNG
|
|
VERBINDLICHKEIT
|
|
SONSTIGES
|
|
}
|
|
|
|
/// Bewertung eines Postens im Sozialhilfeverfahren. Das ist MEINE
|
|
/// Dokumentation und Argumentation, keine automatische Rechtsauskunft.
|
|
enum SozialhilfeBewertung {
|
|
UNGEPRUEFT
|
|
VERWERTBAR
|
|
GESCHUETZT
|
|
STRITTIG
|
|
}
|
|
|
|
model Vermoegensposten {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
bezeichnung String
|
|
art VermoegensArt
|
|
|
|
// Optionale Verbindung zum konkreten Objekt.
|
|
kontoId String?
|
|
konto Konto? @relation(fields: [kontoId], references: [id], onDelete: SetNull)
|
|
immobilieId String?
|
|
immobilie Immobilie? @relation(fields: [immobilieId], references: [id], onDelete: SetNull)
|
|
vertragId String?
|
|
vertrag Vertrag? @relation(fields: [vertragId], references: [id], onDelete: SetNull)
|
|
|
|
/// Passiva werden als eigener Posten mit Art VERBINDLICHKEIT gefuehrt;
|
|
/// der Wert bleibt positiv, das Vorzeichen ergibt sich aus der Art.
|
|
bewertungSozialhilfe SozialhilfeBewertung @default(UNGEPRUEFT)
|
|
/// Warum halte ich den Posten fuer geschuetzt? Verweis auf § 90 SGB XII,
|
|
/// Schonvermoegen, Bestattungsvorsorge, selbst bewohntes Hausgrundstueck.
|
|
bewertungBegruendungVerschluesselt String?
|
|
|
|
notizenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
wertstaende Wertstand[]
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
|
|
@@index([betreuteId, art])
|
|
@@map("vermoegensposten")
|
|
}
|
|
|
|
/// Ein Wert zu einem Stichtag. Historie, nichts wird ueberschrieben.
|
|
model Wertstand {
|
|
id String @id @default(cuid())
|
|
|
|
vermoegenspostenId String
|
|
vermoegensposten Vermoegensposten @relation(fields: [vermoegenspostenId], references: [id], onDelete: Cascade)
|
|
|
|
stichtag DateTime @db.Date
|
|
wertCent BigInt
|
|
/// Woher stammt der Wert? "Rueckkaufswertmitteilung vom 12.03.2026",
|
|
/// "Maklergutachten", "Bodenrichtwert", "eigene Schaetzung".
|
|
wertquelle String
|
|
|
|
nachweisDokumentId String?
|
|
nachweisDokument Dokument? @relation("Wertnachweis", fields: [nachweisDokumentId], references: [id], onDelete: SetNull)
|
|
|
|
bemerkung String?
|
|
erstelltAm DateTime @default(now())
|
|
|
|
@@unique([vermoegenspostenId, stichtag])
|
|
@@index([vermoegenspostenId, stichtag])
|
|
@@map("wertstand")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Immobilie
|
|
// =============================================================================
|
|
|
|
enum ImmobilienStatusSozialhilfe {
|
|
UNGEKLAERT
|
|
SELBST_BEWOHNT_GESCHUETZT
|
|
HAERTEFALL_GELTEND_GEMACHT
|
|
DARLEHEN_91_SGB_XII
|
|
VERKAUF_LAEUFT
|
|
VERWERTET
|
|
}
|
|
|
|
model Immobilie {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
bezeichnung String
|
|
strasse String?
|
|
plz String?
|
|
ort String?
|
|
|
|
grundbuchamt String?
|
|
grundbuchBlatt String?
|
|
gemarkung String?
|
|
flurstueck String?
|
|
eigentumsanteil String? // "1/1", "1/2", ...
|
|
wohnflaecheQm Int?
|
|
grundstueckQm Int?
|
|
baujahr Int?
|
|
|
|
bewohntStatus String? // "leerstehend", "vermietet", "selbst bewohnt"
|
|
leerstandSeit DateTime? @db.Date
|
|
|
|
statusSozialhilfe ImmobilienStatusSozialhilfe @default(UNGEKLAERT)
|
|
statusBegruendungVerschluesselt String?
|
|
|
|
notizenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
belastungen Belastung[]
|
|
checklistenPunkte ImmobilienCheckpunkt[]
|
|
vermoegensposten Vermoegensposten[]
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
|
|
@@index([betreuteId])
|
|
@@map("immobilie")
|
|
}
|
|
|
|
model Belastung {
|
|
id String @id @default(cuid())
|
|
|
|
immobilieId String
|
|
immobilie Immobilie @relation(fields: [immobilieId], references: [id], onDelete: Cascade)
|
|
|
|
art String // "Grundschuld", "Wohnrecht", "Niessbrauch", "Reallast"
|
|
abteilung String? // Grundbuch-Abteilung II oder III
|
|
laufendeNr String?
|
|
glaeubiger String?
|
|
betragCent BigInt?
|
|
bemerkung String?
|
|
geloeschtImGrundbuchAm DateTime? @db.Date
|
|
|
|
erstelltAm DateTime @default(now())
|
|
|
|
@@index([immobilieId])
|
|
@@map("belastung")
|
|
}
|
|
|
|
/// Pflichten bei Leerstand: Versicherung informiert, Frostschutz,
|
|
/// Winterdienst, Zaehlerstaende, Schluesselverzeichnis, Fotodoku.
|
|
model ImmobilienCheckpunkt {
|
|
id String @id @default(cuid())
|
|
|
|
immobilieId String
|
|
immobilie Immobilie @relation(fields: [immobilieId], references: [id], onDelete: Cascade)
|
|
|
|
titel String
|
|
beschreibung String?
|
|
erledigt Boolean @default(false)
|
|
erledigtAm DateTime? @db.Date
|
|
faelligAm DateTime? @db.Date
|
|
/// Wiederkehrend (z.B. Kontrollgang, Zaehlerstand) als RRULE.
|
|
wiederholungRrule String?
|
|
reihenfolge Int @default(0)
|
|
|
|
erstelltAm DateTime @default(now())
|
|
|
|
@@index([immobilieId])
|
|
@@map("immobilien_checkpunkt")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Versicherungen und Vertraege
|
|
// =============================================================================
|
|
|
|
enum VertragArt {
|
|
LEBENSVERSICHERUNG
|
|
STERBEGELDVERSICHERUNG
|
|
BESTATTUNGSVORSORGE
|
|
RENTENVERSICHERUNG_PRIVAT
|
|
UNFALLVERSICHERUNG
|
|
HAFTPFLICHT
|
|
HAUSRAT
|
|
WOHNGEBAEUDE
|
|
KRANKENZUSATZ
|
|
PFLEGEZUSATZ
|
|
RECHTSSCHUTZ
|
|
STERBEKASSE
|
|
STROM
|
|
GAS
|
|
WASSER
|
|
TELEFON_INTERNET
|
|
RUNDFUNKBEITRAG
|
|
MITGLIEDSCHAFT
|
|
ABONNEMENT
|
|
SONSTIGES
|
|
}
|
|
|
|
enum VertragStatus {
|
|
AKTIV
|
|
BEITRAGSFREI
|
|
RUHEND
|
|
GEKUENDIGT
|
|
ABGETRETEN
|
|
VERWERTET
|
|
ABGELAUFEN
|
|
}
|
|
|
|
enum ZahlRhythmus {
|
|
MONATLICH
|
|
VIERTELJAEHRLICH
|
|
HALBJAEHRLICH
|
|
JAEHRLICH
|
|
EINMALIG
|
|
}
|
|
|
|
model Vertrag {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
bezeichnung String
|
|
gesellschaft String?
|
|
art VertragArt
|
|
status VertragStatus @default(AKTIV)
|
|
|
|
vertragsnummerVerschluesselt String?
|
|
|
|
beginnAm DateTime? @db.Date
|
|
endeAm DateTime? @db.Date
|
|
/// Kuendigungsfrist in Monaten zum Ende der Laufzeit.
|
|
kuendigungsfristMonate Int?
|
|
kuendigungZum DateTime? @db.Date
|
|
gekuendigtAm DateTime? @db.Date
|
|
|
|
beitragCent BigInt?
|
|
rhythmus ZahlRhythmus?
|
|
|
|
/// Bewertung fuer das Sozialamt - meine Argumentation, keine Auskunft.
|
|
bewertungSozialhilfe SozialhilfeBewertung @default(UNGEPRUEFT)
|
|
bewertungBegruendungVerschluesselt String?
|
|
|
|
/// Wann wurde zuletzt ein Rueckkaufswert angefordert? Steuert die
|
|
/// Wiedervorlage nach vier Wochen.
|
|
rueckkaufswertAngefragtAm DateTime? @db.Date
|
|
|
|
notizenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
vermoegensposten Vermoegensposten[]
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
zahlungen Buchung[] @relation("Vertragszahlung")
|
|
|
|
@@index([betreuteId, status])
|
|
@@map("vertrag")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Kontakte und Kontaktprotokoll
|
|
// =============================================================================
|
|
|
|
/// Anrede fuer die Briefanrede eines Kontakts.
|
|
enum KontaktAnrede {
|
|
OHNE
|
|
FRAU
|
|
HERR
|
|
}
|
|
|
|
enum KontaktRolle {
|
|
RECHTSPFLEGER
|
|
RICHTER
|
|
BETREUUNGSBEHOERDE
|
|
SACHBEARBEITER_BEZIRK
|
|
SACHBEARBEITER_SOZIALAMT
|
|
HEIMLEITUNG
|
|
PFLEGEDIENSTLEITUNG
|
|
SOZIALDIENST_HEIM
|
|
HAUSARZT
|
|
FACHARZT
|
|
KRANKENKASSE
|
|
PFLEGEKASSE
|
|
RENTENVERSICHERUNG
|
|
NOTAR
|
|
MAKLER
|
|
VERSICHERUNGSMAKLER
|
|
VERSICHERUNG
|
|
STEUERBERATER
|
|
RECHTSANWALT
|
|
BANK
|
|
HANDWERKER
|
|
ANGEHOERIGER
|
|
SONSTIGES
|
|
}
|
|
|
|
model Kontakt {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
name String
|
|
organisation String?
|
|
rolle KontaktRolle @default(SONSTIGES)
|
|
/// Fuer die Briefanrede. Ohne Angabe wird mit vollem Namen angeredet -
|
|
/// nie geraten, das faellt dem Empfaenger sofort auf.
|
|
anrede KontaktAnrede @default(OHNE)
|
|
|
|
telefon String?
|
|
mobil String?
|
|
fax String?
|
|
email String?
|
|
strasse String?
|
|
plz String?
|
|
ort String?
|
|
/// Aktenzeichen, unter dem dieser Kontakt den Vorgang fuehrt.
|
|
aktenzeichen String?
|
|
|
|
notizenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
protokolle Kontaktprotokoll[]
|
|
vorgaenge Vorgang[] @relation("VorgangKontakte")
|
|
betreuteVorgaenge Vorgang[] @relation("VorgangAnsprechpartner")
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
|
|
@@index([betreuteId, rolle])
|
|
@@map("kontakt")
|
|
}
|
|
|
|
enum KontaktArt {
|
|
BESUCH
|
|
TELEFONAT
|
|
EMAIL
|
|
BRIEF
|
|
VIDEOCALL
|
|
FAX
|
|
VORSPRACHE
|
|
}
|
|
|
|
/// Grundlage des Jahresberichts nach § 1863 BGB: persoenliche Kontakte,
|
|
/// Wuensche der betreuten Person, daraus folgende Aufgaben.
|
|
model Kontaktprotokoll {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
kontaktId String?
|
|
kontakt Kontakt? @relation(fields: [kontaktId], references: [id], onDelete: SetNull)
|
|
vorgangId String?
|
|
vorgang Vorgang? @relation(fields: [vorgangId], references: [id], onDelete: SetNull)
|
|
|
|
datum DateTime
|
|
art KontaktArt
|
|
dauerMinuten Int?
|
|
gespraechspartner String?
|
|
/// true = persoenlicher Kontakt zur betreuten Person selbst.
|
|
mitBetreuter Boolean @default(false)
|
|
|
|
inhaltVerschluesselt String?
|
|
/// Geaeusserte Wuensche - fuer § 1821 BGB und den Jahresbericht.
|
|
wuenscheVerschluesselt String?
|
|
folgeaufgabenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
|
|
@@index([betreuteId, datum])
|
|
@@map("kontaktprotokoll")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Gesundheit und Pflege
|
|
// =============================================================================
|
|
|
|
enum GesundheitsArt {
|
|
ARZTBESUCH
|
|
KRANKENHAUS
|
|
EXAZERBATION
|
|
STURZ
|
|
MEDIKATIONSAENDERUNG
|
|
SAUERSTOFFVERSORGUNG
|
|
INHALATION
|
|
HILFSMITTEL
|
|
IMPFUNG
|
|
REHA
|
|
PFLEGEBEGUTACHTUNG
|
|
HEILMITTEL
|
|
SONSTIGES
|
|
}
|
|
|
|
model Gesundheitsereignis {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
datum DateTime @db.Date
|
|
bisDatum DateTime? @db.Date // z.B. Ende eines Krankenhausaufenthalts
|
|
art GesundheitsArt
|
|
|
|
titel String
|
|
beschreibungVerschluesselt String?
|
|
behandler String?
|
|
einrichtung String?
|
|
|
|
/// Abwesenheitstage mindern den Heimeigenanteil - deshalb hier zaehlbar.
|
|
abwesenheitstage Int?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
|
|
@@index([betreuteId, datum])
|
|
@@map("gesundheitsereignis")
|
|
}
|
|
|
|
model Heimvertrag {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String @unique
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
einrichtung String
|
|
traeger String?
|
|
strasse String?
|
|
plz String?
|
|
ort String?
|
|
|
|
vertragAm DateTime? @db.Date
|
|
einzugAm DateTime? @db.Date
|
|
auszugAm DateTime? @db.Date
|
|
kuendigungsfristTage Int? @default(14)
|
|
|
|
// Aktuelle Saetze - historische Werte stehen in Heimkostenmonat.
|
|
pflegeEigenanteilCent BigInt? // einrichtungseinheitlicher Eigenanteil
|
|
unterkunftVerpflegungCent BigInt?
|
|
investitionskostenCent BigInt?
|
|
ausbildungsumlageCent BigInt?
|
|
|
|
notizenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
|
|
@@map("heimvertrag")
|
|
}
|
|
|
|
/// Monatliche Gegenueberstellung Heimrechnung gegen Deckungsquellen.
|
|
model Heimkostenmonat {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
jahr Int
|
|
monat Int // 1..12
|
|
|
|
// Kostenseite
|
|
pflegeEigenanteilCent BigInt @default(0)
|
|
unterkunftVerpflegungCent BigInt @default(0)
|
|
investitionskostenCent BigInt @default(0)
|
|
ausbildungsumlageCent BigInt @default(0)
|
|
zusatzleistungenCent BigInt @default(0)
|
|
|
|
// Deckungsseite
|
|
pflegekasseCent BigInt @default(0) // § 43 SGB XI
|
|
/// Leistungszuschlag nach § 43c SGB XI, abhaengig von der Verweildauer.
|
|
leistungszuschlagProzent Int @default(0)
|
|
leistungszuschlagCent BigInt @default(0)
|
|
renteCent BigInt @default(0)
|
|
sonstigeEinkuenfteCent BigInt @default(0)
|
|
sozialhilfeCent BigInt @default(0)
|
|
eigenmittelCent BigInt @default(0)
|
|
|
|
// Der betreuten Person verbleibende Betraege
|
|
barbetragCent BigInt @default(0)
|
|
bekleidungspauschaleCent BigInt @default(0)
|
|
|
|
abwesenheitstage Int @default(0)
|
|
|
|
rechnungDokumentId String?
|
|
bemerkung String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
|
|
@@unique([betreuteId, jahr, monat])
|
|
@@index([betreuteId, jahr])
|
|
@@map("heimkostenmonat")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Sozialhilfe (Bezirk Oberfranken)
|
|
// =============================================================================
|
|
|
|
enum NachforderungStatus {
|
|
OFFEN
|
|
IN_BESCHAFFUNG
|
|
UEBERSANDT
|
|
ANERKANNT
|
|
NICHT_VORHANDEN
|
|
}
|
|
|
|
enum BescheidArt {
|
|
BEWILLIGUNG
|
|
ABLEHNUNG
|
|
AENDERUNG
|
|
AUFHEBUNG
|
|
ERSTATTUNG
|
|
VORSCHUSS
|
|
WIDERSPRUCHSBESCHEID
|
|
}
|
|
|
|
enum BescheidPruefStatus {
|
|
UNGEPRUEFT
|
|
GEPRUEFT_OK
|
|
WIDERSPRUCH_EINGELEGT
|
|
WIDERSPRUCH_ERLEDIGT
|
|
FRIST_VERSTRICHEN
|
|
}
|
|
|
|
model SozialhilfeAntrag {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
traeger String @default("Bezirk Oberfranken")
|
|
leistungsart String @default("Hilfe zur Pflege nach dem 7. Kapitel SGB XII")
|
|
aktenzeichen String?
|
|
|
|
sachbearbeiter String?
|
|
telefon String?
|
|
email String?
|
|
|
|
antragAm DateTime? @db.Date
|
|
eingangBeimTraegerAm DateTime? @db.Date
|
|
bewilligtVon DateTime? @db.Date
|
|
bewilligtBis DateTime? @db.Date
|
|
|
|
notizenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
nachforderungen Nachforderung[]
|
|
bescheide Bescheid[]
|
|
|
|
@@index([betreuteId])
|
|
@@map("sozialhilfe_antrag")
|
|
}
|
|
|
|
/// Jede vom Traeger angeforderte Unterlage als eigene Zeile.
|
|
model Nachforderung {
|
|
id String @id @default(cuid())
|
|
|
|
antragId String
|
|
antrag SozialhilfeAntrag @relation(fields: [antragId], references: [id], onDelete: Cascade)
|
|
|
|
bezeichnung String
|
|
erlaeuterung String?
|
|
status NachforderungStatus @default(OFFEN)
|
|
|
|
angefordertAm DateTime? @db.Date
|
|
fristBis DateTime? @db.Date
|
|
uebersandtAm DateTime? @db.Date
|
|
/// Wenn die Unterlage nicht existiert: warum nicht? Geht als Erklaerung
|
|
/// mit ins Unterlagenpaket.
|
|
begruendungNichtVorhanden String?
|
|
|
|
reihenfolge Int @default(0)
|
|
/// Vorbefuellte Standardunterlage aus der Checkliste.
|
|
ausCheckliste Boolean @default(false)
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
|
|
@@index([antragId, status])
|
|
@@map("nachforderung")
|
|
}
|
|
|
|
model Bescheid {
|
|
id String @id @default(cuid())
|
|
|
|
antragId String
|
|
antrag SozialhilfeAntrag @relation(fields: [antragId], references: [id], onDelete: Cascade)
|
|
|
|
art BescheidArt
|
|
bescheidVom DateTime @db.Date
|
|
/// Massgeblich fuer den Fristbeginn - nicht das Datum auf dem Bescheid.
|
|
zugegangenAm DateTime? @db.Date
|
|
zeitraumVon DateTime? @db.Date
|
|
zeitraumBis DateTime? @db.Date
|
|
betragCent BigInt?
|
|
|
|
/// In der Regel ein Monat ab Bekanntgabe (§ 84 SGG). Bei fehlender oder
|
|
/// falscher Rechtsbehelfsbelehrung ein Jahr - deshalb speicherbar.
|
|
rechtsbehelfsfristMonate Int @default(1)
|
|
widerspruchMoeglichBis DateTime? @db.Date
|
|
pruefStatus BescheidPruefStatus @default(UNGEPRUEFT)
|
|
widerspruchEingelegtAm DateTime? @db.Date
|
|
|
|
notizenVerschluesselt String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
verknuepfungen DokumentVerknuepfung[]
|
|
|
|
@@index([antragId, bescheidVom])
|
|
@@map("bescheid")
|
|
}
|
|
|
|
enum LeistungStatus {
|
|
NICHT_GEPRUEFT
|
|
PRUEFUNG_LAEUFT
|
|
BEANTRAGT
|
|
BEWILLIGT
|
|
ABGELEHNT
|
|
LAEUFT
|
|
NICHT_ZUTREFFEND
|
|
}
|
|
|
|
/// Katalog weiterer Leistungen: Landespflegegeld, Zuzahlungsbefreiung,
|
|
/// Rundfunkbeitragsbefreiung, Schwerbehindertenausweis, Grundsicherung.
|
|
model Leistung {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
bezeichnung String
|
|
traeger String?
|
|
rechtsgrundlage String?
|
|
status LeistungStatus @default(NICHT_GEPRUEFT)
|
|
|
|
beantragtAm DateTime? @db.Date
|
|
bewilligtAm DateTime? @db.Date
|
|
gueltigBis DateTime? @db.Date
|
|
betragCent BigInt?
|
|
rhythmus ZahlRhythmus?
|
|
|
|
/// Muss jaehrlich neu beantragt werden (Landespflegegeld, Zuzahlung).
|
|
jaehrlichNeuBeantragen Boolean @default(false)
|
|
|
|
/// Stabiler Schluessel aus dem Katalog, verhindert Dubletten beim Seed.
|
|
katalogSchluessel String?
|
|
|
|
notizen String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
@@unique([betreuteId, katalogSchluessel])
|
|
@@index([betreuteId, status])
|
|
@@map("leistung")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Berichte
|
|
// =============================================================================
|
|
|
|
enum BerichtArt {
|
|
VERMOEGENSVERZEICHNIS // § 1835 BGB
|
|
JAHRESBERICHT // § 1863 BGB
|
|
VERMOEGENSUEBERSICHT // § 1859 BGB (befreiter Betreuer)
|
|
RECHNUNGSLEGUNG // § 1865 BGB
|
|
BELEGMAPPE
|
|
UNTERLAGENPAKET_BEZIRK
|
|
VOLLEXPORT
|
|
}
|
|
|
|
/// Erzeugte Berichte werden protokolliert - was habe ich wann in welcher
|
|
/// Fassung abgegeben? Der Entwurfstext bleibt fuer die Ueberarbeitung stehen.
|
|
model Bericht {
|
|
id String @id @default(cuid())
|
|
|
|
betreuteId String
|
|
betreute Betreute @relation(fields: [betreuteId], references: [id], onDelete: Cascade)
|
|
|
|
art BerichtArt
|
|
titel String
|
|
zeitraumVon DateTime? @db.Date
|
|
zeitraumBis DateTime? @db.Date
|
|
stichtag DateTime? @db.Date
|
|
|
|
/// Vom Nutzer ueberarbeiteter Freitext (Jahresbericht) - verschluesselt.
|
|
entwurfVerschluesselt String?
|
|
|
|
/// Abgelegte PDF-Fassung im Objektspeicher.
|
|
objektSchluessel String?
|
|
dateiname String?
|
|
pruefsumme String?
|
|
|
|
erstelltVonId String?
|
|
erstelltVon User? @relation(fields: [erstelltVonId], references: [id], onDelete: SetNull)
|
|
|
|
abgegebenAm DateTime? @db.Date
|
|
abgegebenAn String?
|
|
|
|
erstelltAm DateTime @default(now())
|
|
aktualisiertAm DateTime @updatedAt
|
|
geloeschtAm DateTime?
|
|
|
|
@@index([betreuteId, art])
|
|
@@map("bericht")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Audit-Log
|
|
// =============================================================================
|
|
|
|
enum AuditAktion {
|
|
ERSTELLT
|
|
GEAENDERT
|
|
GELOESCHT
|
|
WIEDERHERGESTELLT
|
|
ENDGUELTIG_GELOESCHT
|
|
ANGESEHEN
|
|
EXPORTIERT
|
|
DOKUMENT_HERUNTERGELADEN
|
|
ANMELDUNG
|
|
ANMELDUNG_FEHLGESCHLAGEN
|
|
ABMELDUNG
|
|
ZWEITER_FAKTOR_EINGERICHTET
|
|
PASSKEY_REGISTRIERT
|
|
PASSKEY_ENTFERNT
|
|
PASSWORT_GEAENDERT
|
|
ROLLE_GEAENDERT
|
|
FREIGABE_GEAENDERT
|
|
}
|
|
|
|
/// Unveraenderlich und nur anhaengend. Ein Datenbank-Trigger
|
|
/// (prisma/sql/audit-schutz.sql) verhindert UPDATE und DELETE.
|
|
/// Zugleich mein Nachweis gegenueber Gericht und moeglichen Miterben.
|
|
model AuditLog {
|
|
id String @id @default(cuid())
|
|
zeitpunkt DateTime @default(now())
|
|
|
|
userId String?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
/// Momentaufnahme - bleibt lesbar, auch wenn der Benutzer geloescht wird.
|
|
userEmail String?
|
|
userName String?
|
|
|
|
betreuteId String?
|
|
betreute Betreute? @relation(fields: [betreuteId], references: [id], onDelete: SetNull)
|
|
|
|
aktion AuditAktion
|
|
entitaet String // Modellname, z.B. "Betreute"
|
|
entitaetId String?
|
|
bezeichnung String? // lesbarer Titel des betroffenen Datensatzes
|
|
|
|
/// Geaenderte Felder als JSON {feld: {alt, neu}} - verschluesselt, weil
|
|
/// dort Gesundheits- und Finanzdaten stehen koennen.
|
|
aenderungVerschluesselt String?
|
|
|
|
ipHash String?
|
|
userAgent String?
|
|
|
|
@@index([betreuteId, zeitpunkt])
|
|
@@index([userId, zeitpunkt])
|
|
@@index([entitaet, entitaetId])
|
|
@@map("audit_log")
|
|
}
|