feat(stammdaten): Mitarbeiter, Abteilungen und Geraete (#25)
This commit was merged in pull request #25.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "@/lib/validierung";
|
||||
import { MandantFehler, type HandelnderBenutzer } from "@/server/mandant";
|
||||
import { RechteFehler, rechtErforderlich } from "@/server/auth/sitzungspflicht";
|
||||
import {
|
||||
AbteilungsFehler,
|
||||
abteilungAnlegen,
|
||||
abteilungArchivieren,
|
||||
abteilungBearbeiten,
|
||||
} from "@/server/services/abteilungen";
|
||||
|
||||
/**
|
||||
* Server Actions der Abteilungen (#011).
|
||||
*
|
||||
* Recht ist `verwaltung`: an einer Abteilung haengen Ziel-OU und
|
||||
* Standardgruppen — also das, was die spaeteren Befehle in ein Verzeichnis
|
||||
* schreiben. Ein Tippfehler dort legt ein Konto an der falschen Stelle an.
|
||||
*/
|
||||
|
||||
export type AktionsErgebnis = { readonly fehler?: string; readonly erfolg?: string };
|
||||
|
||||
const RECHTE_MELDUNG =
|
||||
"Deine Anmeldung gilt nicht mehr. Melde dich in einem zweiten Tab neu an und klicke dann hier " +
|
||||
"noch einmal — was du eingegeben hast, steht weiterhin auf dieser Seite.";
|
||||
|
||||
async function angemeldetOderMeldung(): Promise<
|
||||
{ readonly benutzer: HandelnderBenutzer } | { readonly fehler: string }
|
||||
> {
|
||||
try {
|
||||
const { benutzer } = await rechtErforderlich("verwaltung");
|
||||
return { benutzer: { id: benutzer.id, rolle: benutzer.rolle } };
|
||||
} catch (fehler) {
|
||||
if (fehler instanceof RechteFehler) return { fehler: RECHTE_MELDUNG };
|
||||
throw fehler;
|
||||
}
|
||||
}
|
||||
|
||||
function alsErgebnis(fehler: unknown, neutral: string): AktionsErgebnis {
|
||||
if (fehler instanceof AbteilungsFehler || fehler instanceof MandantFehler) {
|
||||
return { fehler: fehler.message };
|
||||
}
|
||||
console.error(`${neutral}:`, fehler instanceof Error ? `${fehler.name}: ${fehler.message}` : fehler);
|
||||
return { fehler: neutral };
|
||||
}
|
||||
|
||||
const AbteilungsSchema = z.object({
|
||||
name: z.string().trim().min(1, "Bitte einen Namen angeben."),
|
||||
zielOu: z.string().trim().optional(),
|
||||
standardGruppen: z.string().trim().optional(),
|
||||
mailDomain: z.string().trim().optional(),
|
||||
notizen: z.string().trim().optional(),
|
||||
kundeId: z.string().trim().optional(),
|
||||
});
|
||||
|
||||
function ausFormular(formular: FormData) {
|
||||
return AbteilungsSchema.safeParse({
|
||||
name: formular.get("name"),
|
||||
zielOu: formular.get("zielOu") ?? undefined,
|
||||
standardGruppen: formular.get("standardGruppen") ?? undefined,
|
||||
mailDomain: formular.get("mailDomain") ?? undefined,
|
||||
notizen: formular.get("notizen") ?? undefined,
|
||||
kundeId: formular.get("kundeId") ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/** Standardgruppen kommen als eine Zeile mit Kommas – so, wie sie im Befehl stehen. */
|
||||
function gruppenZerlegen(roh: string | undefined): string[] {
|
||||
return (roh ?? "")
|
||||
.split(",")
|
||||
.map((gruppe) => gruppe.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export async function abteilungAnlegenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const eingabe = ausFormular(formular);
|
||||
if (!eingabe.success) {
|
||||
return { fehler: eingabe.error.issues[0]?.message ?? "Die Eingabe ist unvollständig." };
|
||||
}
|
||||
|
||||
try {
|
||||
const abteilung = await abteilungAnlegen(
|
||||
{
|
||||
...eingabe.data,
|
||||
standardGruppen: gruppenZerlegen(eingabe.data.standardGruppen),
|
||||
kundeId: eingabe.data.kundeId || null,
|
||||
},
|
||||
zugang.benutzer,
|
||||
);
|
||||
revalidatePath("/admin/abteilungen");
|
||||
return { erfolg: `„${abteilung.name}“ wurde angelegt.` };
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Die Abteilung konnte nicht angelegt werden.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function abteilungBearbeitenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const abteilungId = z.string().min(1).safeParse(formular.get("abteilungId"));
|
||||
if (!abteilungId.success) return { fehler: "Es fehlt die Abteilung." };
|
||||
const eingabe = ausFormular(formular);
|
||||
if (!eingabe.success) {
|
||||
return { fehler: eingabe.error.issues[0]?.message ?? "Die Eingabe ist unvollständig." };
|
||||
}
|
||||
|
||||
try {
|
||||
await abteilungBearbeiten(
|
||||
abteilungId.data,
|
||||
{ ...eingabe.data, standardGruppen: gruppenZerlegen(eingabe.data.standardGruppen) },
|
||||
zugang.benutzer,
|
||||
);
|
||||
revalidatePath("/admin/abteilungen");
|
||||
return { erfolg: "Gespeichert." };
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Die Änderung konnte nicht gespeichert werden.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function abteilungArchivierenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const eingabe = z
|
||||
.object({ abteilungId: z.string().min(1), archiviert: z.enum(["true", "false"]) })
|
||||
.safeParse({ abteilungId: formular.get("abteilungId"), archiviert: formular.get("archiviert") });
|
||||
if (!eingabe.success) return { fehler: "Die Eingabe ist unvollständig." };
|
||||
|
||||
try {
|
||||
await abteilungArchivieren(eingabe.data.abteilungId, eingabe.data.archiviert === "true", zugang.benutzer);
|
||||
revalidatePath("/admin/abteilungen");
|
||||
return {};
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Die Änderung konnte nicht gespeichert werden.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { GeraeteTyp } from "@/generated/prisma/enums";
|
||||
import { z } from "@/lib/validierung";
|
||||
import { RechteFehler, rechtErforderlich } from "@/server/auth/sitzungspflicht";
|
||||
import { MandantFehler, kundenEbeneSichtbar, type HandelnderBenutzer } from "@/server/mandant";
|
||||
import {
|
||||
GeraeteFehler,
|
||||
geraeteImportieren,
|
||||
geraetAnlegen,
|
||||
geraetArchivieren,
|
||||
geraetBearbeiten,
|
||||
geraetLoeschen,
|
||||
type ImportAbweisung,
|
||||
} from "@/server/services/geraete";
|
||||
|
||||
/**
|
||||
* Server Actions der Geraete (#011).
|
||||
*
|
||||
* Duenn: Recht pruefen, Zod anwenden, Dienst rufen. Das Recht ist
|
||||
* `geraeteBearbeiten` — ADMIN, REDAKTEUR und TECHNIKER. Lesen darf jeder
|
||||
* Angemeldete, das prueft die Seite selbst.
|
||||
*/
|
||||
|
||||
export type AktionsErgebnis = {
|
||||
readonly fehler?: string;
|
||||
readonly erfolg?: string;
|
||||
/** Nur beim CSV-Import: je abgewiesene Zeile eine Nummer und ein Grund. */
|
||||
readonly abgewiesen?: readonly ImportAbweisung[];
|
||||
};
|
||||
|
||||
const RECHTE_MELDUNG =
|
||||
"Deine Anmeldung gilt nicht mehr. Melde dich in einem zweiten Tab neu an und klicke dann hier " +
|
||||
"noch einmal — was du eingegeben hast, steht weiterhin auf dieser Seite.";
|
||||
|
||||
/**
|
||||
* Die Rechtepruefung so, dass ein abgelaufenes Cookie kein Datenverlust ist.
|
||||
*
|
||||
* `rechtErforderlich` wirft. Ungefangen landet der Wurf in Nexts Fehlergrenze,
|
||||
* die Seite wird ausgehaengt — und bei einem Formular mit halb eingetippten
|
||||
* Stammdaten oder einer eingelesenen CSV-Datei ist das genau die Arbeit, die weg
|
||||
* ist. Befund aus dem Review von #010, geloest wie dort (siehe
|
||||
* src/server/actions/vorlagen.ts).
|
||||
*/
|
||||
async function angemeldetOderMeldung(
|
||||
recht: "geraeteBearbeiten" | "verwaltung" = "geraeteBearbeiten",
|
||||
): Promise<{ readonly benutzer: HandelnderBenutzer } | { readonly fehler: string }> {
|
||||
try {
|
||||
const { benutzer } = await rechtErforderlich(recht);
|
||||
// Die Dienste brauchen von der Sitzung nur Kennung und Rolle.
|
||||
return { benutzer: { id: benutzer.id, rolle: benutzer.rolle } };
|
||||
} catch (fehler) {
|
||||
if (fehler instanceof RechteFehler) return { fehler: RECHTE_MELDUNG };
|
||||
throw fehler;
|
||||
}
|
||||
}
|
||||
|
||||
/** Uebersetzt die Fehler der Dienste in eine Meldung, alles andere in eine neutrale. */
|
||||
function alsErgebnis(fehler: unknown, neutral: string): AktionsErgebnis {
|
||||
if (fehler instanceof GeraeteFehler || fehler instanceof MandantFehler) {
|
||||
return { fehler: fehler.message };
|
||||
}
|
||||
if (fehler instanceof RechteFehler) {
|
||||
return { fehler: RECHTE_MELDUNG };
|
||||
}
|
||||
console.error(`${neutral}:`, fehler instanceof Error ? `${fehler.name}: ${fehler.message}` : fehler);
|
||||
return { fehler: neutral };
|
||||
}
|
||||
|
||||
const TypSchema = z.enum(GeraeteTyp);
|
||||
|
||||
const AnlegenSchema = z.object({
|
||||
hostname: z.string().trim().optional(),
|
||||
bezeichnung: z.string().trim().optional(),
|
||||
seriennummer: z.string().trim().optional(),
|
||||
inventarnummer: z.string().trim().optional(),
|
||||
typ: TypSchema.optional(),
|
||||
modell: z.string().trim().optional(),
|
||||
notizen: z.string().trim().optional(),
|
||||
mitarbeiterId: z.string().trim().optional(),
|
||||
kundeId: z.string().trim().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Ein Feld, das das Formular nicht mitschickt, ist `undefined` und heisst „nicht
|
||||
* angefasst“. Ein mitgeschicktes leeres Feld heisst „geleert“ — beim Bearbeiten
|
||||
* macht das den Unterschied.
|
||||
*/
|
||||
function feld(formular: FormData, name: string): string | undefined {
|
||||
const wert = formular.get(name);
|
||||
return typeof wert === "string" ? wert : undefined;
|
||||
}
|
||||
|
||||
export async function geraetAnlegenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const eingabe = AnlegenSchema.safeParse({
|
||||
hostname: feld(formular, "hostname"),
|
||||
bezeichnung: feld(formular, "bezeichnung"),
|
||||
seriennummer: feld(formular, "seriennummer"),
|
||||
inventarnummer: feld(formular, "inventarnummer"),
|
||||
typ: feld(formular, "typ") || undefined,
|
||||
modell: feld(formular, "modell"),
|
||||
notizen: feld(formular, "notizen"),
|
||||
mitarbeiterId: feld(formular, "mitarbeiterId"),
|
||||
kundeId: feld(formular, "kundeId"),
|
||||
});
|
||||
if (!eingabe.success) {
|
||||
return { fehler: eingabe.error.issues[0]?.message ?? "Die Eingabe ist unvollständig." };
|
||||
}
|
||||
|
||||
// Eine Kundenwahl gibt es nur im Modus Dienstleistung; `kundeErmitteln`
|
||||
// verwirft eine mitgeschickte Kennung im Modus Intern ohnehin (#008).
|
||||
const kundeId = (await kundenEbeneSichtbar()) ? eingabe.data.kundeId || null : null;
|
||||
|
||||
try {
|
||||
const geraet = await geraetAnlegen(
|
||||
{
|
||||
hostname: eingabe.data.hostname ?? null,
|
||||
bezeichnung: eingabe.data.bezeichnung ?? null,
|
||||
seriennummer: eingabe.data.seriennummer ?? null,
|
||||
inventarnummer: eingabe.data.inventarnummer ?? null,
|
||||
...(eingabe.data.typ ? { typ: eingabe.data.typ } : {}),
|
||||
modell: eingabe.data.modell ?? null,
|
||||
notizen: eingabe.data.notizen ?? null,
|
||||
mitarbeiterId: eingabe.data.mitarbeiterId ?? null,
|
||||
kundeId,
|
||||
},
|
||||
zugang.benutzer,
|
||||
);
|
||||
revalidatePath("/geraete");
|
||||
return {
|
||||
erfolg: `„${geraet.hostname ?? geraet.bezeichnung ?? geraet.tagCode}“ wurde angelegt, Tag-Code ${geraet.tagCode}.`,
|
||||
};
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Das Gerät konnte nicht angelegt werden.");
|
||||
}
|
||||
}
|
||||
|
||||
const BearbeitenSchema = AnlegenSchema.extend({
|
||||
geraetId: z.string().min(1),
|
||||
/** Kommt als verstecktes Feld mit, damit ein Aenderungsversuch auffaellt. */
|
||||
tagCode: z.string().trim().optional(),
|
||||
});
|
||||
|
||||
export async function geraetBearbeitenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const eingabe = BearbeitenSchema.safeParse({
|
||||
geraetId: feld(formular, "geraetId"),
|
||||
hostname: feld(formular, "hostname"),
|
||||
bezeichnung: feld(formular, "bezeichnung"),
|
||||
seriennummer: feld(formular, "seriennummer"),
|
||||
inventarnummer: feld(formular, "inventarnummer"),
|
||||
typ: feld(formular, "typ") || undefined,
|
||||
modell: feld(formular, "modell"),
|
||||
notizen: feld(formular, "notizen"),
|
||||
mitarbeiterId: feld(formular, "mitarbeiterId"),
|
||||
tagCode: feld(formular, "tagCode"),
|
||||
});
|
||||
if (!eingabe.success) {
|
||||
return { fehler: eingabe.error.issues[0]?.message ?? "Die Eingabe ist unvollständig." };
|
||||
}
|
||||
|
||||
try {
|
||||
await geraetBearbeiten(
|
||||
eingabe.data.geraetId,
|
||||
{
|
||||
// Ein Feld, das das Formular nicht mitschickt, bleibt unberuehrt; ein
|
||||
// leeres Feld heisst „geleert“. Deshalb `?? null` und nicht `|| null`.
|
||||
...(eingabe.data.hostname === undefined ? {} : { hostname: eingabe.data.hostname }),
|
||||
...(eingabe.data.bezeichnung === undefined ? {} : { bezeichnung: eingabe.data.bezeichnung }),
|
||||
...(eingabe.data.seriennummer === undefined ? {} : { seriennummer: eingabe.data.seriennummer }),
|
||||
...(eingabe.data.inventarnummer === undefined ? {} : { inventarnummer: eingabe.data.inventarnummer }),
|
||||
...(eingabe.data.typ === undefined ? {} : { typ: eingabe.data.typ }),
|
||||
...(eingabe.data.modell === undefined ? {} : { modell: eingabe.data.modell }),
|
||||
...(eingabe.data.notizen === undefined ? {} : { notizen: eingabe.data.notizen }),
|
||||
...(eingabe.data.mitarbeiterId === undefined ? {} : { mitarbeiterId: eingabe.data.mitarbeiterId }),
|
||||
/*
|
||||
* Auch ein **leer** mitgeschickter tagCode ist ein Aenderungsversuch und
|
||||
* gehoert vor den Dienst. Mit `? :` auf den Wahrheitswert fiel er weg,
|
||||
* und damit lief genau das ins Leere, was `GeraeteAenderung.tagCode`
|
||||
* ausdruecklich sichtbar machen soll.
|
||||
*/
|
||||
...(eingabe.data.tagCode === undefined ? {} : { tagCode: eingabe.data.tagCode }),
|
||||
},
|
||||
zugang.benutzer,
|
||||
);
|
||||
revalidatePath("/geraete");
|
||||
return { erfolg: "Gespeichert." };
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Die Änderung konnte nicht gespeichert werden.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function geraetArchivierenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const eingabe = z
|
||||
.object({ geraetId: z.string().min(1), archiviert: z.enum(["true", "false"]) })
|
||||
.safeParse({ geraetId: feld(formular, "geraetId"), archiviert: feld(formular, "archiviert") });
|
||||
if (!eingabe.success) return { fehler: "Die Eingabe ist unvollständig." };
|
||||
|
||||
try {
|
||||
await geraetArchivieren(eingabe.data.geraetId, eingabe.data.archiviert === "true", zugang.benutzer);
|
||||
revalidatePath("/geraete");
|
||||
return {};
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Die Änderung konnte nicht gespeichert werden.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Endgueltig loeschen — nur `verwaltung`.
|
||||
*
|
||||
* Archivieren ist der Normalfall und steht jedem offen, der Geraete pflegt.
|
||||
* Loeschen dagegen ist nicht ruecknehmbar und verbrennt den Tag-Code fuer immer:
|
||||
* er kommt nach der Regel des Projekts nie zurueck, der gedruckte Aufkleber ist
|
||||
* damit wertlos. Gebraucht wird es nur fuer den Vertipper von eben — und dafuer
|
||||
* darf man den Administrator fragen. Abteilungen und Mitarbeiter haben aus
|
||||
* demselben Grund gar keinen Loeschweg.
|
||||
*/
|
||||
export async function geraetLoeschenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung("verwaltung");
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const geraetId = z.string().min(1).safeParse(feld(formular, "geraetId"));
|
||||
if (!geraetId.success) return { fehler: "Es fehlt das Gerät." };
|
||||
|
||||
try {
|
||||
await geraetLoeschen(geraetId.data, zugang.benutzer);
|
||||
revalidatePath("/geraete");
|
||||
return { erfolg: "Gelöscht. Der Tag-Code bleibt vergeben." };
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Das Gerät konnte nicht gelöscht werden.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function geraeteImportierenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const eingabe = z
|
||||
.object({
|
||||
inhalt: z.string().min(1, "Bitte eine CSV-Datei auswählen oder den Inhalt einfügen."),
|
||||
kundeId: z.string().trim().optional(),
|
||||
})
|
||||
.safeParse({ inhalt: feld(formular, "inhalt"), kundeId: feld(formular, "kundeId") });
|
||||
if (!eingabe.success) {
|
||||
return { fehler: eingabe.error.issues[0]?.message ?? "Die Eingabe ist unvollständig." };
|
||||
}
|
||||
|
||||
const kundeId = (await kundenEbeneSichtbar()) ? eingabe.data.kundeId || null : null;
|
||||
|
||||
try {
|
||||
const ergebnis = await geraeteImportieren(eingabe.data.inhalt, zugang.benutzer, kundeId);
|
||||
revalidatePath("/geraete");
|
||||
const teile = [
|
||||
`${ergebnis.angelegt} von ${ergebnis.gesamt} ${ergebnis.gesamt === 1 ? "Zeile" : "Zeilen"} angelegt`,
|
||||
];
|
||||
if (ergebnis.abgewiesen.length > 0) {
|
||||
teile.push(
|
||||
`${ergebnis.abgewiesen.length} ${ergebnis.abgewiesen.length === 1 ? "Zeile" : "Zeilen"} abgewiesen`,
|
||||
);
|
||||
}
|
||||
return { erfolg: `${teile.join(", ")}.`, abgewiesen: ergebnis.abgewiesen };
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Die Datei konnte nicht eingelesen werden.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "@/lib/validierung";
|
||||
import { MandantFehler } from "@/server/mandant";
|
||||
import { RechteFehler, rechtErforderlich } from "@/server/auth/sitzungspflicht";
|
||||
import {
|
||||
MitarbeiterFehler,
|
||||
mitarbeiterAktivSetzen,
|
||||
mitarbeiterAnlegen,
|
||||
mitarbeiterBearbeiten,
|
||||
} from "@/server/services/mitarbeiter";
|
||||
import type { HandelnderBenutzer } from "@/server/mandant";
|
||||
|
||||
/**
|
||||
* Server Actions der Mitarbeiter (#011).
|
||||
*
|
||||
* Duenn: Recht pruefen, Zod anwenden, Dienst rufen. Das Recht ist
|
||||
* `mitarbeiterBearbeiten` — dieselben Rollen wie bei den Geraeten, weil der
|
||||
* Techniker beim Onboarding vor der Person steht, bevor der naechste
|
||||
* Verzeichnisabgleich gelaufen ist.
|
||||
*/
|
||||
|
||||
export type AktionsErgebnis = { readonly fehler?: string; readonly erfolg?: string };
|
||||
|
||||
const RECHTE_MELDUNG =
|
||||
"Deine Anmeldung gilt nicht mehr. Melde dich in einem zweiten Tab neu an und klicke dann hier " +
|
||||
"noch einmal — was du eingegeben hast, steht weiterhin auf dieser Seite.";
|
||||
|
||||
/**
|
||||
* Faengt den Rechtefehler ab, statt ihn werfen zu lassen.
|
||||
*
|
||||
* Ein ungefangener Wurf landet in Nexts Fehlergrenze und haengt die Seite aus —
|
||||
* samt allem, was im Formular stand. Nach zwoelf Stunden laeuft die Sitzung von
|
||||
* selbst ab; das trifft sonst genau die Person, die gerade lange tippt (#010).
|
||||
*/
|
||||
async function angemeldetOderMeldung(): Promise<
|
||||
{ readonly benutzer: HandelnderBenutzer } | { readonly fehler: string }
|
||||
> {
|
||||
try {
|
||||
const { benutzer } = await rechtErforderlich("mitarbeiterBearbeiten");
|
||||
return { benutzer: { id: benutzer.id, rolle: benutzer.rolle } };
|
||||
} catch (fehler) {
|
||||
if (fehler instanceof RechteFehler) return { fehler: RECHTE_MELDUNG };
|
||||
throw fehler;
|
||||
}
|
||||
}
|
||||
|
||||
function alsErgebnis(fehler: unknown, neutral: string): AktionsErgebnis {
|
||||
if (fehler instanceof MitarbeiterFehler || fehler instanceof MandantFehler) {
|
||||
return { fehler: fehler.message };
|
||||
}
|
||||
console.error(`${neutral}:`, fehler instanceof Error ? `${fehler.name}: ${fehler.message}` : fehler);
|
||||
return { fehler: neutral };
|
||||
}
|
||||
|
||||
const StammdatenSchema = z.object({
|
||||
vorname: z.string().trim().min(1, "Bitte einen Vornamen angeben."),
|
||||
nachname: z.string().trim().min(1, "Bitte einen Nachnamen angeben."),
|
||||
anzeigename: z.string().trim().optional(),
|
||||
mail: z.string().trim().optional(),
|
||||
upn: z.string().trim().optional(),
|
||||
sam: z.string().trim().optional(),
|
||||
abteilung: z.string().trim().optional(),
|
||||
titel: z.string().trim().optional(),
|
||||
kundeId: z.string().trim().optional(),
|
||||
});
|
||||
|
||||
function ausFormular(formular: FormData) {
|
||||
return StammdatenSchema.safeParse({
|
||||
vorname: formular.get("vorname"),
|
||||
nachname: formular.get("nachname"),
|
||||
anzeigename: formular.get("anzeigename") ?? undefined,
|
||||
mail: formular.get("mail") ?? undefined,
|
||||
upn: formular.get("upn") ?? undefined,
|
||||
sam: formular.get("sam") ?? undefined,
|
||||
abteilung: formular.get("abteilung") ?? undefined,
|
||||
titel: formular.get("titel") ?? undefined,
|
||||
kundeId: formular.get("kundeId") ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export async function mitarbeiterAnlegenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const eingabe = ausFormular(formular);
|
||||
if (!eingabe.success) {
|
||||
return { fehler: eingabe.error.issues[0]?.message ?? "Die Eingabe ist unvollständig." };
|
||||
}
|
||||
|
||||
try {
|
||||
const mitarbeiter = await mitarbeiterAnlegen(
|
||||
{ ...eingabe.data, kundeId: eingabe.data.kundeId || null },
|
||||
zugang.benutzer,
|
||||
);
|
||||
revalidatePath("/mitarbeiter");
|
||||
return { erfolg: `„${mitarbeiter.anzeigename}“ wurde angelegt.` };
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Die Person konnte nicht angelegt werden.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function mitarbeiterBearbeitenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const mitarbeiterId = z.string().min(1).safeParse(formular.get("mitarbeiterId"));
|
||||
if (!mitarbeiterId.success) return { fehler: "Es fehlt die Person." };
|
||||
const eingabe = ausFormular(formular);
|
||||
if (!eingabe.success) {
|
||||
return { fehler: eingabe.error.issues[0]?.message ?? "Die Eingabe ist unvollständig." };
|
||||
}
|
||||
|
||||
try {
|
||||
await mitarbeiterBearbeiten(mitarbeiterId.data, eingabe.data, zugang.benutzer);
|
||||
revalidatePath("/mitarbeiter");
|
||||
revalidatePath(`/mitarbeiter/${mitarbeiterId.data}`);
|
||||
return { erfolg: "Gespeichert." };
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Die Änderung konnte nicht gespeichert werden.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function mitarbeiterAktivSetzenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const eingabe = z
|
||||
.object({ mitarbeiterId: z.string().min(1), aktiv: z.enum(["true", "false"]) })
|
||||
.safeParse({ mitarbeiterId: formular.get("mitarbeiterId"), aktiv: formular.get("aktiv") });
|
||||
if (!eingabe.success) return { fehler: "Die Eingabe ist unvollständig." };
|
||||
|
||||
try {
|
||||
const mitarbeiter = await mitarbeiterAktivSetzen(
|
||||
eingabe.data.mitarbeiterId,
|
||||
eingabe.data.aktiv === "true",
|
||||
zugang.benutzer,
|
||||
);
|
||||
revalidatePath("/mitarbeiter");
|
||||
revalidatePath(`/mitarbeiter/${eingabe.data.mitarbeiterId}`);
|
||||
return { erfolg: mitarbeiter.aktiv ? "Wieder aktiv." : "Deaktiviert." };
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Die Änderung konnte nicht gespeichert werden.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user