Rollen Chunk 6: Team-Tab mit Matrix-Kalender und Genehmigungen
Docker Image / test (push) Canceled after 0s
Docker Image / image (push) Canceled after 0s

This commit is contained in:
loki committed 2026-10-11 20:19:06 +02:00
1 parent 8fe7700256
commit 133a6e6ffb
1 file changed
+101
+101
View File
@@ -0,0 +1,101 @@
import { NextResponse } from "next/server";
import { authedRole } from "@/lib/auth";
import { getDb, initializeDatabase } from "@/lib/db";
import { calculateVacationBalance } from "@/lib/domain/vacation";
import { formatDays } from "@/lib/format";
import { errorResponse } from "@/lib/http";
import {
ensureYearSettings,
getVacationRequest,
setVacationStatus,
sumPendingUnitsByYear,
sumVacationUnitsByYear,
} from "@/lib/repository";
export async function POST(
request: Request,
context: { params: Promise<{ id: string }> },
) {
const user = await authedRole(request, ["admin", "teamleiter"]);
if (user instanceof NextResponse) {
return user;
}
const { id } = await context.params;
if (!/^\d+$/.test(id)) {
return errorResponse(400, "invalidId", "Vacation id must be an integer.");
}
let body: { action?: unknown };
try {
body = await request.json();
} catch {
return errorResponse(400, "invalidBody", "Request body must be JSON.");
}
if (body.action !== "genehmigen" && body.action !== "ablehnen") {
return errorResponse(
400,
"invalidAction",
"action must be 'genehmigen' or 'ablehnen'.",
);
}
const db = getDb();
initializeDatabase(db);
const target = db
.prepare(
"SELECT id, user_id, status FROM vacation_requests WHERE id = ? AND deleted_at IS NULL",
)
.get(Number(id)) as
| { id: number; user_id: string; status: string }
| undefined;
if (!target || target.status !== "beantragt") {
return errorResponse(
404,
"notFound",
"Diese offene Anfrage existiert nicht (mehr).",
);
}
const saved = getVacationRequest(db, target.user_id, Number(id));
if (!saved) {
return errorResponse(
404,
"notFound",
"Diese offene Anfrage existiert nicht (mehr).",
);
}
const year = Number(saved.startDate.slice(0, 4));
if (body.action === "genehmigen") {
const settings = ensureYearSettings(db, target.user_id, year);
const committed =
sumVacationUnitsByYear(db, target.user_id, year) +
sumPendingUnitsByYear(db, target.user_id, year) -
saved.units;
const remaining = calculateVacationBalance(
settings.entitlementUnits,
settings.carryoverUnits,
committed,
);
if (saved.units > remaining) {
return errorResponse(
422,
"vacationExceedsBalance",
`Der Urlaubsanspruch für ${year} reicht nicht mehr aus ` +
`(Rest: ${formatDays(remaining)}, benötigt: ${formatDays(saved.units)}). ` +
"Passende Anfragen vorher ablehnen oder das Kontingent anpassen.",
);
}
}
const updated = setVacationStatus(
db,
target.user_id,
Number(id),
body.action === "genehmigen" ? "bestätigt" : "abgelehnt",
);
if (!updated) {
// Zwischen Prüfung und Update entschieden (synchron, aber Absicherung).
return errorResponse(
409,
"alreadyDecided",
"Diese Anfrage wurde bereits entschieden.",
);
}
return NextResponse.json({ status: body.action === "genehmigen" ? "bestätigt" : "abgelehnt" });
}