feat: Immobilie, Versicherungen und Heim mit COPD-Verlauf

Etappe 6. Das leerstehende Haus, die Vertraege und der
Gesundheitsverlauf - die drei Bereiche, in denen fuer diesen Fall das
meiste Geld und das meiste Risiko liegt.

Leerstands-Checkliste mit zehn Punkten, jeder mit Begruendung. Der
wichtigste: den Gebaeudeversicherer ueber den Leerstand informieren -
sonst droht Leistungsfreiheit, und zwar genau dann, wenn es brennt oder
eine Leitung platzt. Dazu Verkehrssicherungspflicht, Frostschutz zum
Oktober, Kontrollgaenge, Zaehlerstaende, Schluesselverzeichnis.

Der Kuendigungsrechner nennt den spaetesten ABSENDETAG, nicht den
Fristablauf: eine Kuendigung wirkt mit dem Zugang, nicht mit dem
Abschicken. Drei Tage Postlaufpuffer sind einkalkuliert.

Der Kuendigungs-Assistent beantwortet die Genehmigungsfrage nach
§ 1850 BGB nicht - er stellt sie und verlangt den Gang zum
Rechtspfleger. Der Versandnachweis ist Pflichtfeld: ohne ihn kommt die
Kuendigung nicht durch die Pruefung.

Leistungszuschlag nach § 43c SGB XI mit Stand 01.01.2024
(15/30/50/75 Prozent nach Verweildauer, erst ab Pflegegrad 2). Die
Oberflaeche zeigt, ab wann die naechste Stufe greift - das ist bares
Geld und wird regelmaessig uebersehen.

COPD-Schnellerfassung fuer Exazerbation, Sauerstoff, Sturz,
Krankenhaus, Hilfsmittel und Impfungen, mit STIKO-Plan fuer COPD.
Beschreibungen verschluesselt. Die Jahresauswertung ueber Exazerbationen
und Krankenhaustage wandert in den Jahresbericht und begruendet eine
Hoeherstufung.

74 Tests.

