import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core"; import { approvalRequestLifecycle } from "../db/approvalRequest.lifecycle.generated"; import type { Transaction } from "../generated/kysely-tailordb"; import { InvalidStatusTransitionError, NotRequesterError, RequestNotFoundError, } from "../lib/errors.generated"; export interface WithdrawApprovalRequestInput { approvalRequestId: string; comment?: string; } /** * Function: withdrawApprovalRequest * * Lets the original requester voluntarily terminate their own request before * it resolves. Writes one ApprovalDecision row with decision = WITHDRAW and * transitions the request to WITHDRAWN. */ export async function run( db: Transaction, input: WithdrawApprovalRequestInput, 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, "withdraw"); if (!nextStatus) return err(new InvalidStatusTransitionError(input.approvalRequestId)); if (ctx.actorId !== request.requesterId) { return err(new NotRequesterError(input.approvalRequestId)); } const now = new Date(); await db .insertInto("ApprovalDecision") .values({ approvalRequestId: request.id, decision: "WITHDRAW", decidedByUserId: ctx.actorId, comment: input.comment, decidedAt: now, }) .execute(); const updated = await db .updateTable("ApprovalRequest") .set({ status: nextStatus, resolvedAt: now }) .where("id", "=", input.approvalRequestId) .returningAll() .executeTakeFirstOrThrow(); return ok({ approvalRequest: updated }); }