In der Nutzerverwaltung kannst du jetzt pro Konto die Rolle, die AG-Zuordnung und die Freigaberollen bearbeiten. Die feste 3er-Freigabelogik bleibt Vorstand A / Vorstand B / Finanz-AG, aber jetzt legst du über die Nutzer fest, wer diese Schritte autorisieren darf. Zusätzlich gibt es unter Nutzer anlegen eine eigene Insel für die Freigabe-Schwelle, und diese Schwelle wird jetzt auch wirklich überall verwendet: in der Erfassungslogik, in den Budgetkarten, im CSV-Backup/-Import und im Audit-Restore. Die Hauptänderungen sitzen in dashboard-shell.tsx, budget-column.tsx, route.ts, schema.prisma und route.ts.
All checks were successful
CI / build-and-deploy (push) Successful in 1m22s

Den Zeitraum-Bereich habe ich dabei gleich mit aufgeräumt: die Auswahl des aktuellen Haushalts ist breiter und sauberer angeordnet, und die Desktop-Nutzerverwaltung ist jetzt wirklich links Anlegen + Schwelle und rechts die Nutzerliste. Seed und Backup/Restore kennen die neuen Felder ebenfalls in seed.ts, route.ts und route.ts.
This commit is contained in:
Jan
2026-04-12 20:09:46 +02:00
parent 92d96ffa27
commit b202fc6c26
20 changed files with 1018 additions and 365 deletions

View File

@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
import { createAuditLog } from "@/lib/audit-log";
import { parseCsv } from "@/lib/backup-csv";
import { canManageUsers } from "@/lib/domain";
import { canManageUsers, DEFAULT_APPROVAL_THRESHOLD, getLegacyApprovalPreference, normalizeApprovalPermissions } from "@/lib/domain";
import prisma from "@/lib/prisma";
import { getCurrentViewer } from "@/lib/session";
@@ -28,6 +28,21 @@ function toNumber(value: string | undefined) {
return Number.isFinite(parsed) ? parsed : null;
}
function toApprovalPermissions(
value: string | undefined,
role: "ADMIN" | "FINANCE" | "MEMBER",
approvalPreference: "CHAIR_A" | "CHAIR_B" | "FINANCE" | null
) {
const explicitPermissions = value
? value
.split("|")
.map((entry) => entry.trim())
.filter((entry) => entry.length > 0) as ("CHAIR_A" | "CHAIR_B" | "FINANCE")[]
: [];
return normalizeApprovalPermissions(role, explicitPermissions, approvalPreference);
}
export async function POST(request: Request) {
const viewer = await getCurrentViewer();
@@ -71,6 +86,7 @@ export async function POST(request: Request) {
);
}
const settingsRows = rawEntries.filter((entry) => entry.recordType === "settings");
const periodRows = rawEntries.filter((entry) => entry.recordType === "period");
const groupRows = rawEntries.filter((entry) => entry.recordType === "workingGroup");
const budgetRows = rawEntries.filter((entry) => entry.recordType === "budget");
@@ -87,6 +103,16 @@ export async function POST(request: Request) {
await tx.user.deleteMany();
await tx.workingGroup.deleteMany();
await tx.accountingPeriod.deleteMany();
await tx.appSettings.deleteMany();
const settingsRow = settingsRows[0];
await tx.appSettings.create({
data: {
id: settingsRow?.id || "global",
approvalThreshold: toNumber(settingsRow?.approvalThreshold) ?? DEFAULT_APPROVAL_THRESHOLD,
createdAt: toDate(settingsRow?.createdAt) ?? new Date()
}
});
for (const row of periodRows) {
const startsAt = toDate(row.periodStartsAt);
@@ -119,6 +145,10 @@ export async function POST(request: Request) {
}
for (const row of userRows) {
const role = row.role as "ADMIN" | "FINANCE" | "MEMBER";
const approvalPreference = toNullable(row.approvalPreference) as "CHAIR_A" | "CHAIR_B" | "FINANCE" | null;
const approvalPermissions = toApprovalPermissions(row.approvalPermissions, role, approvalPreference);
await tx.user.create({
data: {
id: row.id,
@@ -126,8 +156,9 @@ export async function POST(request: Request) {
username: row.username,
email: toNullable(row.email),
passwordHash: row.passwordHash,
role: row.role as "ADMIN" | "FINANCE" | "MEMBER",
approvalPreference: toNullable(row.approvalPreference) as "CHAIR_A" | "CHAIR_B" | "FINANCE" | null,
role,
approvalPreference: getLegacyApprovalPreference(approvalPermissions),
approvalPermissions,
workingGroupId: toNullable(row.workingGroupId),
createdAt: toDate(row.createdAt) ?? new Date()
}