import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { approvalRequestLifecycle } from "../db/approvalRequest.lifecycle.generated"; import type { Transaction } from "../generated/kysely-tailordb"; import { InvalidStatusTransitionError, MissingRequiredFieldError, RequestNotFoundError, } from "../lib/errors.generated"; export interface CancelApprovalRequestInput { approvalRequestId: string; reason: string; } export async function run(db: Transaction, input: CancelApprovalRequestInput, ctx: CommandContext) { const request = await db .selectFrom("ApprovalRequest") .selectAll() .where("id", "=", input.approvalRequestId) .forUpdate() .executeTakeFirst(); if (!request) return err(new RequestNotFoundError(input.approvalRequestId)); const nextStatus = approvalRequestLifecycle.tryTransition(request.status, "cancel"); if (!nextStatus) return err(new InvalidStatusTransitionError(input.approvalRequestId)); if (input.reason.trim() === "") { return err(new MissingRequiredFieldError("reason")); } const now = new Date(); await db .insertInto("ApprovalDecision") .values({ approvalRequestId: request.id, decision: "CANCEL", decidedByUserId: ctx.actorId, comment: input.reason, decidedAt: now, }) .execute(); const updated = await db .updateTable("ApprovalRequest") .set({ status: nextStatus, resolvedAt: now }) .where("id", "=", input.approvalRequestId) .returningAll() .executeTakeFirstOrThrow(); return ok({ approvalRequest: updated }); }