Files
betreuungs-cockpit/docs/schema-wuensche/substanz.md
T
Florian Wilde 22b3a9b9e9 feat: Stammdaten, Ersteinrichtung, Zweitfaktor und Protokollansicht
Damit ist Etappe 1 fachlich vollstaendig.

Ersteinrichtung in drei Schritten, bewusst ausserhalb des
Anwendungsrahmens: der setzt Benutzer, zweiten Faktor UND eine betreute
Person voraus - beim allerersten Start gibt es nichts davon. Die Aktion
fuer den ersten Zugang sperrt sich selbst, sobald ein Benutzer existiert.

Zweiter Faktor: Das Geheimnis wird zuerst nur abgelegt, ohne
totpAktiviertAm. Erst ein echter Code aus der App schaltet ihn scharf.
Wer sich beim Abtippen vertut oder die falsche App nimmt, wuerde sich
sonst dauerhaft aussperren. Erst danach gibt es die
Wiederherstellungscodes - einmal angezeigt, danach nur noch als Hash.

Betreuungsjahr (src/lib/stammdaten/betreuungsjahr.ts): laeuft ab dem Tag
der Bestellung, nicht ab dem Kalenderjahr. Schrittweise vorwaerts
gerechnet statt ueber eine Differenz, damit Schaltjahre und der
29. Februar von selbst stimmen. Daran haengen Jahresbericht,
Rechnungslegung und die Ausschlussfrist der Aufwandspauschale.

Aendert sich das Bestellungsdatum, werden die Pflichtfristen neu
abgeleitet - ueber einen dynamischen Import, damit die Stammdaten auch
dann speicherbar bleiben, wenn das Fristenmodul fehlt.

Protokollansicht zeigt je Eintrag die geaenderten Felder als alt -> neu.
Die Aenderungen liegen verschluesselt, weil dort Diagnosen und
Kontostaende stehen koennen.

Seed legt bewusst keine echten Personendaten an, nur den ersten Zugang.
Mit SEED_DEMO=true kommt ein vollstaendiger Beispielfall dazu - Erika
Musterfrau, COPD GOLD III, Pflegegrad 3, Heimeinzug Januar 2026,
leerstehendes Haus, zwei Versicherungen, laufender Antrag beim Bezirk
mit acht Nachforderungen. Nur ausserhalb der Produktion.

Docker Compose ohne Reverse Proxy - der ist auf dem Zielserver
vorhanden. Die Datenbank hat keine Portfreigabe und ist nur ueber das
interne Netz erreichbar; die Anwendung horcht auf 127.0.0.1.

Etappe: 1
Phase: Fundament
2026-08-22 02:16:22 +02:00

