Files
Laufzettel/src/server/actions/einstellungen.ts
T
florian.wildeandClaude Opus 5 ecc3c579df
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(mandant): Betriebsmodus, Kundenverwaltung und Einstellungen (#22)
Zwei Betriebsmodi, ein Codepfad. src/server/mandant.ts ist die eine Stelle,
an der die Mandantenregel steht.

Der Review fand drei Blocker: die Lesemodelle wandten die Mandantensicht nicht
an, der Protokolleintrag des Moduswechsels stand außerhalb der Transaktion, und
Prüfung und Vollzug lagen so weit auseinander, dass zwei gleichzeitige Wechsel
beide durchliefen. Alle drei geschlossen, der letzte mit einem Advisory Lock.

336 Tests.

Closes #8

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 05:03:11 +00:00

107 lines
3.6 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { Betriebsmodus } from "@/generated/prisma/enums";
import {
EinstellungsFehler,
NamensregelnSchema,
einstellungenSpeichern,
modusWechseln,
} from "@/server/services/einstellungen";
import { rechtErforderlich } from "@/server/auth/sitzungspflicht";
/** Server Actions der Einstellungen (#008). Recht prüfen, Zod, Service rufen. */
export type AktionsErgebnis = { readonly fehler?: string; readonly erfolg?: string };
const EinstellungsSchema = z.object({
eigeneOrganisationName: z.string().trim().min(1, "Die eigene Organisation braucht einen Namen."),
gesperrteOu: z.string().trim().optional(),
namensregeln: NamensregelnSchema,
});
export async function einstellungenSpeichernAktion(
_vorher: AktionsErgebnis,
formular: FormData,
): Promise<AktionsErgebnis> {
const { benutzer } = await rechtErforderlich("verwaltung");
const eingabe = EinstellungsSchema.safeParse({
eigeneOrganisationName: formular.get("eigeneOrganisationName"),
gesperrteOu: formular.get("gesperrteOu") ?? undefined,
namensregeln: {
upn: formular.get("regel_upn") ?? undefined,
sam: formular.get("regel_sam") ?? undefined,
mail: formular.get("regel_mail") ?? undefined,
hostname: formular.get("regel_hostname") ?? undefined,
},
});
if (!eingabe.success) {
return { fehler: eingabe.error.issues[0]?.message ?? "Die Eingabe ist unvollständig." };
}
try {
await einstellungenSpeichern(
{
eigeneOrganisationName: eingabe.data.eigeneOrganisationName,
namensregeln: eingabe.data.namensregeln,
gesperrteOu: eingabe.data.gesperrteOu ?? null,
},
benutzer.id,
);
revalidatePath("/admin");
return { erfolg: "Gespeichert." };
} catch (fehler) {
if (fehler instanceof EinstellungsFehler) return { fehler: fehler.message };
console.error("Einstellungen speichern fehlgeschlagen:", fehler);
return { fehler: "Die Einstellungen konnten nicht gespeichert werden." };
}
}
const WechselSchema = z.object({
ziel: z.enum(Betriebsmodus),
// Der Bestaetigungsdialog verlangt ein ausdrueckliches Haekchen: der Wechsel
// aendert, was die ganze Anwendung zeigt.
bestaetigt: z.literal("ja", { message: "Bitte den Wechsel ausdrücklich bestätigen." }),
name: z.string().trim().optional(),
kuerzel: z.string().trim().optional(),
});
export async function modusWechselnAktion(
_vorher: AktionsErgebnis,
formular: FormData,
): Promise<AktionsErgebnis> {
const { benutzer } = await rechtErforderlich("verwaltung");
const eingabe = WechselSchema.safeParse({
ziel: formular.get("ziel"),
bestaetigt: formular.get("bestaetigt"),
name: formular.get("name") ?? undefined,
kuerzel: formular.get("kuerzel") ?? undefined,
});
if (!eingabe.success) {
return { fehler: eingabe.error.issues[0]?.message ?? "Die Eingabe ist unvollständig." };
}
try {
await modusWechseln(
eingabe.data.ziel,
{ name: eingabe.data.name, kuerzel: eingabe.data.kuerzel },
benutzer.id,
);
// Der Modus aendert Navigation und Beschriftungen ueberall.
revalidatePath("/", "layout");
return {
erfolg:
eingabe.data.ziel === Betriebsmodus.DIENSTLEISTUNG
? "Der Betriebsmodus ist jetzt Dienstleistung. Kunden sind ab sofort eine sichtbare Ebene."
: "Der Betriebsmodus ist jetzt Intern. Die Kundenebene ist ausgeblendet.",
};
} catch (fehler) {
if (fehler instanceof EinstellungsFehler) return { fehler: fehler.message };
console.error("Moduswechsel fehlgeschlagen:", fehler);
return { fehler: "Der Moduswechsel ist fehlgeschlagen." };
}
}