This commit is contained in:
@@ -21,7 +21,7 @@ const updateBudgetSchema = z
|
||||
if (value.releasedAmount !== undefined && value.releasedAmount > value.totalBudget) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Der bereits an die AG uebergebene Betrag darf das Budget nicht uebersteigen.",
|
||||
message: "Der bereits an die AG übergebene Betrag darf das Budget nicht übersteigen.",
|
||||
path: ["releasedAmount"]
|
||||
});
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export async function PATCH(request: Request, { params }: Context) {
|
||||
}
|
||||
|
||||
if (!canManageBudgets(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen Budgets aendern." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen dürfen Budgets ändern." }, { status: 403 });
|
||||
}
|
||||
|
||||
const budget = await prisma.budget.findUnique({
|
||||
@@ -65,7 +65,7 @@ export async function PATCH(request: Request, { params }: Context) {
|
||||
const parsed = updateBudgetSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Budgetname, Betrag, Mitteluebergabe oder Farbe sind ungueltig." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Budgetname, Betrag, Mittelübergabe oder Farbe sind ungültig." }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -140,7 +140,7 @@ export async function DELETE(_: Request, { params }: Context) {
|
||||
}
|
||||
|
||||
if (!canManageBudgets(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen Budgets loeschen." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen dürfen Budgets löschen." }, { status: 403 });
|
||||
}
|
||||
|
||||
const budget = await prisma.budget.findUnique({
|
||||
@@ -160,7 +160,7 @@ export async function DELETE(_: Request, { params }: Context) {
|
||||
|
||||
if (budget._count.expenses > 0) {
|
||||
return NextResponse.json(
|
||||
{ error: "Dieses Budget enthaelt noch Ausgaben. Bitte loesche oder verschiebe erst die Posten." },
|
||||
{ error: "Dieses Budget enthält noch Ausgaben. Bitte lösche oder verschiebe erst die Posten." },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
@@ -175,7 +175,7 @@ export async function DELETE(_: Request, { params }: Context) {
|
||||
entityType: "budget",
|
||||
entityId: budget.id,
|
||||
entityLabel: budget.name,
|
||||
summary: `Budget ${budget.name} wurde geloescht.`,
|
||||
summary: `Budget ${budget.name} wurde gelöscht.`,
|
||||
metadata: {
|
||||
rollback: {
|
||||
kind: "budget.delete",
|
||||
|
||||
@@ -20,7 +20,7 @@ const budgetSchema = z
|
||||
if (value.releasedAmount !== undefined && value.releasedAmount > value.totalBudget) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Der bereits an die AG uebergebene Betrag darf das Budget nicht uebersteigen.",
|
||||
message: "Der bereits an die AG übergebene Betrag darf das Budget nicht übersteigen.",
|
||||
path: ["releasedAmount"]
|
||||
});
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
if (!canManageBudgets(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen Budgets verwalten." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen dürfen Budgets verwalten." }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await request.json().catch(() => null);
|
||||
@@ -42,7 +42,7 @@ export async function POST(request: Request) {
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json(
|
||||
{ error: "Bitte AG, Budgetname, Betrag, Mitteluebergabe und Farbe korrekt angeben." },
|
||||
{ error: "Bitte AG, Budgetname, Betrag, Mittelübergabe und Farbe korrekt angeben." },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export async function POST(request: Request, { params }: Context) {
|
||||
const parsed = approvalSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Freigabetyp ungueltig." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Freigabetyp ungültig." }, { status: 400 });
|
||||
}
|
||||
|
||||
const existingApprovals = expense.approvals.map((approval) => approval.approvalType);
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function POST(_: Request, { params }: Context) {
|
||||
}
|
||||
|
||||
if (!canDocumentExpense(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen dokumentieren." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen dürfen dokumentieren." }, { status: 403 });
|
||||
}
|
||||
|
||||
const expense = await prisma.expense.findUnique({
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function POST(_: Request, { params }: Context) {
|
||||
}
|
||||
|
||||
if (!canMarkPaid(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen Bezahlt setzen." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen dürfen Bezahlt setzen." }, { status: 403 });
|
||||
}
|
||||
|
||||
const expense = await prisma.expense.findUnique({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
|
||||
|
||||
import { createAuditLog } from "@/lib/audit-log";
|
||||
import { canDocumentExpense } from "@/lib/domain";
|
||||
import { uploadExpenseProofToDrive } from "@/lib/google-drive";
|
||||
import { serializeDriveError, uploadExpenseProofToDrive } from "@/lib/google-drive";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { getCurrentViewer } from "@/lib/session";
|
||||
|
||||
@@ -48,11 +48,11 @@ export async function POST(request: Request, { params }: Context) {
|
||||
}
|
||||
|
||||
if (expense.creatorId !== viewer.id && !canDocumentExpense(viewer.role)) {
|
||||
return NextResponse.json({ error: "Du darfst fuer diese Ausgabe keinen Beleg hochladen." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Du darfst für diese Ausgabe keinen Beleg hochladen." }, { status: 403 });
|
||||
}
|
||||
|
||||
if (expense.approvalStatus !== "APPROVED") {
|
||||
return NextResponse.json({ error: "Belegabgabe ist erst nach Freigabe moeglich." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Belegabgabe ist erst nach Freigabe möglich." }, { status: 400 });
|
||||
}
|
||||
|
||||
const formData = await request.formData().catch(() => null);
|
||||
@@ -60,11 +60,11 @@ export async function POST(request: Request, { params }: Context) {
|
||||
const invoiceDate = parseInvoiceDate(formData?.get("invoiceDate") ?? null);
|
||||
|
||||
if (!invoiceDate) {
|
||||
return NextResponse.json({ error: "Bitte ein gueltiges Rechnungsdatum angeben." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Bitte ein gültiges Rechnungsdatum angeben." }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!(file instanceof File)) {
|
||||
return NextResponse.json({ error: "Bitte einen Beleg als Bild oder PDF auswaehlen." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Bitte einen Beleg als Bild oder PDF auswählen." }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!ACCEPTED_MIME_TYPES.has(file.type)) {
|
||||
@@ -72,7 +72,7 @@ export async function POST(request: Request, { params }: Context) {
|
||||
}
|
||||
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
return NextResponse.json({ error: "Der Beleg darf maximal 12 MB gross sein." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Der Beleg darf maximal 12 MB groß sein." }, { status: 400 });
|
||||
}
|
||||
|
||||
const uploadedFile = await uploadExpenseProofToDrive({
|
||||
@@ -82,8 +82,15 @@ export async function POST(request: Request, { params }: Context) {
|
||||
fileName: file.name,
|
||||
mimeType: file.type,
|
||||
buffer: Buffer.from(await file.arrayBuffer())
|
||||
}).catch((error) => {
|
||||
const serialized = serializeDriveError(error);
|
||||
return NextResponse.json(serialized, { status: serialized.status ?? 500 });
|
||||
});
|
||||
|
||||
if (uploadedFile instanceof NextResponse) {
|
||||
return uploadedFile;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const transactionResult = await prisma.$transaction(async (tx) => {
|
||||
const document = await tx.expenseDocument.create({
|
||||
@@ -125,7 +132,7 @@ export async function POST(request: Request, { params }: Context) {
|
||||
entityType: "expense",
|
||||
entityId: transactionResult.updatedExpense.id,
|
||||
entityLabel: transactionResult.updatedExpense.title,
|
||||
summary: `Rechnung fuer ${transactionResult.updatedExpense.title} wurde abgegeben.`,
|
||||
summary: `Rechnung für ${transactionResult.updatedExpense.title} wurde abgegeben.`,
|
||||
metadata: {
|
||||
documentId: transactionResult.document.id,
|
||||
proofUrl: transactionResult.document.proofUrl,
|
||||
|
||||
@@ -55,7 +55,7 @@ export async function DELETE(_: Request, { params }: Context) {
|
||||
entityType: "expense",
|
||||
entityId: expense.id,
|
||||
entityLabel: expense.title,
|
||||
summary: `Ausgabe ${expense.title} wurde geloescht.`,
|
||||
summary: `Ausgabe ${expense.title} wurde gelöscht.`,
|
||||
metadata: {
|
||||
rollback: {
|
||||
kind: "expense.delete",
|
||||
|
||||
@@ -120,7 +120,8 @@ export async function POST(request: Request) {
|
||||
{
|
||||
id: expense.id,
|
||||
title: expense.title,
|
||||
amount: Number(expense.amount)
|
||||
amount: Number(expense.amount),
|
||||
workingGroupId: expense.agId
|
||||
},
|
||||
requiredApprovalTypes
|
||||
);
|
||||
|
||||
@@ -74,7 +74,7 @@ export async function GET() {
|
||||
}
|
||||
|
||||
if (!canManageUsers(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen CSV-Backups herunterladen." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen dürfen CSV-Backups herunterladen." }, { status: 403 });
|
||||
}
|
||||
|
||||
const [appSettings, users, accountingPeriods, workingGroups, auditLogs] = await Promise.all([
|
||||
|
||||
@@ -73,7 +73,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
if (!canManageUsers(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen Backups einspielen." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen dürfen Backups einspielen." }, { status: 403 });
|
||||
}
|
||||
|
||||
const formData = await request.formData().catch(() => null);
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function POST(request: Request) {
|
||||
const parsed = subscriptionSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Push-Subscription ist ungueltig." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Push-Subscription ist ungültig." }, { status: 400 });
|
||||
}
|
||||
|
||||
await prisma.pushSubscription.upsert({
|
||||
@@ -57,7 +57,7 @@ export async function DELETE(request: Request) {
|
||||
const parsed = z.object({ endpoint: z.string().url() }).safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Push-Subscription ist ungueltig." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Push-Subscription ist ungültig." }, { status: 400 });
|
||||
}
|
||||
|
||||
await prisma.pushSubscription.deleteMany({
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { canManageSettings } from "@/lib/domain";
|
||||
import { runDriveDiagnostics, serializeDriveError } from "@/lib/google-drive";
|
||||
import { getCurrentViewer } from "@/lib/session";
|
||||
|
||||
export async function POST() {
|
||||
const viewer = await getCurrentViewer();
|
||||
|
||||
if (!viewer) {
|
||||
return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!canManageSettings(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur AG Orga darf die Drive-Verbindung testen." }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await runDriveDiagnostics();
|
||||
return NextResponse.json(result);
|
||||
} catch (error) {
|
||||
const serialized = serializeDriveError(error);
|
||||
|
||||
return NextResponse.json(serialized, { status: serialized.status ?? 500 });
|
||||
}
|
||||
}
|
||||
@@ -22,14 +22,14 @@ export async function PATCH(request: Request) {
|
||||
}
|
||||
|
||||
if (!canManageUsers(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen Einstellungen aendern." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen dürfen Einstellungen ändern." }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await request.json().catch(() => null);
|
||||
const parsed = settingsSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Bitte gueltige Einstellungen eingeben." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Bitte gültige Einstellungen eingeben." }, { status: 400 });
|
||||
}
|
||||
|
||||
const changesOrgaSettings =
|
||||
|
||||
@@ -56,7 +56,7 @@ export async function PATCH(request: Request, { params }: Context) {
|
||||
}
|
||||
|
||||
if (!canManageUsers(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen Nutzer bearbeiten." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen dürfen Nutzer bearbeiten." }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await request.json().catch(() => null);
|
||||
@@ -215,7 +215,7 @@ export async function DELETE(_: Request, { params }: Context) {
|
||||
entityType: "user",
|
||||
entityId: user.id,
|
||||
entityLabel: user.username,
|
||||
summary: `Nutzer ${user.username} wurde geloescht.`,
|
||||
summary: `Nutzer ${user.username} wurde gelöscht.`,
|
||||
metadata: {
|
||||
rollback: {
|
||||
kind: "user.delete",
|
||||
|
||||
@@ -52,7 +52,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
if (!canManageUsers(viewer.role)) {
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen duerfen Nutzer anlegen." }, { status: 403 });
|
||||
return NextResponse.json({ error: "Nur Vorstand allgemein, AG Orga oder AG Finanzen dürfen Nutzer anlegen." }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await request.json().catch(() => null);
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function PATCH(request: Request, { params }: Context) {
|
||||
const parsed = workingGroupSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Bitte einen gueltigen AG-Namen angeben." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Bitte einen gültigen AG-Namen angeben." }, { status: 400 });
|
||||
}
|
||||
|
||||
const workingGroup = await prisma.workingGroup.findUnique({
|
||||
@@ -137,7 +137,7 @@ export async function DELETE(_: Request, { params }: Context) {
|
||||
entityType: "workingGroup",
|
||||
entityId: workingGroup.id,
|
||||
entityLabel: workingGroup.name,
|
||||
summary: `AG ${workingGroup.name} wurde geloescht.`,
|
||||
summary: `AG ${workingGroup.name} wurde gelöscht.`,
|
||||
metadata: {
|
||||
rollback: {
|
||||
kind: "workingGroup.delete",
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function POST(request: Request) {
|
||||
const parsed = workingGroupSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Bitte einen gueltigen AG-Namen angeben." }, { status: 400 });
|
||||
return NextResponse.json({ error: "Bitte einen gültigen AG-Namen angeben." }, { status: 400 });
|
||||
}
|
||||
|
||||
const existingWorkingGroup = await prisma.workingGroup.findFirst({
|
||||
|
||||
Reference in New Issue
Block a user