181 lines
5.7 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Schema-Wünsche aus Etappe 6 „Substanz“
Das Schema wurde nicht angefasst. Hier steht, was beim Bauen von
Immobilie, Verträgen und Pflege gefehlt hat — mit der Umgehung, die
stattdessen gewählt wurde, damit später klar ist, was zu migrieren wäre.
---
## 1. `Buchung.immobilieId`
**Problem.** Das Kostenkonto der Immobilie (Grundsteuer, Gebäude­versicherung,
Energie, Wasser, Instandhaltung, Schornsteinfeger) kann nur über die
Kategorien `GRUNDSTEUER`, `IMMOBILIE_BETRIEBSKOSTEN`,
`IMMOBILIE_INSTANDHALTUNG` und `MIETEINNAHME` zusammengestellt werden. Eine
Zuordnung „diese Buchung gehört zu diesem Objekt“ gibt es nicht.
**Folge.** Solange nur ein Objekt geführt wird, ist die Zuordnung eindeutig.
Bei einem zweiten Grundstück wären die Zahlen falsch. Die Oberfläche sagt
das an der Auswertung ausdrücklich dazu.
**Wunsch.**
```prisma
model Buchung {
immobilieId String?
immobilie Immobilie? @relation(fields: [immobilieId], references: [id], onDelete: SetNull)
}
```
---
## 2. Eigene Buchungskategorie für die Gebäudeversicherung
`BuchungKategorie.VERSICHERUNG` sammelt alle Versicherungen ein, auch
Sterbegeld und Haftpflicht. Für das Kostenkonto eines Hauses ist das zu
grob; die Gebäudeversicherung muss dort erscheinen, die private
Unfallversicherung nicht.
**Umgehung.** Die Gebäudeversicherung wird als
`IMMOBILIE_BETRIEBSKOSTEN` gebucht. Das steht als Hinweis an der
Auswertung.
**Wunsch.** `IMMOBILIE_VERSICHERUNG` in `BuchungKategorie`.
---
## 3. `Inventarstueck` — Inventarliste der Wertgegenstände
**Problem.** Für die Fotodokumentation und die Inventarliste eines
leerstehenden Hauses fehlt ein Modell mit `bezeichnung`, `raum`,
`geschaetzterWertCent` und `verbleib`. `Vermoegensposten` passt fachlich
(Art `WERTGEGENSTAND`, Verknüpfung auf `immobilieId`, Wert über
`Wertstand`, Fotos über `DokumentVerknuepfung`), hat aber keine Felder für
Raum und Verbleib.
**Umgehung.** Raum, Verbleib und Bemerkung stehen als beschriftete Zeilen
im ohnehin verschlüsselten `notizenVerschluesselt`; gelesen und geschrieben
wird das ausschließlich über `src/lib/immobilie/inventar.ts`. Der
geschätzte Wert liegt als `Wertstand` mit der Quelle „eigene Schätzung“.
**Wunsch.**
```prisma
model Inventarstueck {
id String @id @default(cuid())
betreuteId String
immobilieId String?
bezeichnung String
raum String?
geschaetzterWertCent BigInt?
verbleib String? // "im Haus", "Heimzimmer", "verschenkt", "entsorgt", "verkauft"
bemerkungVerschluesselt String?
}
```
---
## 4. `Immobilie.checklisteUebernommenAm`
Ob die Leerstands-Checkliste schon einmal übernommen wurde, lässt sich nur
daran ablesen, ob `ImmobilienCheckpunkt`-Zeilen existieren. Das genügt,
weil die Übernahme über den stabilen Titel abgleicht und keine Dubletten
erzeugt — sauberer wäre ein `systemSchluessel` am Checkpunkt.
**Wunsch.**
```prisma
model ImmobilienCheckpunkt {
systemSchluessel String?
@@unique([immobilieId, systemSchluessel])
}
```
Bis dahin dient der `titel` als Schlüssel; er darf deshalb beim
Bearbeiten eines übernommenen Punktes nicht geändert werden, ohne dass die
nächste Übernahme ihn erneut anlegt.
---
## 5. Pflegegrad-Historie als eigenes Modell
`Betreute` trägt den aktuellen Pflegegrad, seit wann und das
Bescheiddatum. Frühere Grade gehen verloren, sobald der Grad
hochgestuft wird — für den Jahresbericht und für eine Höherstufung ist
aber genau die Kette interessant.
**Umgehung.** Jede Begutachtung wird als `Gesundheitsereignis` mit der Art
`PFLEGEBEGUTACHTUNG` erfasst; der Verlauf zeigt daraus die Historie. Der
Grad selbst steht im Titel und in der verschlüsselten Beschreibung, ist
also nicht auswertbar.
**Wunsch.**
```prisma
model Pflegegradstand {
id String @id @default(cuid())
betreuteId String
grad Int
gueltigAb DateTime @db.Date
bescheidVom DateTime? @db.Date
antragAm DateTime? @db.Date
ergebnis String? // "hochgestuft", "abgelehnt", "unverändert"
}
```
---
## 6. Strukturierte Felder am `Gesundheitsereignis`
Die Schnellerfassung für COPD braucht Zahlen, die man später auswerten
will: Sauerstofffluss in l/min, Stunden pro Tag, Schweregrad einer
Exazerbation, ob stationär behandelt wurde.
**Umgehung.** Diese Angaben werden als beschriftete Zeilen in die
verschlüsselte Beschreibung geschrieben
(`src/lib/pflege/ereignis-details.ts`). Sie sind damit lesbar, aber nicht
summierbar. Auswertbar bleiben nur Art, Datum, `bisDatum` und
`abwesenheitstage` — das reicht für Exazerbationen je Jahr und
Krankenhaustage je Jahr.
**Wunsch.** Ein `details Json?`-Feld am `Gesundheitsereignis` wäre falsch
(Gesundheitsdaten gehören verschlüsselt). Besser wären wenige typisierte,
unverschlüsselte Kennzahlen ohne Diagnosebezug:
```prisma
model Gesundheitsereignis {
stationaer Boolean @default(false)
schweregrad Int? // 1 leicht, 2 mittel, 3 schwer
}
```
---
## 7. `Vertrag.vertragsnummerVerschluesselt` war nicht registriert
Das Feld steht im Schema, fehlte aber in `VERSCHLUESSELTE_FELDER` in
`src/lib/crypto/feld.ts` — `sealen('Vertrag', { vertragsnummer })` hätte
geworfen. Der Eintrag wurde ergänzt; das Schema blieb unverändert.
---
## 8. `Vertrag.laufzeitJahre`
Der Kündigungsrechner braucht die Vertragsdauer, um die Verlängerung zu
bestimmen. Das Schema kennt nur `beginnAm`, `endeAm` und
`kuendigungsfristMonate`.
**Umgehung.** Fehlt `endeAm`, rechnet
`src/lib/vertraege/kuendigung.ts` mit einer Verlängerung um ein Jahr —
der Regelfall bei Versicherungen — und schreibt diese Annahme als Hinweis
in das Ergebnis.
**Wunsch.**
```prisma
model Vertrag {
laufzeitJahre Int?
verlaengertSichAutomatisch Boolean @default(true)
}
```