refactor(api): extract vacation management support

This commit is contained in:
2026-03-31 14:27:54 +02:00
parent 609804a334
commit 73cfc9341b
3 changed files with 273 additions and 54 deletions
@@ -0,0 +1,99 @@
import type { Prisma } from "@capakraken/db";
import { VacationStatus } from "@capakraken/db";
import type { UpdateVacationStatusInput } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
type VacationApprovalWriteData = Record<string, unknown>;
export const approvableVacationStatuses: readonly VacationStatus[] = [
VacationStatus.PENDING,
VacationStatus.CANCELLED,
VacationStatus.REJECTED,
];
export function assertVacationApprovable(status: VacationStatus): void {
if (!approvableVacationStatuses.includes(status)) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Only PENDING, CANCELLED, or REJECTED vacations can be approved",
});
}
}
export function assertVacationRejectable(status: VacationStatus): void {
if (status !== VacationStatus.PENDING) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Only PENDING vacations can be rejected",
});
}
}
export function assertVacationCancelable(status: VacationStatus): void {
if (status === VacationStatus.CANCELLED) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Already cancelled",
});
}
}
export function isVacationManagerRole(role: string | null | undefined): boolean {
return role === "ADMIN" || role === "MANAGER";
}
export function canActorCancelVacation(input: {
actorId: string;
actorRole: string | null | undefined;
requestedById: string | null | undefined;
resourceUserId: string | null | undefined;
}): boolean {
if (isVacationManagerRole(input.actorRole)) {
return true;
}
return input.requestedById === input.actorId || input.resourceUserId === input.actorId;
}
export function buildApprovedVacationUpdateData(input: {
deductionSnapshotWriteData: VacationApprovalWriteData;
approvedById?: string | undefined;
approvedAt: Date;
}): Prisma.VacationUncheckedUpdateInput {
return {
status: VacationStatus.APPROVED,
rejectionReason: null,
...input.deductionSnapshotWriteData,
...(input.approvedById ? { approvedById: input.approvedById } : {}),
approvedAt: input.approvedAt,
};
}
export function buildRejectedVacationUpdateData(input: {
rejectionReason?: string | undefined;
}): Prisma.VacationUncheckedUpdateInput {
return {
status: VacationStatus.REJECTED,
...(input.rejectionReason !== undefined ? { rejectionReason: input.rejectionReason } : {}),
};
}
export function buildVacationStatusUpdateData(input: {
status: UpdateVacationStatusInput["status"];
note?: string | undefined;
approvedById: string;
approvedAt: Date;
}): Prisma.VacationUncheckedUpdateInput {
const data: Prisma.VacationUncheckedUpdateInput = { status: input.status };
if (input.status === VacationStatus.APPROVED) {
data.approvedById = input.approvedById;
data.approvedAt = input.approvedAt;
data.rejectionReason = null;
}
if (input.note !== undefined) {
data.note = input.note;
}
return data;
}