fix: Ersteinrichtung liess sich nicht abschliessen, Betreute nicht loeschen

Zwei Fehler, beide erst im Betrieb sichtbar.

1. Schritt 3 der Ersteinrichtung meldete "Bitte pruefe die markierten
   Felder" - und markierte keines. Man kam dort nicht weiter.

   Das kurze Formular schickt neun Felder, das vollstaendige unter
   Stammdaten alle. Beide gehen durch dasselbe Schema, und die optionalen
   Felder waren zwar nullable, aber nicht optional: Ein Feld, das gar
   nicht abgeschickt wurde, ist undefined, nicht null. Beanstandet wurden
   also zwei Dutzend Felder, die auf dem Bildschirm nie standen.

   Damit das nicht wiederkommt, unterscheidet die Aktion jetzt zwischen
   Eingabefehler und Formular-Schema-Bruch: Beanstandet die Pruefung ein
   Feld, das nicht abgeschickt wurde, sagt sie das ausdruecklich und
   schreibt es ins Serverlog, statt es dem Nutzer als Eingabefehler
   unterzuschieben, den er nirgends finden kann.

2. Eine betreute Person liess sich nicht endgueltig loeschen. Postgres
   setzt beim Loeschen den Verweis im Audit-Log auf NULL, und genau
   dieses UPDATE hat der Unveraenderlichkeits-Trigger abgewiesen. Damit
   scheiterte auch das Leeren des Papierkorbs nach 30 Tagen.

   Der Trigger erlaubt jetzt genau eine Ausnahme: das Nullen der beiden
   Fremdschluessel. Jede andere Aenderung bleibt verboten, auch das
   Setzen eines Fremdschluessels auf einen anderen Wert. Der Nachweis
   bleibt vollstaendig - es verschwindet nur der Verweis auf einen
   Datensatz, den es nicht mehr gibt.

Geprueft: sieben neue Tests gegen die tatsaechlichen Formularinhalte, der
Ablauf einmal komplett im Browser bis zum Dashboard, und der Trigger
gegen eine laufende Datenbank - Loeschen gelingt, Faelschen scheitert.

Etappe: 8
Phase: Haertung
This commit is contained in:
Florian Wilde
2026-08-22 14:36:18 +02:00
parent 9365645f72
commit 7780739a1a
7 changed files with 296 additions and 21 deletions
+45 -14
View File
@@ -53,6 +53,49 @@ function felderAusFehler(fehler: z.ZodError): Record<string, string> {
return ergebnis
}
/**
* Baut die Antwort auf einen Pruefungsfehler.
*
* Der wichtige Teil ist die Unterscheidung: Beanstandet die Pruefung ein
* Feld, das gar nicht abgeschickt wurde, dann passt das Formular nicht zum
* Schema - und die Meldung liesse sich auf dem Bildschirm keinem Feld
* zuordnen. Der Nutzer saehe "Bitte pruefe die markierten Felder", ohne
* dass irgendetwas markiert waere, und kaeme nie weiter.
*
* Das ist ein Fehler im Programm, keiner in der Eingabe. Er wird deshalb
* benannt und ins Serverlog geschrieben, statt ihn dem Nutzer als
* Eingabefehler unterzuschieben.
*/
function antwortAufFehler(fehler: z.ZodError, formular: FormData): BetreuteZustand {
const felder = felderAusFehler(fehler)
const gesendet = new Set(formular.keys())
const nichtZuordenbar = Object.entries(felder).filter(([feld]) => !gesendet.has(feld))
if (nichtZuordenbar.length > 0) {
const liste = nichtZuordenbar.map(([feld, meldung]) => `${feld}: ${meldung}`).join('; ')
console.error(
`[stammdaten] Das Formular passt nicht zum Schema. Beanstandet wurden ` +
`Felder, die es gar nicht abgeschickt hat: ${liste}`,
)
return {
fehler:
'Das Formular passt nicht zur Prüfung — betroffen sind Felder, die ' +
'hier gar nicht stehen: ' +
nichtZuordenbar.map(([feld]) => feld).join(', ') +
'. Das ist ein Fehler in der Anwendung, nicht in deiner Eingabe. ' +
'Einzelheiten stehen im Serverlog.',
felder,
werte: werteMerken(formular),
}
}
return {
fehler: 'Bitte prüfe die markierten Felder.',
felder,
werte: werteMerken(formular),
}
}
function werteMerken(formular: FormData): Record<string, string> {
const werte: Record<string, string> = {}
for (const [name, wert] of formular.entries()) {
@@ -77,13 +120,7 @@ export async function betreuteAnlegen(
}
const gelesen = BetreuteSchema.safeParse(formularLesen(formular))
if (!gelesen.success) {
return {
fehler: 'Bitte prüfe die markierten Felder.',
felder: felderAusFehler(gelesen.error),
werte: werteMerken(formular),
}
}
if (!gelesen.success) return antwortAufFehler(gelesen.error, formular)
const { offen, verschluesselt } = aufteilen(gelesen.data)
@@ -134,13 +171,7 @@ export async function betreuteAendern(
const { benutzer, betreute } = await erfordereZugang(betreuteId, 'STAMMDATEN', 'schreiben')
const gelesen = BetreuteSchema.safeParse(formularLesen(formular))
if (!gelesen.success) {
return {
fehler: 'Bitte prüfe die markierten Felder.',
felder: felderAusFehler(gelesen.error),
werte: werteMerken(formular),
}
}
if (!gelesen.success) return antwortAufFehler(gelesen.error, formular)
const { offen, verschluesselt } = aufteilen(gelesen.data)