'use server' import { revalidatePath } from 'next/cache' import { redirect } from 'next/navigation' import type { GesundheitsArt } from '@prisma/client' import { db, nichtGeloescht } from '@/lib/db' import { erfordereZugang, ZugriffVerweigert } from '@/lib/auth/sitzung' import { aenderungErmitteln, protokollieren } from '@/lib/audit' import { oeffnen, sealen } from '@/lib/crypto/feld' import { formatDatum } from '@/lib/utils' import { detailsFormatieren, detailfelderFuer } from '@/lib/pflege/ereignis-details' import { PFLEGEGRAD_RECHTSGRUNDLAGE_BEGUTACHTUNG } from '@/lib/pflege/konstanten' import { ErmittelterWilleSchema, GesundheitsereignisSchema, HeimvertragSchema, HoeherstufungSchema, } from '@/lib/validierung/pflege' import { formularLesen, zodFehler, type Formularzustand } from '@/lib/validierung/substanz' /** * Server Actions fuer Heim, Pflege und Gesundheitsverlauf. * * Gesundheitsdaten sind besondere Kategorien nach Art. 9 DSGVO. Jeder * Freitext wandert deshalb ueber sealen('Gesundheitsereignis', ...) in die * Datenbank, und im Audit-Log steht nur der Titel, nie die Beschreibung. */ const MODUL = 'GESUNDHEIT' as const function alsFehler(fehler: unknown): Formularzustand { if (fehler instanceof ZugriffVerweigert) return { fehler: fehler.message } console.error('[pflege]', fehler) return { fehler: 'Das Speichern hat nicht geklappt. Bitte noch einmal versuchen.' } } // ----------------------------------------------------------------------------- // Heimvertrag // ----------------------------------------------------------------------------- export async function heimvertragSpeichern( _vorher: Formularzustand, formular: FormData, ): Promise { const betreuteId = String(formular.get('betreuteId') ?? '') let ziel: string | null = null try { const { benutzer } = await erfordereZugang(betreuteId, MODUL, 'schreiben') const gelesen = HeimvertragSchema.safeParse(formularLesen(formular)) if (!gelesen.success) return zodFehler(gelesen.error) const daten = gelesen.data const felder = { einrichtung: daten.einrichtung, traeger: daten.traeger, strasse: daten.strasse, plz: daten.plz, ort: daten.ort, vertragAm: daten.vertragAm, einzugAm: daten.einzugAm, auszugAm: daten.auszugAm, kuendigungsfristTage: daten.kuendigungsfristTage, pflegeEigenanteilCent: daten.pflegeEigenanteil, unterkunftVerpflegungCent: daten.unterkunftVerpflegung, investitionskostenCent: daten.investitionskosten, ausbildungsumlageCent: daten.ausbildungsumlage, ...sealen('Heimvertrag', { notizen: daten.notizen }), } const vorher = await db.heimvertrag.findUnique({ where: { betreuteId } }) const nachher = await db.heimvertrag.upsert({ where: { betreuteId }, create: { betreuteId, ...felder }, update: felder, }) await protokollieren({ aktion: vorher ? 'GEAENDERT' : 'ERSTELLT', entitaet: 'Heimvertrag', entitaetId: nachher.id, bezeichnung: nachher.einrichtung, betreuteId, userId: benutzer.id, userEmail: benutzer.email, userName: benutzer.name, aenderung: vorher ? aenderungErmitteln(oeffnen('Heimvertrag', vorher), oeffnen('Heimvertrag', nachher)) : undefined, }) revalidatePath('/pflege') ziel = '/pflege' } catch (fehler) { return alsFehler(fehler) } redirect(ziel) } // ----------------------------------------------------------------------------- // Gesundheitsverlauf // ----------------------------------------------------------------------------- /** * Liest die Zusatzfelder der Schnellerfassung aus dem Formular. Sie * heissen dort "detail_" und werden zusammen mit dem Freitext zu * einer Beschreibung zusammengesetzt, die anschliessend verschluesselt * wird. */ function detailwerteLesen(formular: FormData, art: GesundheitsArt): Record { const werte: Record = {} for (const feld of detailfelderFuer(art)) { const roh = formular.get(`detail_${feld.name}`) if (typeof roh !== 'string') continue const wert = roh.trim() if (wert === '') continue werte[feld.name] = feld.typ === 'jaNein' ? (wert === 'on' ? 'ja' : wert) : wert } return werte } export async function gesundheitsereignisSpeichern( _vorher: Formularzustand, formular: FormData, ): Promise { const betreuteId = String(formular.get('betreuteId') ?? '') const ereignisId = String(formular.get('ereignisId') ?? '') let ziel: string | null = null try { const { benutzer } = await erfordereZugang(betreuteId, MODUL, 'schreiben') const gelesen = GesundheitsereignisSchema.safeParse(formularLesen(formular)) if (!gelesen.success) return zodFehler(gelesen.error) const daten = gelesen.data const beschreibung = detailsFormatieren( daten.art, detailwerteLesen(formular, daten.art), daten.beschreibung, ) const felder = { datum: daten.datum, bisDatum: daten.bisDatum, art: daten.art, titel: daten.titel, behandler: daten.behandler, einrichtung: daten.einrichtung, abwesenheitstage: daten.abwesenheitstage, ...sealen('Gesundheitsereignis', { beschreibung }), } if (ereignisId) { const vorhanden = await db.gesundheitsereignis.findFirst({ where: { id: ereignisId, betreuteId, ...nichtGeloescht }, }) if (!vorhanden) return { fehler: 'Diesen Eintrag gibt es nicht.' } const nachher = await db.gesundheitsereignis.update({ where: { id: ereignisId }, data: felder, }) await protokollieren({ aktion: 'GEAENDERT', entitaet: 'Gesundheitsereignis', entitaetId: ereignisId, bezeichnung: nachher.titel, betreuteId, userId: benutzer.id, userEmail: benutzer.email, userName: benutzer.name, // Bewusst ohne die Beschreibung: Diagnosen gehoeren nicht ins Log, // auch nicht verschluesselt. aenderung: aenderungErmitteln( { datum: vorhanden.datum, bisDatum: vorhanden.bisDatum, art: vorhanden.art, titel: vorhanden.titel, abwesenheitstage: vorhanden.abwesenheitstage }, { datum: nachher.datum, bisDatum: nachher.bisDatum, art: nachher.art, titel: nachher.titel, abwesenheitstage: nachher.abwesenheitstage }, ), }) } else { const neu = await db.gesundheitsereignis.create({ data: { betreuteId, ...felder } }) await protokollieren({ aktion: 'ERSTELLT', entitaet: 'Gesundheitsereignis', entitaetId: neu.id, bezeichnung: neu.titel, betreuteId, userId: benutzer.id, userEmail: benutzer.email, userName: benutzer.name, }) } revalidatePath('/pflege/verlauf') revalidatePath('/pflege') ziel = '/pflege/verlauf' } catch (fehler) { return alsFehler(fehler) } redirect(ziel) } export async function gesundheitsereignisLoeschen( betreuteId: string, ereignisId: string, ): Promise { const { benutzer } = await erfordereZugang(betreuteId, MODUL, 'schreiben') const ereignis = await db.gesundheitsereignis.findFirst({ where: { id: ereignisId, betreuteId, ...nichtGeloescht }, }) if (!ereignis) throw new ZugriffVerweigert('Diesen Eintrag gibt es nicht.') await db.gesundheitsereignis.update({ where: { id: ereignisId }, data: { geloeschtAm: new Date() }, }) await protokollieren({ aktion: 'GELOESCHT', entitaet: 'Gesundheitsereignis', entitaetId: ereignisId, bezeichnung: ereignis.titel, betreuteId, userId: benutzer.id, userEmail: benutzer.email, userName: benutzer.name, }) revalidatePath('/pflege/verlauf') } // ----------------------------------------------------------------------------- // Ermittelter Wille (§ 1821 BGB) // ----------------------------------------------------------------------------- /** * Der nach § 1821 BGB zu ermittelnde Wille der betreuten Person. * * Seit der Reform 2023 ist er die Leitschnur des Betreuerhandelns: Der * Betreuer hat den Wuenschen der betreuten Person zu entsprechen, auch * wenn er sie fuer unvernuenftig haelt - Grenze ist die erhebliche Gefahr * fuer Person oder Vermoegen. Deshalb gehoert dieses Feld gepflegt und * nicht einmal zu Beginn ausgefuellt und dann vergessen. */ export async function ermitteltenWillenSpeichern( _vorher: Formularzustand, formular: FormData, ): Promise { const betreuteId = String(formular.get('betreuteId') ?? '') try { const { benutzer } = await erfordereZugang(betreuteId, MODUL, 'schreiben') const gelesen = ErmittelterWilleSchema.safeParse(formularLesen(formular)) if (!gelesen.success) return zodFehler(gelesen.error) await db.betreute.update({ where: { id: betreuteId }, data: sealen('Betreute', { ermittelterWille: gelesen.data.ermittelterWille }), }) await protokollieren({ aktion: 'GEAENDERT', entitaet: 'Betreute', entitaetId: betreuteId, bezeichnung: 'Ermittelter Wille (§ 1821 BGB)', betreuteId, userId: benutzer.id, userEmail: benutzer.email, userName: benutzer.name, }) revalidatePath('/pflege/wille') return { erfolg: 'Gespeichert.' } } catch (fehler) { return alsFehler(fehler) } } // ----------------------------------------------------------------------------- // Pflegegrad: Termin fuer die Hoeherstufungspruefung // ----------------------------------------------------------------------------- /** * Setzt den Termin, an dem geprueft wird, ob ein Hoeherstufungsantrag * sinnvoll ist. Geschrieben wird eine Frist mit dem stabilen * Systemschluessel "pflegegrad-pruefung" - denselben, den das Fristenmodul * fuer diese Pflichtfrist benutzt. Deshalb upsert und nicht create: Es * soll genau eine solche Frist geben. */ export async function hoeherstufungsterminSetzen( _vorher: Formularzustand, formular: FormData, ): Promise { const betreuteId = String(formular.get('betreuteId') ?? '') try { const { benutzer, betreute } = await erfordereZugang(betreuteId, MODUL, 'schreiben') const gelesen = HoeherstufungSchema.safeParse(formularLesen(formular)) if (!gelesen.success) return zodFehler(gelesen.error) const beschreibung = (gelesen.data.begruendung ? `${gelesen.data.begruendung}\n\n` : '') + `Aktueller Pflegegrad: ${betreute.pflegegrad ?? 'nicht erfasst'}` + (betreute.pflegegradSeit ? ` (seit ${formatDatum(betreute.pflegegradSeit)})` : '') + `. Prüfen: Haben Exazerbationen, Krankenhaustage oder der Hilfebedarf ` + `zugenommen? Der Verlauf unter /pflege/verlauf liefert die Zahlen. ` + `Eine Verschlechterung rechtfertigt einen Antrag auf Neubegutachtung.` await db.frist.upsert({ where: { betreuteId_systemSchluessel: { betreuteId, systemSchluessel: 'pflegegrad-pruefung' }, }, create: { betreuteId, systemSchluessel: 'pflegegrad-pruefung', titel: 'Pflegegrad überprüfen — Höherstufung sinnvoll?', beschreibung, datum: gelesen.data.pruefungAm, typ: 'SELBST_GESETZT', rechtsgrundlage: PFLEGEGRAD_RECHTSGRUNDLAGE_BEGUTACHTUNG, vorwarnungTage: [30, 7], }, update: { datum: gelesen.data.pruefungAm, beschreibung, status: 'OFFEN', erledigtAm: null, }, }) await protokollieren({ aktion: 'GEAENDERT', entitaet: 'Frist', entitaetId: null, bezeichnung: `Pflegegradprüfung auf ${formatDatum(gelesen.data.pruefungAm)} gesetzt`, betreuteId, userId: benutzer.id, userEmail: benutzer.email, userName: benutzer.name, }) revalidatePath('/pflege') revalidatePath('/fristen') return { erfolg: `Termin auf den ${formatDatum(gelesen.data.pruefungAm)} gesetzt.` } } catch (fehler) { return alsFehler(fehler) } }