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,89 @@
|
||||
import { formatDatum } from '@/lib/utils'
|
||||
|
||||
/**
|
||||
* Gemeinsame Bausteine der Anschreiben an Versicherer.
|
||||
*
|
||||
* Der erzeugte Text ist Freitext zum Kopieren und Bearbeiten - bewusst
|
||||
* kein fertiges PDF. Jeder Fall hat eine Besonderheit, die in den Brief
|
||||
* gehoert, und ein Formulartext, den man nicht mehr aendern kann, wird
|
||||
* frueher oder falsch abgeschickt. Die PDF-Erzeugung kommt in Etappe 7.
|
||||
*/
|
||||
|
||||
export type Briefkopf = {
|
||||
/** Ich als Betreuer. */
|
||||
betreuerName: string
|
||||
betreuerStrasse?: string | null
|
||||
betreuerPlz?: string | null
|
||||
betreuerOrt?: string | null
|
||||
/** Die betreute Person. */
|
||||
betreuteName: string
|
||||
betreuteGeburtsdatum: Date | null
|
||||
betreuteAnschrift?: string | null
|
||||
/** Betreuungsgericht und Aktenzeichen - Nachweis der Bestellung. */
|
||||
betreuungsgericht?: string | null
|
||||
aktenzeichenGericht?: string | null
|
||||
bestellungAm?: Date | null
|
||||
}
|
||||
|
||||
export type Empfaenger = {
|
||||
gesellschaft: string | null
|
||||
anschrift?: string | null
|
||||
}
|
||||
|
||||
export function anschriftBlock(empfaenger: Empfaenger): string {
|
||||
const zeilen = [empfaenger.gesellschaft ?? '[Name der Gesellschaft]']
|
||||
if (empfaenger.anschrift) {
|
||||
zeilen.push(...empfaenger.anschrift.split('\n').map((z) => z.trim()).filter(Boolean))
|
||||
} else {
|
||||
zeilen.push('[Straße und Hausnummer]', '[PLZ und Ort]')
|
||||
}
|
||||
return zeilen.join('\n')
|
||||
}
|
||||
|
||||
export function absenderBlock(kopf: Briefkopf): string {
|
||||
const zeilen = [kopf.betreuerName]
|
||||
if (kopf.betreuerStrasse) zeilen.push(kopf.betreuerStrasse)
|
||||
const ort = [kopf.betreuerPlz, kopf.betreuerOrt].filter(Boolean).join(' ')
|
||||
if (ort) zeilen.push(ort)
|
||||
return zeilen.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* Der Absatz, der in jedem Schreiben stehen muss: Wer bin ich, und woraus
|
||||
* folgt meine Vertretungsmacht? Ohne ihn kommt die Antwort "aus
|
||||
* datenschutzrechtlichen Gruenden koennen wir Ihnen keine Auskunft
|
||||
* erteilen" - und dann ist ein Monat vergangen.
|
||||
*/
|
||||
export function bestellungsAbsatz(kopf: Briefkopf): string {
|
||||
const gericht = kopf.betreuungsgericht ?? '[Betreuungsgericht]'
|
||||
const az = kopf.aktenzeichenGericht ?? '[Aktenzeichen]'
|
||||
const seit = kopf.bestellungAm
|
||||
? `mit Beschluss vom ${formatDatum(kopf.bestellungAm)}`
|
||||
: '[Datum des Bestellungsbeschlusses ergänzen]'
|
||||
|
||||
return (
|
||||
`ich bin ${seit} vom ${gericht} (Az. ${az}) zum rechtlichen Betreuer ` +
|
||||
`für ${kopf.betreuteName} bestellt; der Aufgabenkreis umfasst die ` +
|
||||
`Vermögenssorge. Eine Kopie der Bestellungsurkunde füge ich bei. ` +
|
||||
`Ich handele damit als gesetzlicher Vertreter (§ 1823 BGB).`
|
||||
)
|
||||
}
|
||||
|
||||
export function betreffZeile(
|
||||
gegenstand: string,
|
||||
vertragsnummer: string | null,
|
||||
betreuteName: string,
|
||||
geburtsdatum: Date | null,
|
||||
): string {
|
||||
const nummer = vertragsnummer ? `Vertragsnummer ${vertragsnummer}` : 'Vertragsnummer [bitte ergänzen]'
|
||||
const geboren = geburtsdatum ? `, geboren am ${formatDatum(geburtsdatum)}` : ''
|
||||
return `Betreff: ${gegenstand}\n${nummer}\nVersicherungsnehmerin: ${betreuteName}${geboren}`
|
||||
}
|
||||
|
||||
export function datumsZeile(ort: string | null | undefined, datum: Date): string {
|
||||
return `${ort ? `${ort}, den ` : ''}${formatDatum(datum)}`
|
||||
}
|
||||
|
||||
export function grussformel(betreuerName: string): string {
|
||||
return `Mit freundlichen Grüßen\n\n\n${betreuerName}\nRechtlicher Betreuer`
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import type { SozialhilfeBewertung, VertragArt, VertragStatus, ZahlRhythmus } from '@prisma/client'
|
||||
|
||||
/**
|
||||
* Beschriftungen und Einordnungen fuer Versicherungen und Vertraege.
|
||||
*/
|
||||
|
||||
export const VERTRAG_ART_BESCHRIFTUNG: Record<VertragArt, string> = {
|
||||
LEBENSVERSICHERUNG: 'Lebensversicherung',
|
||||
STERBEGELDVERSICHERUNG: 'Sterbegeldversicherung',
|
||||
BESTATTUNGSVORSORGE: 'Bestattungsvorsorgevertrag',
|
||||
RENTENVERSICHERUNG_PRIVAT: 'Private Rentenversicherung',
|
||||
UNFALLVERSICHERUNG: 'Unfallversicherung',
|
||||
HAFTPFLICHT: 'Privathaftpflicht',
|
||||
HAUSRAT: 'Hausratversicherung',
|
||||
WOHNGEBAEUDE: 'Wohngebäudeversicherung',
|
||||
KRANKENZUSATZ: 'Krankenzusatzversicherung',
|
||||
PFLEGEZUSATZ: 'Pflegezusatzversicherung',
|
||||
RECHTSSCHUTZ: 'Rechtsschutzversicherung',
|
||||
STERBEKASSE: 'Sterbekasse',
|
||||
STROM: 'Stromvertrag',
|
||||
GAS: 'Gasvertrag',
|
||||
WASSER: 'Wasserversorgung',
|
||||
TELEFON_INTERNET: 'Telefon und Internet',
|
||||
RUNDFUNKBEITRAG: 'Rundfunkbeitrag',
|
||||
MITGLIEDSCHAFT: 'Mitgliedschaft',
|
||||
ABONNEMENT: 'Abonnement',
|
||||
SONSTIGES: 'Sonstiges',
|
||||
}
|
||||
|
||||
export const VERTRAG_STATUS_BESCHRIFTUNG: Record<VertragStatus, string> = {
|
||||
AKTIV: 'aktiv',
|
||||
BEITRAGSFREI: 'beitragsfrei gestellt',
|
||||
RUHEND: 'ruhend',
|
||||
GEKUENDIGT: 'gekündigt',
|
||||
ABGETRETEN: 'abgetreten',
|
||||
VERWERTET: 'verwertet',
|
||||
ABGELAUFEN: 'abgelaufen',
|
||||
}
|
||||
|
||||
export const RHYTHMUS_BESCHRIFTUNG: Record<ZahlRhythmus, string> = {
|
||||
MONATLICH: 'monatlich',
|
||||
VIERTELJAEHRLICH: 'vierteljährlich',
|
||||
HALBJAEHRLICH: 'halbjährlich',
|
||||
JAEHRLICH: 'jährlich',
|
||||
EINMALIG: 'einmalig',
|
||||
}
|
||||
|
||||
/** Wie oft im Jahr wird gezahlt? Fuer den Jahresbeitrag in der Uebersicht. */
|
||||
export const RHYTHMUS_JAHRESFAKTOR: Record<ZahlRhythmus, number> = {
|
||||
MONATLICH: 12,
|
||||
VIERTELJAEHRLICH: 4,
|
||||
HALBJAEHRLICH: 2,
|
||||
JAEHRLICH: 1,
|
||||
EINMALIG: 0,
|
||||
}
|
||||
|
||||
export function jahresbeitragCent(
|
||||
beitragCent: bigint | null | undefined,
|
||||
rhythmus: ZahlRhythmus | null | undefined,
|
||||
): bigint | null {
|
||||
if (beitragCent === null || beitragCent === undefined || !rhythmus) return null
|
||||
const faktor = RHYTHMUS_JAHRESFAKTOR[rhythmus]
|
||||
if (faktor === 0) return null
|
||||
return beitragCent * BigInt(faktor)
|
||||
}
|
||||
|
||||
export const BEWERTUNG_BESCHRIFTUNG: Record<SozialhilfeBewertung, string> = {
|
||||
UNGEPRUEFT: 'ungeprüft',
|
||||
VERWERTBAR: 'verwertbar',
|
||||
GESCHUETZT: 'geschützt',
|
||||
STRITTIG: 'strittig',
|
||||
}
|
||||
|
||||
export const BEWERTUNG_ERKLAERUNG: Record<SozialhilfeBewertung, string> = {
|
||||
UNGEPRUEFT: 'Noch nicht eingeordnet. Vor dem nächsten Schreiben an den Bezirk nachholen.',
|
||||
VERWERTBAR:
|
||||
'Der Vertrag lässt sich zu Geld machen und ist vor der Sozialhilfe einzusetzen. ' +
|
||||
'Das heißt nicht, dass gekündigt werden muss — erst rechnen, dann handeln.',
|
||||
GESCHUETZT:
|
||||
'Aus meiner Sicht Schonvermögen, etwa eine angemessene Bestattungsvorsorge ' +
|
||||
'(§ 90 Abs. 3 SGB XII) oder ein Vertrag unterhalb der Vermögensfreigrenze. ' +
|
||||
'Die Begründung gehört daneben.',
|
||||
STRITTIG: 'Der Träger sieht es anders als ich. Schriftwechsel dokumentieren.',
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertragsarten, bei denen eine Verwertung genehmigungspflichtig sein
|
||||
* kann. Das ist bewusst eine Pruefliste, keine Entscheidung: Ob § 1850 BGB
|
||||
* greift, haengt am Einzelfall und beantwortet der Rechtspfleger, nicht
|
||||
* diese Anwendung.
|
||||
*/
|
||||
export const GENEHMIGUNG_PRUEFEN_BEI: VertragArt[] = [
|
||||
'LEBENSVERSICHERUNG',
|
||||
'RENTENVERSICHERUNG_PRIVAT',
|
||||
'STERBEGELDVERSICHERUNG',
|
||||
'BESTATTUNGSVORSORGE',
|
||||
'STERBEKASSE',
|
||||
]
|
||||
|
||||
/**
|
||||
* Vertragsarten, die einen Rueckkaufswert haben koennen. Nur bei diesen
|
||||
* ergibt die Anforderung beim Versicherer Sinn.
|
||||
*/
|
||||
export const MIT_RUECKKAUFSWERT: VertragArt[] = [
|
||||
'LEBENSVERSICHERUNG',
|
||||
'RENTENVERSICHERUNG_PRIVAT',
|
||||
'STERBEGELDVERSICHERUNG',
|
||||
'BESTATTUNGSVORSORGE',
|
||||
'STERBEKASSE',
|
||||
]
|
||||
|
||||
export function kannRueckkaufswertHaben(art: VertragArt): boolean {
|
||||
return MIT_RUECKKAUFSWERT.includes(art)
|
||||
}
|
||||
|
||||
export function genehmigungPruefen(art: VertragArt): boolean {
|
||||
return GENEHMIGUNG_PRUEFEN_BEI.includes(art)
|
||||
}
|
||||
|
||||
/** Wertquellen fuer Rueckkaufswerte - Pflichtangabe an jedem Wertstand. */
|
||||
export const WERTQUELLEN_VERTRAG = [
|
||||
'Rückkaufswertmitteilung der Gesellschaft',
|
||||
'Jahresstandmitteilung',
|
||||
'telefonische Auskunft',
|
||||
'eigene Schätzung',
|
||||
] as const
|
||||
|
||||
export const RECHTSGRUNDLAGE_GENEHMIGUNG = '§ 1850 BGB'
|
||||
@@ -0,0 +1,207 @@
|
||||
import { alsIsoTag, heuteAlsTag, plusJahre, plusMonate, plusTage, tageZwischen, tagesdatum } from '@/lib/fristen/datum'
|
||||
|
||||
/**
|
||||
* Kuendigungsfrist-Rechner.
|
||||
*
|
||||
* Die eigentliche Falle bei Versicherungsvertraegen ist nicht der
|
||||
* Kuendigungstermin, sondern der Tag, an dem der Brief spaetestens raus
|
||||
* muss. Eine Kuendigung wird mit dem ZUGANG beim Versicherer wirksam, nicht
|
||||
* mit dem Absenden - wer am letzten Tag der Frist einwirft, hat die Frist
|
||||
* verpasst und zahlt ein weiteres Jahr.
|
||||
*
|
||||
* Deshalb rechnet diese Datei drei Daten aus:
|
||||
*
|
||||
* 1. den naechstmoeglichen Kuendigungstermin (zu wann kann gekuendigt werden),
|
||||
* 2. den Tag, an dem die Kuendigung spaetestens zugehen muss,
|
||||
* 3. den spaetesten Absendetag mit Postlaufpuffer.
|
||||
*
|
||||
* Reine Logik, keine Datenbank.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Puffer zwischen Absenden und Zugang. Drei Kalendertage sind knapp, aber
|
||||
* bei einem Einwurf-Einschreiben realistisch. Wer es genauer braucht,
|
||||
* schickt zusaetzlich ein Fax oder eine E-Mail - beides zaehlt fuer den
|
||||
* Zugang und ist am selben Tag da.
|
||||
*/
|
||||
export const POSTLAUF_PUFFER_TAGE = 3
|
||||
|
||||
/** Ab wann wird gewarnt, dass der Absendetag naeher rueckt. */
|
||||
export const KUENDIGUNG_VORWARNUNG_TAGE = 30
|
||||
|
||||
/**
|
||||
* Verlaengerung, wenn im Vertrag keine Laufzeit hinterlegt ist. Ein Jahr
|
||||
* ist bei Versicherungen der Regelfall (§ 11 Abs. 1 VVG).
|
||||
*/
|
||||
export const STANDARD_LAUFZEIT_JAHRE = 1
|
||||
|
||||
export type KuendigungEingabe = {
|
||||
/** Laufzeitbeginn laut Vertrag. */
|
||||
beginnAm: Date | null
|
||||
/** Fest vereinbartes Vertragsende, falls bekannt. */
|
||||
endeAm?: Date | null
|
||||
/** Vertragsdauer in Jahren; fehlt sie, wird mit einem Jahr gerechnet. */
|
||||
laufzeitJahre?: number | null
|
||||
/** Kuendigungsfrist in Monaten zum Ende der Laufzeit. */
|
||||
kuendigungsfristMonate: number | null
|
||||
/** Verlaengert sich der Vertrag stillschweigend? Regelfall: ja. */
|
||||
verlaengertSichAutomatisch?: boolean
|
||||
/** Bezugstag; im Test gesetzt, sonst heute. */
|
||||
heute?: Date
|
||||
}
|
||||
|
||||
export type Kuendigungsplan = {
|
||||
/** Laesst sich ueberhaupt etwas berechnen? */
|
||||
berechenbar: boolean
|
||||
/** Zu diesem Tag kann als Naechstes gekuendigt werden. */
|
||||
naechsterTermin: Date | null
|
||||
/** Bis zu diesem Tag muss die Kuendigung beim Vertragspartner sein. */
|
||||
zugangSpaetestensAm: Date | null
|
||||
/** Bis zu diesem Tag muss der Brief raus (Zugang minus Postlauf). */
|
||||
spaetesterAbsendetag: Date | null
|
||||
/** Tage von heute bis zum Absendetag. Negativ heisst: schon vorbei. */
|
||||
tageBisAbsendetag: number | null
|
||||
/** Weniger als 30 Tage bis zum Absendetag - jetzt handeln. */
|
||||
vorwarnung: boolean
|
||||
/** Der Absendetag ist verstrichen und der Vertrag laeuft aus. */
|
||||
verpasst: boolean
|
||||
/** Erklaerungen und Annahmen, die in die Rechnung eingegangen sind. */
|
||||
hinweise: string[]
|
||||
}
|
||||
|
||||
const LEERER_PLAN: Kuendigungsplan = {
|
||||
berechenbar: false,
|
||||
naechsterTermin: null,
|
||||
zugangSpaetestensAm: null,
|
||||
spaetesterAbsendetag: null,
|
||||
tageBisAbsendetag: null,
|
||||
vorwarnung: false,
|
||||
verpasst: false,
|
||||
hinweise: [],
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den naechstmoeglichen Kuendigungstermin.
|
||||
*
|
||||
* Vorgehen: Vom ersten Ablauftermin an wird geprueft, ob der zugehoerige
|
||||
* Absendetag noch in der Zukunft liegt. Ist er verstrichen, wird um eine
|
||||
* Laufzeit weitergerueckt. Der erste Termin, dessen Brief noch rechtzeitig
|
||||
* rausgeht, ist der naechstmoegliche.
|
||||
*/
|
||||
export function kuendigungsplan(eingabe: KuendigungEingabe): Kuendigungsplan {
|
||||
const heute = tagesdatum(eingabe.heute ?? heuteAlsTag())
|
||||
const hinweise: string[] = []
|
||||
|
||||
const frist = eingabe.kuendigungsfristMonate
|
||||
if (frist === null || frist === undefined) {
|
||||
hinweise.push(
|
||||
'Es ist keine Kündigungsfrist hinterlegt. Es wird ohne Frist gerechnet — ' +
|
||||
'bitte im Vertrag nachsehen und nachtragen.',
|
||||
)
|
||||
}
|
||||
const fristMonate = frist ?? 0
|
||||
|
||||
const laufzeit =
|
||||
eingabe.laufzeitJahre && eingabe.laufzeitJahre > 0
|
||||
? eingabe.laufzeitJahre
|
||||
: STANDARD_LAUFZEIT_JAHRE
|
||||
if (!eingabe.laufzeitJahre || eingabe.laufzeitJahre <= 0) {
|
||||
hinweise.push(
|
||||
`Im Vertrag steht keine Laufzeit. Gerechnet wird mit einer Verlängerung um ` +
|
||||
`${STANDARD_LAUFZEIT_JAHRE} Jahr — bei Versicherungen der Regelfall.`,
|
||||
)
|
||||
}
|
||||
|
||||
const verlaengert = eingabe.verlaengertSichAutomatisch ?? true
|
||||
|
||||
// Erster Ablauftermin: das vereinbarte Ende, sonst Beginn plus Laufzeit.
|
||||
let termin: Date | null = null
|
||||
if (eingabe.endeAm) {
|
||||
termin = tagesdatum(eingabe.endeAm)
|
||||
} else if (eingabe.beginnAm) {
|
||||
termin = plusJahre(tagesdatum(eingabe.beginnAm), laufzeit)
|
||||
}
|
||||
|
||||
if (!termin) {
|
||||
return {
|
||||
...LEERER_PLAN,
|
||||
hinweise: [
|
||||
'Ohne Laufzeitbeginn oder Vertragsende lässt sich kein Kündigungstermin ' +
|
||||
'berechnen. Bitte mindestens den Vertragsbeginn eintragen.',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// Bis zu 200 Verlaengerungen durchgehen - deckt auch einen Vertrag von
|
||||
// 1962 ab, ohne bei fehlerhaften Daten endlos zu laufen.
|
||||
let absendetag = absendetagZu(termin, fristMonate)
|
||||
let durchlaeufe = 0
|
||||
while (absendetag.getTime() < heute.getTime() && verlaengert && durchlaeufe < 200) {
|
||||
termin = plusJahre(termin, laufzeit)
|
||||
absendetag = absendetagZu(termin, fristMonate)
|
||||
durchlaeufe += 1
|
||||
}
|
||||
|
||||
const zugang = plusMonate(termin, -fristMonate)
|
||||
const tageBis = tageZwischen(heute, absendetag)
|
||||
const verpasst = absendetag.getTime() < heute.getTime()
|
||||
|
||||
if (verpasst) {
|
||||
hinweise.push(
|
||||
`Der Absendetag für den Termin ${alsIsoTag(termin)} ist verstrichen. ` +
|
||||
`Dieser Vertrag verlängert sich nicht automatisch — eine Kündigung zu diesem ` +
|
||||
`Termin ist nicht mehr möglich.`,
|
||||
)
|
||||
} else if (durchlaeufe > 0) {
|
||||
hinweise.push(
|
||||
`Der Vertrag hat sich bereits ${durchlaeufe}-mal verlängert. Angezeigt ist der ` +
|
||||
`nächste Termin, zu dem noch rechtzeitig gekündigt werden kann.`,
|
||||
)
|
||||
}
|
||||
|
||||
hinweise.push(
|
||||
`Eine Kündigung wird mit dem Zugang wirksam, nicht mit dem Absenden. Deshalb ` +
|
||||
`sind zwischen Absendetag und Zugang ${POSTLAUF_PUFFER_TAGE} Tage Postlauf ` +
|
||||
`eingerechnet. Am sichersten: Einschreiben mit Rückschein oder zusätzlich ` +
|
||||
`ein Fax mit Sendeprotokoll.`,
|
||||
)
|
||||
|
||||
return {
|
||||
berechenbar: true,
|
||||
naechsterTermin: termin,
|
||||
zugangSpaetestensAm: zugang,
|
||||
spaetesterAbsendetag: absendetag,
|
||||
tageBisAbsendetag: tageBis,
|
||||
vorwarnung: !verpasst && tageBis <= KUENDIGUNG_VORWARNUNG_TAGE,
|
||||
verpasst,
|
||||
hinweise,
|
||||
}
|
||||
}
|
||||
|
||||
/** Der Absendetag zu einem Termin: Termin minus Frist minus Postlauf. */
|
||||
export function absendetagZu(termin: Date, fristMonate: number): Date {
|
||||
return plusTage(plusMonate(tagesdatum(termin), -fristMonate), -POSTLAUF_PUFFER_TAGE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Kurzfassung fuer die Uebersichtstabelle: eine Ampelstufe je Vertrag.
|
||||
* Farbe ist nie die einzige Information - der Text steht daneben.
|
||||
*/
|
||||
export type Kuendigungsampel = {
|
||||
variante: 'rot' | 'orange' | 'gelb' | 'gruen'
|
||||
text: string
|
||||
}
|
||||
|
||||
export function kuendigungsampel(plan: Kuendigungsplan): Kuendigungsampel | null {
|
||||
if (!plan.berechenbar || plan.tageBisAbsendetag === null) return null
|
||||
if (plan.verpasst) {
|
||||
return { variante: 'rot', text: 'Absendetag verstrichen' }
|
||||
}
|
||||
const tage = plan.tageBisAbsendetag
|
||||
if (tage <= 7) return { variante: 'rot', text: `Brief muss in ${tage} Tagen raus` }
|
||||
if (tage <= KUENDIGUNG_VORWARNUNG_TAGE) {
|
||||
return { variante: 'orange', text: `Brief muss in ${tage} Tagen raus` }
|
||||
}
|
||||
if (tage <= 60) return { variante: 'gelb', text: `Absendetag in ${tage} Tagen` }
|
||||
return { variante: 'gruen', text: `Absendetag in ${tage} Tagen` }
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
import type { VertragArt } from '@prisma/client'
|
||||
import { formatDatum } from '@/lib/utils'
|
||||
import { alsIsoTag, heuteAlsTag } from '@/lib/fristen/datum'
|
||||
import {
|
||||
absenderBlock,
|
||||
anschriftBlock,
|
||||
bestellungsAbsatz,
|
||||
betreffZeile,
|
||||
datumsZeile,
|
||||
grussformel,
|
||||
type Briefkopf,
|
||||
type Empfaenger,
|
||||
} from '@/lib/vertraege/anschreiben'
|
||||
import {
|
||||
genehmigungPruefen,
|
||||
kannRueckkaufswertHaben,
|
||||
RECHTSGRUNDLAGE_GENEHMIGUNG,
|
||||
VERTRAG_ART_BESCHRIFTUNG,
|
||||
} from '@/lib/vertraege/konstanten'
|
||||
import {
|
||||
kuendigungsplan,
|
||||
type Kuendigungsplan,
|
||||
type KuendigungEingabe,
|
||||
} from '@/lib/vertraege/kuendigung'
|
||||
|
||||
/**
|
||||
* Kuendigungs-Assistent.
|
||||
*
|
||||
* Er tut absichtlich drei Dinge NICHT: Er kuendigt nicht, er verschickt
|
||||
* nichts, und er entscheidet nicht, ob eine Genehmigung noetig ist.
|
||||
*
|
||||
* Der Grund fuer das Letzte ist wichtig genug, um ihn hier hinzuschreiben:
|
||||
* Ob die Verwertung einer Lebensversicherung nach § 1850 BGB
|
||||
* genehmigungspflichtig ist, haengt am Einzelfall - an der Vertragsart, am
|
||||
* Wert, an der Frage, ob es sich um eine Verfuegung ueber eine Forderung
|
||||
* handelt, und an der Handhabung des jeweiligen Betreuungsgerichts. Eine
|
||||
* Automatik, die "nicht genehmigungspflichtig" ausgibt und daneben liegt,
|
||||
* macht das Rechtsgeschaeft schwebend unwirksam und den Betreuer
|
||||
* haftbar. Also: Der Assistent stellt die Frage, laut und sichtbar, und
|
||||
* verlangt eine bewusste Antwort - die Rueckfrage beim Rechtspfleger.
|
||||
*/
|
||||
|
||||
export type KuendigungsassistentEingabe = {
|
||||
kopf: Briefkopf
|
||||
empfaenger: Empfaenger
|
||||
vertragsbezeichnung: string
|
||||
vertragsnummer: string | null
|
||||
art: VertragArt
|
||||
laufzeit: KuendigungEingabe
|
||||
/** Soll zum naechstmoeglichen Termin gekuendigt werden oder zu einem anderen? */
|
||||
kuendigungZum?: Date | null
|
||||
heute?: Date
|
||||
}
|
||||
|
||||
export type Genehmigungsfrage = {
|
||||
/** Muss die Frage gestellt werden? */
|
||||
stellen: boolean
|
||||
frage: string
|
||||
rechtsgrundlage: string
|
||||
hinweis: string
|
||||
}
|
||||
|
||||
export type Versandnachweis = {
|
||||
/** Ohne Nachweis wird die Kuendigung nicht als erledigt gefuehrt. */
|
||||
verlangt: true
|
||||
moeglichkeiten: string[]
|
||||
hinweis: string
|
||||
}
|
||||
|
||||
export type Kuendigungspaket = {
|
||||
plan: Kuendigungsplan
|
||||
/** Der Termin, zu dem gekuendigt wird. */
|
||||
terminZum: Date | null
|
||||
schreiben: string
|
||||
genehmigung: Genehmigungsfrage
|
||||
versandnachweis: Versandnachweis
|
||||
/** Alles, was vor dem Absenden geklaert sein muss. */
|
||||
warnungen: string[]
|
||||
}
|
||||
|
||||
export function kuendigungsassistent(eingabe: KuendigungsassistentEingabe): Kuendigungspaket {
|
||||
const heute = eingabe.heute ?? heuteAlsTag()
|
||||
const plan = kuendigungsplan({ ...eingabe.laufzeit, heute })
|
||||
const terminZum = eingabe.kuendigungZum ?? plan.naechsterTermin
|
||||
|
||||
const warnungen: string[] = []
|
||||
|
||||
if (!plan.berechenbar) {
|
||||
warnungen.push(
|
||||
'Der Kündigungstermin lässt sich nicht berechnen. Trag Vertragsbeginn und ' +
|
||||
'Kündigungsfrist nach, bevor du das Schreiben abschickst — sonst kündigst ' +
|
||||
'du ins Blaue.',
|
||||
)
|
||||
}
|
||||
if (plan.verpasst) {
|
||||
warnungen.push(
|
||||
'Der Absendetag für den errechneten Termin ist verstrichen. Schick die ' +
|
||||
'Kündigung trotzdem los, aber rechne mit einer Bestätigung zum übernächsten ' +
|
||||
'Termin — und prüfe, ob ein Sonderkündigungsrecht greift.',
|
||||
)
|
||||
}
|
||||
if (plan.vorwarnung && !plan.verpasst) {
|
||||
warnungen.push(
|
||||
`Der Brief muss bis zum ${formatDatum(plan.spaetesterAbsendetag)} raus. ` +
|
||||
`Das sind nur noch ${plan.tageBisAbsendetag} Tage.`,
|
||||
)
|
||||
}
|
||||
if (kannRueckkaufswertHaben(eingabe.art)) {
|
||||
warnungen.push(
|
||||
'Vor der Kündigung den Rückkaufswert UND die beitragsfreie ' +
|
||||
'Versicherungssumme vergleichen. Eine Beitragsfreistellung erhält den ' +
|
||||
'Vertrag und ist bei Sterbegeld- und Bestattungsvorsorgeverträgen häufig ' +
|
||||
'die bessere Lösung als der Rückkauf — gekündigtes Geld kommt nicht zurück.',
|
||||
)
|
||||
}
|
||||
if (eingabe.art === 'BESTATTUNGSVORSORGE' || eingabe.art === 'STERBEKASSE') {
|
||||
warnungen.push(
|
||||
'Eine angemessene Bestattungsvorsorge ist regelmäßig Schonvermögen ' +
|
||||
'(§ 90 Abs. 3 SGB XII). Kündige sie nicht vorschnell, nur weil der Träger ' +
|
||||
'sie in einer Aufstellung führt — erst die Angemessenheit klären.',
|
||||
)
|
||||
}
|
||||
if (!eingabe.vertragsnummer) {
|
||||
warnungen.push(
|
||||
'Die Vertragsnummer fehlt. Ohne sie ordnet die Gesellschaft das Schreiben ' +
|
||||
'nicht zu, und die Frist läuft weiter.',
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
plan,
|
||||
terminZum,
|
||||
schreiben: kuendigungsschreiben(eingabe, terminZum, heute),
|
||||
genehmigung: genehmigungsfrage(eingabe.art),
|
||||
versandnachweis: VERSANDNACHWEIS,
|
||||
warnungen,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Frage nach der Genehmigung. Sie wird gestellt, nicht beantwortet.
|
||||
*/
|
||||
export function genehmigungsfrage(art: VertragArt): Genehmigungsfrage {
|
||||
const bezeichnung = VERTRAG_ART_BESCHRIFTUNG[art]
|
||||
|
||||
if (!genehmigungPruefen(art)) {
|
||||
return {
|
||||
stellen: false,
|
||||
frage: `Ist die Kündigung dieses Vertrages (${bezeichnung}) genehmigungspflichtig?`,
|
||||
rechtsgrundlage: RECHTSGRUNDLAGE_GENEHMIGUNG,
|
||||
hinweis:
|
||||
'Bei laufenden Verbrauchs- und Sachversicherungsverträgen ist eine ' +
|
||||
'Genehmigung in der Regel nicht erforderlich. Bist du unsicher, kostet ' +
|
||||
'die Rückfrage beim Rechtspfleger fünf Minuten.',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
stellen: true,
|
||||
frage:
|
||||
`Hast du beim Rechtspfleger geklärt, ob die Verwertung dieses Vertrages ` +
|
||||
`(${bezeichnung}) genehmigungspflichtig ist?`,
|
||||
rechtsgrundlage: RECHTSGRUNDLAGE_GENEHMIGUNG,
|
||||
hinweis:
|
||||
'Die Kündigung einer Lebens- oder Rentenversicherung ist eine Verfügung ' +
|
||||
'über eine Forderung und kann nach § 1850 BGB der Genehmigung des ' +
|
||||
'Betreuungsgerichts bedürfen. Ob sie es im konkreten Fall tut, hängt am ' +
|
||||
'Einzelfall — diese Anwendung entscheidet das ausdrücklich nicht. ' +
|
||||
'Die Genehmigung muss VOR dem Rechtsgeschäft vorliegen (§ 1858 BGB); ' +
|
||||
'wer erst kündigt und dann fragt, hat ein schwebend unwirksames Geschäft ' +
|
||||
'und im schlimmsten Fall ein Haftungsproblem. Ein kurzer Anruf beim ' +
|
||||
'zuständigen Rechtspfleger, notiert im Kontaktprotokoll, klärt das.',
|
||||
}
|
||||
}
|
||||
|
||||
export const VERSANDNACHWEIS: Versandnachweis = {
|
||||
verlangt: true,
|
||||
moeglichkeiten: [
|
||||
'Einschreiben mit Rückschein (Rückschein einscannen)',
|
||||
'Einwurf-Einschreiben (Einlieferungsbeleg und Sendungsstatus)',
|
||||
'Fax mit Sendeprotokoll („OK“, mit Datum und Uhrzeit)',
|
||||
'Bestätigung der Gesellschaft über den Eingang der Kündigung',
|
||||
],
|
||||
hinweis:
|
||||
'Ohne Nachweis über den Zugang ist die Kündigung im Streitfall nicht ' +
|
||||
'bewiesen — und beweisen muss sie derjenige, der gekündigt hat. Lade den ' +
|
||||
'Nachweis als Dokument hoch und verknüpfe ihn mit dem Vertrag, bevor du ' +
|
||||
'die Kündigung hier als erledigt einträgst.',
|
||||
}
|
||||
|
||||
function kuendigungsschreiben(
|
||||
eingabe: KuendigungsassistentEingabe,
|
||||
terminZum: Date | null,
|
||||
heute: Date,
|
||||
): string {
|
||||
const kopf = eingabe.kopf
|
||||
const zum = terminZum
|
||||
? `zum nächstmöglichen Termin, nach meiner Berechnung zum ${formatDatum(terminZum)},`
|
||||
: 'zum nächstmöglichen Termin'
|
||||
|
||||
return [
|
||||
absenderBlock(kopf),
|
||||
'',
|
||||
anschriftBlock(eingabe.empfaenger),
|
||||
'',
|
||||
datumsZeile(kopf.betreuerOrt, heute),
|
||||
'',
|
||||
betreffZeile(
|
||||
'Kündigung des Vertrages',
|
||||
eingabe.vertragsnummer,
|
||||
kopf.betreuteName,
|
||||
kopf.betreuteGeburtsdatum,
|
||||
),
|
||||
'',
|
||||
'Sehr geehrte Damen und Herren,',
|
||||
'',
|
||||
bestellungsAbsatz(kopf),
|
||||
'',
|
||||
`hiermit kündige ich den oben genannten Vertrag ` +
|
||||
`(${eingabe.vertragsbezeichnung}) ${zum} ` +
|
||||
`hilfsweise zum nächstmöglichen Zeitpunkt.`,
|
||||
'',
|
||||
'Ich bitte um schriftliche Bestätigung der Kündigung unter Angabe des ' +
|
||||
'Beendigungszeitpunktes sowie um Mitteilung, welcher Betrag zur Auszahlung ' +
|
||||
'kommt und wann die Auszahlung erfolgt.',
|
||||
'',
|
||||
'Die Auszahlung bitte ich ausschließlich auf ein Konto der Versicherungsnehmerin ' +
|
||||
'vorzunehmen; die Bankverbindung teile ich Ihnen auf Anforderung mit. ' +
|
||||
'Zahlungen auf ein Konto des Betreuers sind ausgeschlossen.',
|
||||
'',
|
||||
'Sollten dem Vertrag Abtretungen, Verpfändungen oder unwiderrufliche ' +
|
||||
'Bezugsrechte entgegenstehen, bitte ich um sofortige Mitteilung, bevor Sie ' +
|
||||
'die Kündigung ausführen.',
|
||||
'',
|
||||
grussformel(kopf.betreuerName),
|
||||
'',
|
||||
'Anlage: Kopie der Bestellungsurkunde',
|
||||
'',
|
||||
`— Entwurf erstellt am ${alsIsoTag(heute)}. Bitte vor dem Absenden prüfen und ` +
|
||||
`an den Einzelfall anpassen. —`,
|
||||
].join('\n')
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import { formatDatum } from '@/lib/utils'
|
||||
import { heuteAlsTag, plusTage, tageZwischen } from '@/lib/fristen/datum'
|
||||
import {
|
||||
absenderBlock,
|
||||
anschriftBlock,
|
||||
bestellungsAbsatz,
|
||||
betreffZeile,
|
||||
datumsZeile,
|
||||
grussformel,
|
||||
type Briefkopf,
|
||||
type Empfaenger,
|
||||
} from '@/lib/vertraege/anschreiben'
|
||||
|
||||
/**
|
||||
* Anforderung des Rueckkaufswerts beim Versicherer.
|
||||
*
|
||||
* Warum das ein eigener Knopf ist: Der Bezirk verlangt fuer die Hilfe zur
|
||||
* Pflege zu jedem Versicherungsvertrag den Rueckkaufswert zu einem
|
||||
* Stichtag - und zwar schriftlich von der Gesellschaft, nicht aus der
|
||||
* Jahresmitteilung geschaetzt. Die Antwort dauert regelmaessig drei bis
|
||||
* sechs Wochen. Wer die Anfrage vergisst, haelt am Ende das ganze
|
||||
* Verfahren auf.
|
||||
*
|
||||
* Deshalb: Brief erzeugen, Datum der Anfrage festhalten, Wiedervorlage in
|
||||
* vier Wochen. Die Wiedervorlage legt die Server Action als Frist an.
|
||||
*/
|
||||
|
||||
export const RUECKKAUFSWERT_WIEDERVORLAGE_TAGE = 28
|
||||
|
||||
export function wiedervorlageAm(angefragtAm: Date = heuteAlsTag()): Date {
|
||||
return plusTage(angefragtAm, RUECKKAUFSWERT_WIEDERVORLAGE_TAGE)
|
||||
}
|
||||
|
||||
export type RueckkaufswertAnfrage = {
|
||||
kopf: Briefkopf
|
||||
empfaenger: Empfaenger
|
||||
vertragsbezeichnung: string
|
||||
vertragsnummer: string | null
|
||||
/** Zu welchem Tag soll der Wert mitgeteilt werden? */
|
||||
stichtag: Date
|
||||
/** Datum des Schreibens. */
|
||||
heute?: Date
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt das Anschreiben als Freitext.
|
||||
*
|
||||
* Abgefragt wird bewusst mehr als nur der Rueckkaufswert: Die
|
||||
* beitragsfreie Versicherungssumme ist die Alternative zur Kuendigung -
|
||||
* der Vertrag bleibt bestehen, es wird nur nichts mehr eingezahlt. Bei
|
||||
* einer Sterbegeldversicherung ist das haeufig die bessere Loesung als der
|
||||
* Rueckkauf, und ohne die Zahl laesst sich das nicht vergleichen.
|
||||
*/
|
||||
export function rueckkaufswertAnschreiben(anfrage: RueckkaufswertAnfrage): string {
|
||||
const heute = anfrage.heute ?? heuteAlsTag()
|
||||
const kopf = anfrage.kopf
|
||||
|
||||
return [
|
||||
absenderBlock(kopf),
|
||||
'',
|
||||
anschriftBlock(anfrage.empfaenger),
|
||||
'',
|
||||
datumsZeile(kopf.betreuerOrt, heute),
|
||||
'',
|
||||
betreffZeile(
|
||||
'Auskunft über Rückkaufswert und beitragsfreie Versicherungssumme',
|
||||
anfrage.vertragsnummer,
|
||||
kopf.betreuteName,
|
||||
kopf.betreuteGeburtsdatum,
|
||||
),
|
||||
'',
|
||||
'Sehr geehrte Damen und Herren,',
|
||||
'',
|
||||
bestellungsAbsatz(kopf),
|
||||
'',
|
||||
`für ein laufendes Verfahren auf Hilfe zur Pflege beim überörtlichen ` +
|
||||
`Sozialhilfeträger benötige ich zu dem oben genannten Vertrag ` +
|
||||
`(${anfrage.vertragsbezeichnung}) folgende Auskünfte, jeweils zum Stichtag ` +
|
||||
`${formatDatum(anfrage.stichtag)}:`,
|
||||
'',
|
||||
'1. den Rückkaufswert einschließlich bereits zugeteilter Überschussanteile,',
|
||||
'2. die beitragsfreie Versicherungssumme bei sofortiger Beitragsfreistellung,',
|
||||
'3. die Höhe der laufenden Beiträge und den Zahlungsrhythmus,',
|
||||
'4. Beginn, vereinbartes Ende und die Kündigungsfrist des Vertrages,',
|
||||
'5. ob der Vertrag abgetreten, verpfändet oder mit einem unwiderruflichen ' +
|
||||
'Bezugsrecht belegt ist,',
|
||||
'6. ob eine Verwertungsausschlussvereinbarung besteht.',
|
||||
'',
|
||||
`Ich bitte um schriftliche Mitteilung an die oben genannte Anschrift. Da im ` +
|
||||
`Verfahren beim Sozialhilfeträger eine Frist läuft, wäre ich für eine Antwort ` +
|
||||
`bis zum ${formatDatum(wiedervorlageAm(heute))} dankbar.`,
|
||||
'',
|
||||
'Für Rückfragen stehe ich gerne zur Verfügung.',
|
||||
'',
|
||||
grussformel(kopf.betreuerName),
|
||||
'',
|
||||
'Anlage: Kopie der Bestellungsurkunde',
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* Wie lange ist die letzte Anfrage her? Grundlage fuer den Hinweis
|
||||
* "Erinnerung faellig".
|
||||
*/
|
||||
export function anfrageStatus(
|
||||
angefragtAm: Date | null,
|
||||
heute: Date = heuteAlsTag(),
|
||||
): { offen: boolean; erinnerungFaellig: boolean; tageSeitAnfrage: number | null } {
|
||||
if (!angefragtAm) return { offen: false, erinnerungFaellig: false, tageSeitAnfrage: null }
|
||||
const tage = tageZwischen(angefragtAm, heute)
|
||||
return {
|
||||
offen: true,
|
||||
erinnerungFaellig: tage >= RUECKKAUFSWERT_WIEDERVORLAGE_TAGE,
|
||||
tageSeitAnfrage: tage,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user