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
131 lines
4.3 KiB
TypeScript
131 lines
4.3 KiB
TypeScript
import { alsTag, heute, jahrePlus, monatePlus, tagePlus, vorOderGleich } from '@/lib/finanzen/datum'
|
||
|
||
/**
|
||
* Das Betreuungsjahr.
|
||
*
|
||
* Es laeuft ab dem Tag der Bestellung, nicht ab dem 1. Januar. Wer am
|
||
* 15.03.2025 bestellt wurde, legt fuer den Zeitraum 15.03.2025 bis
|
||
* 14.03.2026 Rechnung (§ 1865 Abs. 1 BGB), und die Aufwandspauschale
|
||
* nach § 1878 BGB verfaellt sechs Monate nach dessen Ende.
|
||
*
|
||
* Das ist der haeufigste Rechenfehler in selbstgebauten Tabellen: man
|
||
* rechnet das Kalenderjahr ab und wundert sich, warum das Gericht
|
||
* nachfragt. Deshalb steht die Rechnung hier an genau einer Stelle.
|
||
*
|
||
* Schaltjahrfall: Ist die Bestellung am 29.02. erfolgt, gibt es in
|
||
* Nicht-Schaltjahren keinen 29.02. Das Jahr endet dann am 27.02. und das
|
||
* folgende beginnt am 28.02. - so entsteht weder eine Luecke noch eine
|
||
* Ueberschneidung, und jeder Tag gehoert zu genau einem Betreuungsjahr.
|
||
*/
|
||
|
||
export type Betreuungsjahr = {
|
||
/** 1 = das erste Jahr ab Bestellung. */
|
||
nummer: number
|
||
/** Erster Tag, einschliesslich. */
|
||
von: Date
|
||
/** Letzter Tag, einschliesslich. */
|
||
bis: Date
|
||
/** "1. Betreuungsjahr (15.03.2025 – 14.03.2026)" - fuer Auswahllisten. */
|
||
bezeichnung: string
|
||
}
|
||
|
||
function formatKurz(datum: Date): string {
|
||
const tag = String(datum.getUTCDate()).padStart(2, '0')
|
||
const monat = String(datum.getUTCMonth() + 1).padStart(2, '0')
|
||
return `${tag}.${monat}.${datum.getUTCFullYear()}`
|
||
}
|
||
|
||
function bauen(nummer: number, von: Date, bis: Date): Betreuungsjahr {
|
||
return {
|
||
nummer,
|
||
von,
|
||
bis,
|
||
bezeichnung: `${nummer}. Betreuungsjahr (${formatKurz(von)} – ${formatKurz(bis)})`,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Das n-te Betreuungsjahr. nummer ist 1-basiert; 1 ist das Jahr, das am
|
||
* Tag der Bestellung beginnt.
|
||
*/
|
||
export function betreuungsjahr(bestellungAm: Date, nummer: number): Betreuungsjahr {
|
||
if (!Number.isInteger(nummer) || nummer < 1) {
|
||
throw new Error('Das Betreuungsjahr wird ab 1 gezählt.')
|
||
}
|
||
const start = alsTag(bestellungAm)
|
||
const von = jahrePlus(start, nummer - 1)
|
||
const bis = tagePlus(jahrePlus(start, nummer), -1)
|
||
return bauen(nummer, von, bis)
|
||
}
|
||
|
||
/**
|
||
* In welches Betreuungsjahr faellt ein Datum? Null, wenn es vor der
|
||
* Bestellung liegt - dafuer gibt es keine Rechnungslegung.
|
||
*/
|
||
export function betreuungsjahrZuDatum(bestellungAm: Date, datum: Date): Betreuungsjahr | null {
|
||
const start = alsTag(bestellungAm)
|
||
const ziel = alsTag(datum)
|
||
if (ziel.getTime() < start.getTime()) return null
|
||
|
||
// Grobe Schaetzung ueber die Jahresdifferenz, danach hoechstens zwei
|
||
// Korrekturschritte - das haelt auch den Schaltjahrfall aus.
|
||
let nummer = ziel.getUTCFullYear() - start.getUTCFullYear() + 1
|
||
if (nummer < 1) nummer = 1
|
||
|
||
for (let versuch = 0; versuch < 4; versuch++) {
|
||
const jahr = betreuungsjahr(start, nummer)
|
||
if (ziel.getTime() < jahr.von.getTime()) {
|
||
nummer -= 1
|
||
if (nummer < 1) return null
|
||
continue
|
||
}
|
||
if (ziel.getTime() > jahr.bis.getTime()) {
|
||
nummer += 1
|
||
continue
|
||
}
|
||
return jahr
|
||
}
|
||
return null
|
||
}
|
||
|
||
/** Das Betreuungsjahr, in dem wir heute stehen. */
|
||
export function laufendesBetreuungsjahr(
|
||
bestellungAm: Date,
|
||
stichtag: Date = heute(),
|
||
): Betreuungsjahr | null {
|
||
return betreuungsjahrZuDatum(bestellungAm, stichtag)
|
||
}
|
||
|
||
/**
|
||
* Alle Betreuungsjahre von der Bestellung bis einschliesslich des Jahres,
|
||
* in dem "bis" liegt. Fuer die Auswahlliste im Berichtsformular - das
|
||
* laufende Jahr steht mit dabei, denn eine Zwischenaufstellung ist
|
||
* jederzeit erlaubt.
|
||
*/
|
||
export function betreuungsjahre(bestellungAm: Date, bis: Date = heute()): Betreuungsjahr[] {
|
||
const letztes = betreuungsjahrZuDatum(bestellungAm, bis)
|
||
if (!letztes) return []
|
||
const liste: Betreuungsjahr[] = []
|
||
for (let nummer = 1; nummer <= letztes.nummer; nummer++) {
|
||
liste.push(betreuungsjahr(bestellungAm, nummer))
|
||
}
|
||
return liste
|
||
}
|
||
|
||
/**
|
||
* Ist das Betreuungsjahr abgeschlossen? Nur dann ist die Rechnungslegung
|
||
* faellig; vorher ist der Bericht eine Zwischenaufstellung.
|
||
*/
|
||
export function jahrAbgeschlossen(jahr: Betreuungsjahr, stichtag: Date = heute()): boolean {
|
||
return vorOderGleich(jahr.bis, stichtag)
|
||
}
|
||
|
||
/**
|
||
* Ende der Ausschlussfrist fuer die Aufwandspauschale nach § 1878 BGB:
|
||
* sechs Monate nach dem Ende des Betreuungsjahres. Danach ist der
|
||
* Anspruch weg - kein Ermessen, keine Nachsicht.
|
||
*/
|
||
export function aufwandspauschaleBis(jahr: Betreuungsjahr): Date {
|
||
return monatePlus(jahr.bis, 6)
|
||
}
|