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
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
# Schnittstellen zwischen den Modulen
|
||||
|
||||
Verträge, auf die sich mehrere Etappen stützen. Wer eine dieser Schnittstellen
|
||||
implementiert, hält sich exakt an die hier beschriebene Signatur; wer sie
|
||||
benutzt, darf sich darauf verlassen.
|
||||
|
||||
---
|
||||
|
||||
## 1. Dateiablage — `src/lib/speicher/index.ts`
|
||||
|
||||
Zwei Treiber hinter einer Schnittstelle: `datei` (lokales Verzeichnis, für
|
||||
Entwicklung und kleine Installationen) und `s3` (S3-kompatibler Speicher in
|
||||
der EU). Gesteuert über `SPEICHER_TREIBER` bzw. das Vorhandensein von
|
||||
`S3_ACCESS_KEY_ID`.
|
||||
|
||||
```ts
|
||||
export type Ablageziel = {
|
||||
/** Schlüssel im Speicher, z.B. "betreute/<id>/dokumente/<uuid>.pdf" */
|
||||
schluessel: string
|
||||
groesseBytes: number
|
||||
/** SHA-256 hex — Nachweis der Unverändertheit gegenüber Gericht. */
|
||||
pruefsumme: string
|
||||
}
|
||||
|
||||
/** Legt eine Datei ab. Der Schlüssel wird erzeugt, nie vom Nutzer bestimmt. */
|
||||
export function ablegen(
|
||||
betreuteId: string,
|
||||
bereich: 'dokumente' | 'berichte' | 'exporte' | 'vorschau',
|
||||
dateiname: string,
|
||||
inhalt: Buffer,
|
||||
mimeTyp: string,
|
||||
): Promise<Ablageziel>
|
||||
|
||||
/** Holt eine Datei zurück in den Speicher des Servers. */
|
||||
export function lesen(schluessel: string): Promise<Buffer>
|
||||
|
||||
/**
|
||||
* Kurzlebige, signierte URL. Gültigkeit in Sekunden, Standard 300.
|
||||
* Beim Dateitreiber zeigt sie auf eine eigene, ebenfalls signierte Route.
|
||||
*/
|
||||
export function signierteUrl(schluessel: string, sekunden?: number): Promise<string>
|
||||
|
||||
export function entfernen(schluessel: string): Promise<void>
|
||||
export function existiert(schluessel: string): Promise<boolean>
|
||||
```
|
||||
|
||||
**Regeln**
|
||||
|
||||
- Der Schlüssel enthält immer die `betreuteId` als zweites Segment. Jede
|
||||
Leseoperation prüft vorher, dass der angemeldete Benutzer Zugriff auf
|
||||
genau diese `betreuteId` hat.
|
||||
- Uploads werden nie als HTML ausgeliefert. Ausgehende Antworten tragen
|
||||
`Content-Disposition: attachment` und `X-Content-Type-Options: nosniff`.
|
||||
- Erlaubte Typen: `application/pdf`, `image/jpeg`, `image/png`,
|
||||
`image/heic`, `image/webp`, `image/tiff`. Geprüft wird an den **Magic
|
||||
Bytes**, nicht am gemeldeten MIME-Typ und nicht an der Endung.
|
||||
- Größenlimit aus `env.uploadMaxBytes` (Standard 25 MB).
|
||||
|
||||
---
|
||||
|
||||
## 2. PDF-Erzeugung — `src/lib/pdf/`
|
||||
|
||||
Grundlage ist `@react-pdf/renderer` (kein Chromium im Container).
|
||||
|
||||
### `src/lib/pdf/rahmen.tsx`
|
||||
|
||||
```tsx
|
||||
export type Berichtskopf = {
|
||||
titel: string
|
||||
betreute: { name: string; geburtsdatum: Date | null }
|
||||
/** z.B. "Amtsgericht Bayreuth · Az. XVII 123/25" */
|
||||
behoerde?: string
|
||||
aktenzeichen?: string
|
||||
zeitraumVon?: Date | null
|
||||
zeitraumBis?: Date | null
|
||||
stichtag?: Date | null
|
||||
rechtsgrundlage?: string
|
||||
erstelltAm: Date
|
||||
erstelltVon: string
|
||||
}
|
||||
|
||||
/** Seitenrahmen mit Kopf, Fußzeile (Seite x von y, Erstelldatum) und
|
||||
* optionalem Unterschriftenfeld. Jeder Bericht benutzt ihn. */
|
||||
export function Berichtsseite(props: {
|
||||
kopf: Berichtskopf
|
||||
mitUnterschrift?: boolean
|
||||
children: React.ReactNode
|
||||
}): JSX.Element
|
||||
```
|
||||
|
||||
### `src/lib/pdf/erzeugen.ts`
|
||||
|
||||
```ts
|
||||
/** Rendert ein react-pdf-Dokument zu einem Buffer. */
|
||||
export function pdfErzeugen(dokument: React.ReactElement): Promise<Buffer>
|
||||
|
||||
/**
|
||||
* Fügt mehrere PDFs zu einem zusammen (Belegmappe, Unterlagenpaket).
|
||||
* Bilder werden vorher in einseitige PDFs gewandelt.
|
||||
* Nutzt pdf-lib.
|
||||
*/
|
||||
export function pdfsZusammenfuehren(teile: Buffer[]): Promise<Buffer>
|
||||
|
||||
/** Seitenzahlen und Fußzeile über ein zusammengeführtes PDF legen. */
|
||||
export function paginieren(pdf: Buffer, fusszeile: string): Promise<Buffer>
|
||||
```
|
||||
|
||||
**Regeln**
|
||||
|
||||
- Schrift: die in `@react-pdf/renderer` eingebauten Standardschriften
|
||||
(Helvetica). Kein Nachladen von Google Fonts.
|
||||
- Jeder Bericht trägt Kopf (Betreute, Aktenzeichen, Gericht/Behörde,
|
||||
Zeitraum), Fußzeile mit Seitenzahl und Erstellungsdatum sowie ein
|
||||
Unterschriftenfeld.
|
||||
- Beträge über `formatCent()`, Daten über `formatDatum()`.
|
||||
|
||||
---
|
||||
|
||||
## 3. OCR — `src/lib/ocr/index.ts`
|
||||
|
||||
```ts
|
||||
export type OcrErgebnis = {
|
||||
text: string
|
||||
status: 'fertig' | 'fehlgeschlagen' | 'nicht_verfuegbar'
|
||||
seitenzahl?: number
|
||||
meldung?: string
|
||||
}
|
||||
|
||||
/** Ruft Tesseract (deutsch) auf. Nie werfen — Status zurückgeben. */
|
||||
export function ocrLesen(inhalt: Buffer, mimeTyp: string): Promise<OcrErgebnis>
|
||||
|
||||
/** Ist Tesseract auf diesem System vorhanden? */
|
||||
export function ocrVerfuegbar(): Promise<boolean>
|
||||
```
|
||||
|
||||
**Regeln**
|
||||
|
||||
- Aufruf über das System-Binary `tesseract` (`env.tesseractBefehl`),
|
||||
Sprache `deu`. Im Docker-Image ist es installiert, auf einem Windows-
|
||||
Entwicklungsrechner in der Regel nicht — dann liefert die Funktion
|
||||
`nicht_verfuegbar`, und der Upload funktioniert trotzdem.
|
||||
- PDFs werden vorher mit `pdftoppm` in Bilder gewandelt; fehlt auch das,
|
||||
wird der eingebettete Text des PDFs mit `pdf-lib` gelesen.
|
||||
- Der OCR-Lauf blockiert den Upload nicht: Dokument wird mit
|
||||
`ocrStatus = "ausstehend"` gespeichert, die Erkennung läuft danach.
|
||||
|
||||
---
|
||||
|
||||
## 4. Fristenableitung — `src/lib/fristen/pflichtfristen.ts`
|
||||
|
||||
```ts
|
||||
export type Pflichtfrist = {
|
||||
/** Stabiler Schlüssel, verhindert Dubletten: "anfangsbericht",
|
||||
* "jahresbericht-2027", "aufwandspauschale-2026", … */
|
||||
systemSchluessel: string
|
||||
titel: string
|
||||
beschreibung: string
|
||||
datum: Date
|
||||
rechtsgrundlage: string
|
||||
typ: 'GESETZLICH' | 'BEHOERDLICH' | 'SELBST_GESETZT' | 'WIEDERVORLAGE'
|
||||
ausschlussfrist: boolean
|
||||
vorwarnungTage: number[]
|
||||
wiederholungRrule?: string
|
||||
}
|
||||
|
||||
/** Leitet alle Pflichtfristen aus den Stammdaten ab. Rein, ohne Datenbank. */
|
||||
export function pflichtfristenAbleiten(stammdaten: {
|
||||
bestellungAm: Date | null
|
||||
befreiterBetreuer: boolean
|
||||
ehrenamtlich: boolean
|
||||
pflegegrad: number | null
|
||||
jahreVoraus?: number
|
||||
}): Pflichtfrist[]
|
||||
|
||||
/** Schreibt sie in die Datenbank, ohne Dubletten (upsert auf
|
||||
* betreuteId + systemSchluessel). */
|
||||
export function pflichtfristenSchreiben(betreuteId: string): Promise<number>
|
||||
```
|
||||
|
||||
Abzuleiten sind mindestens:
|
||||
|
||||
| Schlüssel | Frist | Grundlage |
|
||||
|---|---|---|
|
||||
| `vermoegensverzeichnis` | 3 Monate nach Bestellung | § 1835 BGB |
|
||||
| `anfangsbericht` | 3 Monate nach Bestellung | § 1863 Abs. 1 BGB |
|
||||
| `jahresbericht-<jahr>` | jährlich zum Betreuungsjahr | § 1863 Abs. 3 BGB |
|
||||
| `vermoegensuebersicht-<jahr>` | jährlich, mit dem Jahresbericht | § 1859, § 1865 BGB |
|
||||
| `aufwandspauschale-<jahr>` | 6 Monate nach Ende des Betreuungsjahres, **Ausschlussfrist** | § 1878 BGB |
|
||||
| `landespflegegeld-<jahr>` | 31.12. | Bayerisches Landespflegegeldgesetz |
|
||||
| `zuzahlungsbefreiung-<jahr>` | jährlich | § 62 SGB V |
|
||||
| `rueckkaufswerte-<jahr>` | jährlich | selbst gesetzt |
|
||||
| `pflegegrad-pruefung` | konfigurierbar | § 18 SGB XI |
|
||||
|
||||
**Betreuungsjahr**: läuft ab dem Tag der Bestellung, nicht ab dem
|
||||
Kalenderjahr. Die Aufwandspauschale verfällt sechs Monate nach dessen Ende
|
||||
— diese Frist wird als `ausschlussfrist: true` markiert und im Dashboard
|
||||
rot mit ausdrücklicher Verfallwarnung gezeigt.
|
||||
|
||||
---
|
||||
|
||||
## 5. Hinweis-Wissensbasis — `daten/hinweise.json`
|
||||
|
||||
Pflegbare JSON-Datei, kein Hardcoding.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"version": 1,
|
||||
"karten": [
|
||||
{
|
||||
"schluessel": "wohnraum-aufgeben",
|
||||
"titel": "Wohnung kündigen? Erst das Gericht fragen.",
|
||||
"text": "Die Aufgabe von Wohnraum der betreuten Person ist dem Gericht mitzuteilen und bedarf der Genehmigung.",
|
||||
"rechtsgrundlage": "§ 1833 BGB",
|
||||
"link": "https://www.gesetze-im-internet.de/bgb/__1833.html",
|
||||
"kontext": ["IMMOBILIE", "VORGANG:IMMOBILIE", "VORLAGE:wohnungsaufloesung"],
|
||||
"gewicht": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`kontext` ist eine Liste von Auslösern: ein `Modul`, `VORGANG:<Kategorie>`,
|
||||
`VORLAGE:<schluessel>` oder `FRIST:<systemSchluessel>`. Die Oberfläche
|
||||
zeigt passende Karten, sortiert nach `gewicht` absteigend, jede mit
|
||||
`<Rechtshinweis>`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Vorgangs-Vorlagen — `src/lib/vorgaenge/vorlagen.ts`
|
||||
|
||||
```ts
|
||||
export type Vorgangsvorlage = {
|
||||
schluessel: string
|
||||
titel: string
|
||||
kategorie: VorgangKategorie
|
||||
beschreibung: string
|
||||
genehmigungspflichtig: boolean
|
||||
rechtsgrundlage?: string
|
||||
/** Vorbefüllte Aufgabenliste. */
|
||||
aufgaben: { titel: string; beschreibung?: string; tageBisFaellig?: number }[]
|
||||
/** Fristen, die mit angelegt werden. */
|
||||
fristen?: { titel: string; tageBisFaellig: number; rechtsgrundlage?: string }[]
|
||||
}
|
||||
|
||||
export const VORLAGEN: Vorgangsvorlage[]
|
||||
```
|
||||
|
||||
Mindestens: `heimeinzug`, `antrag-hilfe-zur-pflege`, `wohnungsaufloesung`,
|
||||
`hausverkauf`, `darlehen-91-sgb-xii`, `versicherungsverwertung`,
|
||||
`pflegegrad-hoeherstufung`, `steuererklaerung`, `widerspruch-bescheid`.
|
||||
|
||||
---
|
||||
|
||||
## 7. E-Mail — `src/lib/mail.ts`
|
||||
|
||||
```ts
|
||||
export function mailSenden(nachricht: {
|
||||
an: string
|
||||
betreff: string
|
||||
text: string
|
||||
}): Promise<{ gesendet: boolean; meldung?: string }>
|
||||
```
|
||||
|
||||
Ist kein SMTP konfiguriert, wird nichts gesendet und `gesendet: false`
|
||||
zurückgegeben — ohne Fehler. Erinnerungen sind eine Zugabe, kein
|
||||
Kernstück; die Fristen stehen auch so im Dashboard.
|
||||
@@ -0,0 +1,180 @@
|
||||
# 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äudeversicherung,
|
||||
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)
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user