import type { PrismaClient, VacationStatus } from "@capakraken/db"; import { TRPCError } from "@trpc/server"; type DbClient = Pick; export type CancelVacationDeps = { assertVacationCancelable: (status: VacationStatus) => void; isVacationManagerRole: (role: string | null | undefined) => boolean; canActorCancelVacation: (input: { actorId: string; actorRole: string | null | undefined; requestedById: string | null | undefined; resourceUserId: string | null | undefined; }) => boolean; }; export type CancelVacationInput = { id: string; actorId: string; actorRole: string | null | undefined; }; export type CancelVacationResult = { vacation: Awaited>; existingStatus: VacationStatus; }; export async function cancelVacation( db: DbClient, input: CancelVacationInput, deps: CancelVacationDeps, ): Promise { const existing = await db.vacation.findUnique({ where: { id: input.id } }); if (!existing) { throw new TRPCError({ code: "NOT_FOUND", message: "Vacation not found" }); } deps.assertVacationCancelable(existing.status); // Only fetch the linked resource when the actor is not a manager and didn't // originally request the vacation — we need to check resource ownership. const needsResourceCheck = !deps.isVacationManagerRole(input.actorRole) && existing.requestedById !== input.actorId; const resource = needsResourceCheck ? await db.resource.findUnique({ where: { id: existing.resourceId }, select: { userId: true }, }) : null; if ( !deps.canActorCancelVacation({ actorId: input.actorId, actorRole: input.actorRole, requestedById: existing.requestedById, resourceUserId: resource?.userId, }) ) { throw new TRPCError({ code: "FORBIDDEN", message: "You can only cancel your own vacation requests", }); } const updated = await db.vacation.update({ where: { id: input.id }, data: { status: "CANCELLED" }, }); return { vacation: updated, existingStatus: existing.status }; }