Files
ProxLedger/src/server/actions/einstellungen.ts
T
florian.wildeandClaude Opus 5 d416f3869a feat(einstellungen): Betriebswerte im Dashboard einstellbar (#025)
Vier Bereiche unter Einstellungen -> System: Adresse und Agent,
Kaufmaennisches, Sicherheit sowie Firma und Darstellung. Alles liegt als
eine JSON-Zeile in der Tabelle Einstellung; fehlende Felder bekommen ihren
Vorgabewert, eine kaputte Zeile legt den Betrieb nicht lahm.

Die Werte wirken auch wirklich: Die Dashboard-Adresse und der Melderhythmus
landen im ausgelieferten Einzeiler und im systemd-Timer (live geprueft),
der Steuersatz in neuen Rechnungspositionen, die Sitzungsdauer im Token,
die Firmenangaben im Kopf der PDF-Dateien.

Nebenbei geschlossen: die OIDC-Luecke aus #024. Erlaubte Domaenen und ein
Schalter gegen automatisch angelegte Konten. Dazu raeumt der Worker nachts
nach den eingestellten Fristen auf - der juengste Report je Host bleibt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 23:58:30 +02:00

76 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use server";
import { revalidatePath } from "next/cache";
import { protokolliere } from "@/server/audit";
import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
import { einstellungen, einstellungenSchema, einstellungenSpeichern } from "@/server/einstellungen";
export interface AktionsErgebnis {
fehler?: string;
felder?: Record<string, string>;
erfolg?: string;
}
const zahl = (formular: FormData, feld: string) => {
const roh = formular.get(feld)?.toString().replace(",", ".").trim();
return roh === undefined || roh === "" ? undefined : Number(roh);
};
const text = (formular: FormData, feld: string) => formular.get(feld)?.toString().trim() ?? "";
const schalter = (formular: FormData, feld: string) => formular.get(feld) === "an";
export async function einstellungenSpeichernAktion(
_zustand: AktionsErgebnis,
formular: FormData,
): Promise<AktionsErgebnis> {
await sitzungErforderlich();
const vorher = await einstellungen();
const ergebnis = einstellungenSchema.safeParse({
dashboardUrl: text(formular, "dashboardUrl"),
agentRhythmus: text(formular, "agentRhythmus") || vorher.agentRhythmus,
agentStreuungStunden: zahl(formular, "agentStreuungStunden"),
agentAutoUpdate: schalter(formular, "agentAutoUpdate"),
waehrung: text(formular, "waehrung") || vorher.waehrung,
steuersatz: zahl(formular, "steuersatz"),
zahlungszielTage: zahl(formular, "zahlungszielTage"),
margeProzent: zahl(formular, "margeProzent"),
sitzungsdauerStunden: zahl(formular, "sitzungsdauerStunden"),
anmeldeversuche: zahl(formular, "anmeldeversuche"),
oidcDomaenen: text(formular, "oidcDomaenen"),
oidcKontenAnlegen: schalter(formular, "oidcKontenAnlegen"),
aufbewahrungReportsTage: zahl(formular, "aufbewahrungReportsTage"),
aufbewahrungAuditTage: zahl(formular, "aufbewahrungAuditTage"),
firmaName: text(formular, "firmaName"),
firmaAnschrift: text(formular, "firmaAnschrift"),
firmaEmail: text(formular, "firmaEmail"),
zeitzone: text(formular, "zeitzone") || vorher.zeitzone,
});
if (!ergebnis.success) {
const felder: Record<string, string> = {};
for (const fehler of ergebnis.error.issues) {
const feld = fehler.path[0];
if (typeof feld === "string" && !felder[feld]) felder[feld] = fehler.message;
}
return { fehler: "Bitte die markierten Felder prüfen.", felder };
}
await einstellungenSpeichern(ergebnis.data);
// Nur die geänderten Felder ins Protokoll – sonst steht bei jedem Speichern alles darin.
const geaendert = Object.fromEntries(
Object.entries(ergebnis.data).filter(
([feld, wert]) => wert !== vorher[feld as keyof typeof vorher],
),
);
await protokolliere("einstellungen.gespeichert", "Einstellung", "system", geaendert);
revalidatePath("/einstellungen/system");
return { erfolg: "Einstellungen gespeichert." };
}