import { z } from 'zod' /** * Eingabepruefung fuer die Stammdaten der betreuten Person. * * Die Pruefung laeuft IMMER serverseitig - das Formular im Browser ist nur * Bequemlichkeit. Alle Meldungen sind auf Deutsch und sagen, was zu tun * ist, nicht was falsch war. */ /** * Optionales Textfeld. * * Drei Eingaben muessen zu null werden: * "" das Feld war da, blieb aber leer * null ausdruecklich geleert * undefined das Feld war im Formular ueberhaupt nicht dabei * * Der letzte Fall ist der wichtige: Das kurze Formular der Ersteinrichtung * schickt nur eine Handvoll Felder, das vollstaendige unter Stammdaten * alle. Beide gehen durch dasselbe Schema. Ohne .optional() wuerde die * Ersteinrichtung an zwei Dutzend Feldern scheitern, die gar nicht auf dem * Bildschirm stehen - und die Meldung liesse sich keinem Feld zuordnen. */ const optionalerText = (max = 200) => z .string() .trim() .max(max, `Bitte höchstens ${max} Zeichen.`) .optional() .nullable() .transform((wert) => (wert === undefined || wert === '' ? null : wert)) /** HTML-Datumsfelder liefern "" oder "2026-01-15" - oder gar nichts. */ const optionalesDatum = z .string() .trim() .optional() .nullable() .transform((wert) => (wert === undefined || wert === '' ? null : wert)) .refine((wert) => wert === null || /^\d{4}-\d{2}-\d{2}$/.test(wert), { message: 'Bitte ein Datum im Format TT.MM.JJJJ wählen.', }) .transform((wert) => (wert === null ? null : new Date(`${wert}T00:00:00.000Z`))) const pflichtDatum = z .string() .trim() .min(1, 'Bitte ein Datum angeben.') .refine((wert) => /^\d{4}-\d{2}-\d{2}$/.test(wert), { message: 'Bitte ein Datum im Format TT.MM.JJJJ wählen.', }) .transform((wert) => new Date(`${wert}T00:00:00.000Z`)) export const AUFGABENKREISE = [ 'VERMOEGENSSORGE', 'GESUNDHEITSSORGE', 'AUFENTHALTSBESTIMMUNG', 'WOHNUNGSANGELEGENHEITEN', 'BEHOERDENANGELEGENHEITEN', 'POST_UND_FERNMELDEVERKEHR', 'RECHTS_ANTRAGS_UND_BEHOERDENVERTRETUNG', 'ENTSCHEIDUNG_UEBER_FREIHEITSENTZIEHENDE_MASSNAHMEN', 'SONSTIGES', ] as const export const AUFGABENKREIS_BESCHRIFTUNG: Record<(typeof AUFGABENKREISE)[number], string> = { VERMOEGENSSORGE: 'Vermögenssorge', GESUNDHEITSSORGE: 'Gesundheitssorge', AUFENTHALTSBESTIMMUNG: 'Aufenthaltsbestimmung', WOHNUNGSANGELEGENHEITEN: 'Wohnungsangelegenheiten', BEHOERDENANGELEGENHEITEN: 'Behördenangelegenheiten', POST_UND_FERNMELDEVERKEHR: 'Post- und Fernmeldeverkehr', RECHTS_ANTRAGS_UND_BEHOERDENVERTRETUNG: 'Rechts-, Antrags- und Behördenvertretung', ENTSCHEIDUNG_UEBER_FREIHEITSENTZIEHENDE_MASSNAHMEN: 'Entscheidung über freiheitsentziehende Maßnahmen', SONSTIGES: 'Sonstiges', } export const BetreuteSchema = z.object({ // --- Person --------------------------------------------------------------- anrede: optionalerText(20), vorname: z.string().trim().min(1, 'Bitte den Vornamen angeben.').max(100), nachname: z.string().trim().min(1, 'Bitte den Nachnamen angeben.').max(100), geburtsname: optionalerText(100), geburtsdatum: pflichtDatum.refine((datum) => datum <= new Date(), { message: 'Das Geburtsdatum kann nicht in der Zukunft liegen.', }), geburtsort: optionalerText(100), // --- Heim ----------------------------------------------------------------- heimName: optionalerText(150), heimStrasse: optionalerText(150), heimPlz: optionalerText(10), heimOrt: optionalerText(100), heimZimmer: optionalerText(50), heimTelefon: optionalerText(50), heimEinzugAm: optionalesDatum, fruehereStrasse: optionalerText(150), frueherePlz: optionalerText(10), fruehererOrt: optionalerText(100), // --- Gesundheit (verschluesselt gespeichert) ------------------------------ krankenkasse: optionalerText(150), krankenversichertennummer: optionalerText(50), pflegekasse: optionalerText(150), pflegegrad: z.coerce .number() .int() .min(1, 'Der Pflegegrad liegt zwischen 1 und 5.') .max(5, 'Der Pflegegrad liegt zwischen 1 und 5.') .nullable() .catch(null), pflegegradSeit: optionalesDatum, pflegegradBescheidVom: optionalesDatum, schwerbehinderungGdB: z.coerce .number() .int() .min(20, 'Ein Grad der Behinderung wird ab 20 festgestellt.') .max(100, 'Der Grad der Behinderung geht bis 100.') .nullable() .catch(null), schwerbehinderungMerkzeichen: optionalerText(50), diagnosen: optionalerText(4000), ermittelterWille: optionalerText(4000), patientenverfuegungVorhanden: z.coerce.boolean().default(false), vorsorgevollmachtVorhanden: z.coerce.boolean().default(false), // --- Betreuungsgericht ---------------------------------------------------- betreuungsgericht: optionalerText(150), aktenzeichenGericht: optionalerText(80), bestellungAm: optionalesDatum, bestellungBis: optionalesDatum, aufgabenkreise: z.array(z.enum(AUFGABENKREISE)).default([]), aufgabenkreiseFreitext: optionalerText(500), befreiterBetreuer: z.coerce.boolean().default(true), ehrenamtlich: z.coerce.boolean().default(true), // --- Sozialleistungstraeger ---------------------------------------------- aktenzeichenBezirk: optionalerText(80), aktenzeichenSozialamt: optionalerText(80), rentenversicherungsnummer: optionalerText(30), steuerId: optionalerText(20), notizen: optionalerText(4000), }) export type BetreuteEingabe = z.infer /** * Diese Felder wandern verschluesselt in die Datenbank. Der Name ohne * Suffix ist der Klartext im Formular, mit Suffix der Umschlag. */ export const BETREUTE_GEHEIME_FELDER = [ 'krankenversichertennummer', 'diagnosen', 'ermittelterWille', 'rentenversicherungsnummer', 'steuerId', 'notizen', ] as const /** * Liest ein FormData-Objekt in die Struktur, die das Schema erwartet. * Kontrollkaestchen liefern "on" oder gar nichts - beides wird hier * in einen echten Wahrheitswert uebersetzt. */ export function formularLesen(formular: FormData): Record { const roh: Record = {} for (const [name, wert] of formular.entries()) { if (name === 'aufgabenkreise') continue roh[name] = wert } roh.aufgabenkreise = formular.getAll('aufgabenkreise').map(String) roh.patientenverfuegungVorhanden = formular.get('patientenverfuegungVorhanden') === 'on' roh.vorsorgevollmachtVorhanden = formular.get('vorsorgevollmachtVorhanden') === 'on' roh.befreiterBetreuer = formular.get('befreiterBetreuer') === 'on' roh.ehrenamtlich = formular.get('ehrenamtlich') === 'on' // Leere Zahlenfelder duerfen nicht als "" ins Schema. for (const feld of ['pflegegrad', 'schwerbehinderungGdB']) { if (roh[feld] === '') roh[feld] = null } return roh }