Etappe: 6
Phase: Substanz
This commit is contained in:
Florian Wilde
2026-08-22 06:22:24 +02:00
parent d35e24f830
commit b69a12da8f
28 changed files with 4309 additions and 917 deletions
+171 -172
View File
@@ -1,172 +1,171 @@
import { z } from 'zod'
import {
optionalerBetrag,
optionalerText,
optionaleZahl,
optionalesDatum,
pflichtBetrag,
pflichtDatum,
pflichtText,
} from '@/lib/validierung/substanz'
import { WERTQUELLEN_VERTRAG } from '@/lib/vertraege/konstanten'
/**
* Eingabepruefung fuer Versicherungen und Vertraege.
*/
export const VERTRAG_ARTEN = [
'LEBENSVERSICHERUNG',
'STERBEGELDVERSICHERUNG',
'BESTATTUNGSVORSORGE',
'RENTENVERSICHERUNG_PRIVAT',
'UNFALLVERSICHERUNG',
'HAFTPFLICHT',
'HAUSRAT',
'WOHNGEBAEUDE',
'KRANKENZUSATZ',
'PFLEGEZUSATZ',
'RECHTSSCHUTZ',
'STERBEKASSE',
'STROM',
'GAS',
'WASSER',
'TELEFON_INTERNET',
'RUNDFUNKBEITRAG',
'MITGLIEDSCHAFT',
'ABONNEMENT',
'SONSTIGES',
] as const
export const VERTRAG_STATUS = [
'AKTIV',
'BEITRAGSFREI',
'RUHEND',
'GEKUENDIGT',
'ABGETRETEN',
'VERWERTET',
'ABGELAUFEN',
] as const
export const ZAHL_RHYTHMEN = [
'MONATLICH',
'VIERTELJAEHRLICH',
'HALBJAEHRLICH',
'JAEHRLICH',
'EINMALIG',
] as const
export const BEWERTUNGEN = ['UNGEPRUEFT', 'VERWERTBAR', 'GESCHUETZT', 'STRITTIG'] as const
export const VertragSchema = z
.object({
bezeichnung: pflichtText(
150,
'Bitte eine Bezeichnung angeben, z. B. „Sterbegeldversicherung Allianz“.',
),
gesellschaft: optionalerText(150),
art: z.enum(VERTRAG_ARTEN),
status: z.enum(VERTRAG_STATUS).default('AKTIV'),
/** Wird verschlüsselt gespeichert. */
vertragsnummer: optionalerText(80),
beginnAm: optionalesDatum,
endeAm: optionalesDatum,
kuendigungsfristMonate: optionaleZahl(
0,
120,
'Bitte die Kündigungsfrist in ganzen Monaten angeben.',
),
laufzeitJahre: optionaleZahl(1, 99, 'Bitte die Laufzeit in ganzen Jahren angeben.'),
kuendigungZum: optionalesDatum,
gekuendigtAm: optionalesDatum,
beitrag: optionalerBetrag,
rhythmus: z.enum(ZAHL_RHYTHMEN).nullable().catch(null),
bewertungSozialhilfe: z.enum(BEWERTUNGEN).default('UNGEPRUEFT'),
bewertungBegruendung: optionalerText(4000),
notizen: optionalerText(4000),
})
.superRefine((daten, kontext) => {
if (daten.beginnAm && daten.endeAm && daten.endeAm.getTime() < daten.beginnAm.getTime()) {
kontext.addIssue({
code: z.ZodIssueCode.custom,
path: ['endeAm'],
message: 'Das Vertragsende liegt vor dem Beginn.',
})
}
if (daten.beitrag !== null && !daten.rhythmus) {
kontext.addIssue({
code: z.ZodIssueCode.custom,
path: ['rhythmus'],
message: 'Zu einem Beitrag gehört der Zahlungsrhythmus — sonst lässt sich der Jahresbeitrag nicht rechnen.',
})
}
if (daten.bewertungSozialhilfe !== 'UNGEPRUEFT' && !daten.bewertungBegruendung) {
kontext.addIssue({
code: z.ZodIssueCode.custom,
path: ['bewertungBegruendung'],
message:
'Bitte begründen, warum du den Vertrag so einordnest. Der Bezirk fragt danach, ' +
'und im Zweifel steht diese Begründung Jahre später im Widerspruchsverfahren.',
})
}
if (daten.status === 'GEKUENDIGT' && !daten.gekuendigtAm) {
kontext.addIssue({
code: z.ZodIssueCode.custom,
path: ['gekuendigtAm'],
message: 'Bitte das Datum der Kündigung angeben.',
})
}
})
export type VertragEingabe = z.infer<typeof VertragSchema>
/** Rückkaufswert zu einem Stichtag - Historie, nichts wird überschrieben. */
export const RueckkaufswertSchema = z.object({
stichtag: pflichtDatum,
wert: pflichtBetrag,
wertquelle: z.enum(WERTQUELLEN_VERTRAG, {
errorMap: () => ({ message: 'Bitte angeben, woher der Wert stammt.' }),
}),
bemerkung: optionalerText(1000),
})
export type RueckkaufswertEingabe = z.infer<typeof RueckkaufswertSchema>
export const RueckkaufswertAnfrageSchema = z.object({
stichtag: pflichtDatum,
empfaengerAnschrift: optionalerText(500),
})
export type RueckkaufswertAnfrageEingabe = z.infer<typeof RueckkaufswertAnfrageSchema>
/**
* Kündigung vermerken. Der Versandnachweis ist Pflicht: Ohne ihn lässt
* sich der Zugang nicht beweisen, und beweisen muss ihn derjenige, der
* gekündigt hat.
*/
export const KuendigungVermerkenSchema = z
.object({
gekuendigtAm: pflichtDatum,
kuendigungZum: optionalesDatum,
versandart: pflichtText(100, 'Bitte angeben, wie die Kündigung versandt wurde.'),
nachweisVorhanden: z.boolean().default(false),
genehmigungGeklaert: z.boolean().default(false),
bemerkung: optionalerText(2000),
})
.superRefine((daten, kontext) => {
if (!daten.nachweisVorhanden) {
kontext.addIssue({
code: z.ZodIssueCode.custom,
path: ['nachweisVorhanden'],
message:
'Ohne Versandnachweis wird die Kündigung nicht als erledigt geführt. ' +
'Lade Einschreibenbeleg oder Faxprotokoll als Dokument hoch und hake dann an.',
})
}
})
export type KuendigungVermerkenEingabe = z.infer<typeof KuendigungVermerkenSchema>
import { z } from 'zod'
import {
optionalerBetrag,
optionalerText,
optionaleZahl,
optionalesDatum,
pflichtBetrag,
pflichtDatum,
pflichtText,
} from '@/lib/validierung/substanz'
import { WERTQUELLEN_VERTRAG } from '@/lib/vertraege/konstanten'
/**
* Eingabepruefung fuer Versicherungen und Vertraege.
*/
export const VERTRAG_ARTEN = [
'LEBENSVERSICHERUNG',
'STERBEGELDVERSICHERUNG',
'BESTATTUNGSVORSORGE',
'RENTENVERSICHERUNG_PRIVAT',
'UNFALLVERSICHERUNG',
'HAFTPFLICHT',
'HAUSRAT',
'WOHNGEBAEUDE',
'KRANKENZUSATZ',
'PFLEGEZUSATZ',
'RECHTSSCHUTZ',
'STERBEKASSE',
'STROM',
'GAS',
'WASSER',
'TELEFON_INTERNET',
'RUNDFUNKBEITRAG',
'MITGLIEDSCHAFT',
'ABONNEMENT',
'SONSTIGES',
] as const
export const VERTRAG_STATUS = [
'AKTIV',
'BEITRAGSFREI',
'RUHEND',
'GEKUENDIGT',
'ABGETRETEN',
'VERWERTET',
'ABGELAUFEN',
] as const
export const ZAHL_RHYTHMEN = [
'MONATLICH',
'VIERTELJAEHRLICH',
'HALBJAEHRLICH',
'JAEHRLICH',
'EINMALIG',
] as const
export const BEWERTUNGEN = ['UNGEPRUEFT', 'VERWERTBAR', 'GESCHUETZT', 'STRITTIG'] as const
export const VertragSchema = z
.object({
bezeichnung: pflichtText(
150,
'Bitte eine Bezeichnung angeben, z. B. „Sterbegeldversicherung Allianz“.',
),
gesellschaft: optionalerText(150),
art: z.enum(VERTRAG_ARTEN),
status: z.enum(VERTRAG_STATUS).default('AKTIV'),
/** Wird verschlüsselt gespeichert. */
vertragsnummer: optionalerText(80),
beginnAm: optionalesDatum,
endeAm: optionalesDatum,
kuendigungsfristMonate: optionaleZahl(
0,
120,
'Bitte die Kündigungsfrist in ganzen Monaten angeben.',
),
kuendigungZum: optionalesDatum,
gekuendigtAm: optionalesDatum,
beitrag: optionalerBetrag,
rhythmus: z.enum(ZAHL_RHYTHMEN).nullable().catch(null),
bewertungSozialhilfe: z.enum(BEWERTUNGEN).default('UNGEPRUEFT'),
bewertungBegruendung: optionalerText(4000),
notizen: optionalerText(4000),
})
.superRefine((daten, kontext) => {
if (daten.beginnAm && daten.endeAm && daten.endeAm.getTime() < daten.beginnAm.getTime()) {
kontext.addIssue({
code: z.ZodIssueCode.custom,
path: ['endeAm'],
message: 'Das Vertragsende liegt vor dem Beginn.',
})
}
if (daten.beitrag !== null && !daten.rhythmus) {
kontext.addIssue({
code: z.ZodIssueCode.custom,
path: ['rhythmus'],
message: 'Zu einem Beitrag gehört der Zahlungsrhythmus — sonst lässt sich der Jahresbeitrag nicht rechnen.',
})
}
if (daten.bewertungSozialhilfe !== 'UNGEPRUEFT' && !daten.bewertungBegruendung) {
kontext.addIssue({
code: z.ZodIssueCode.custom,
path: ['bewertungBegruendung'],
message:
'Bitte begründen, warum du den Vertrag so einordnest. Der Bezirk fragt danach, ' +
'und im Zweifel steht diese Begründung Jahre später im Widerspruchsverfahren.',
})
}
if (daten.status === 'GEKUENDIGT' && !daten.gekuendigtAm) {
kontext.addIssue({
code: z.ZodIssueCode.custom,
path: ['gekuendigtAm'],
message: 'Bitte das Datum der Kündigung angeben.',
})
}
})
export type VertragEingabe = z.infer<typeof VertragSchema>
/** Rückkaufswert zu einem Stichtag - Historie, nichts wird überschrieben. */
export const RueckkaufswertSchema = z.object({
stichtag: pflichtDatum,
wert: pflichtBetrag,
wertquelle: z.enum(WERTQUELLEN_VERTRAG, {
errorMap: () => ({ message: 'Bitte angeben, woher der Wert stammt.' }),
}),
bemerkung: optionalerText(1000),
})
export type RueckkaufswertEingabe = z.infer<typeof RueckkaufswertSchema>
export const RueckkaufswertAnfrageSchema = z.object({
stichtag: pflichtDatum,
empfaengerAnschrift: optionalerText(500),
})
export type RueckkaufswertAnfrageEingabe = z.infer<typeof RueckkaufswertAnfrageSchema>
/**
* Kündigung vermerken. Der Versandnachweis ist Pflicht: Ohne ihn lässt
* sich der Zugang nicht beweisen, und beweisen muss ihn derjenige, der
* gekündigt hat.
*/
export const KuendigungVermerkenSchema = z
.object({
gekuendigtAm: pflichtDatum,
kuendigungZum: optionalesDatum,
versandart: pflichtText(100, 'Bitte angeben, wie die Kündigung versandt wurde.'),
nachweisVorhanden: z.boolean().default(false),
genehmigungGeklaert: z.boolean().default(false),
bemerkung: optionalerText(2000),
})
.superRefine((daten, kontext) => {
if (!daten.nachweisVorhanden) {
kontext.addIssue({
code: z.ZodIssueCode.custom,
path: ['nachweisVorhanden'],
message:
'Ohne Versandnachweis wird die Kündigung nicht als erledigt geführt. ' +
'Lade Einschreibenbeleg oder Faxprotokoll als Dokument hoch und hake dann an.',
})
}
})
export type KuendigungVermerkenEingabe = z.infer<typeof KuendigungVermerkenSchema>