Rollen Chunk 6: Team-Tab mit Matrix-Kalender und Genehmigungen
This commit is contained in:
1 parent
8fe7700256
commit
133a6e6ffb
1 file changed
+101
@@ -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" });
|
||||
}
|
||||
Reference in new issue
Block a user