import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { APPROVAL_NOT_ACTIVE_ASSIGNEE, APPROVAL_SELF_DECISION_NOT_ALLOWED, type ApproveApprovalStepFn, type FindPendingApproverAssigneeFn, type GetActiveApprovalRequestFn, LEAVE_REQUEST_TARGET_ENTITY_TYPE, } from "../lib/_approvalDeps"; import { LeaveRequestNotFoundError, InvalidStateTransitionError, SelfApprovalError, NotAssigneeError, ApprovalStepFailedError, } from "../lib/errors.generated"; export interface ApproveLeaveCancelInput { id: string; resolvedBy: string; approverComment?: string; } export interface ApproveLeaveCancelDeps { getActiveApprovalRequest: GetActiveApprovalRequestFn; findPendingApproverAssignee: FindPendingApproverAssigneeFn; approveApprovalStep: ApproveApprovalStepFn; } export async function run( db: Transaction, input: ApproveLeaveCancelInput, ctx: CommandContext, deps: ApproveLeaveCancelDeps, ) { const leaveRequest = await db .selectFrom("LeaveRequest") .selectAll() .where("id", "=", input.id) .forUpdate() .executeTakeFirst(); if (!leaveRequest) { return err(new LeaveRequestNotFoundError(input.id)); } // resolvedBy is a user-management User id; self-approval is detected by comparing it against // this same-module request's workerId — no cross-module lookup needed for this check. if (leaveRequest.workerId === input.resolvedBy) { return err(new SelfApprovalError(input.id)); } if (leaveRequest.status !== "CANCEL_PENDING") { return err(new InvalidStateTransitionError(input.id)); } // Resolve the mirrored cancellation approval request as approved. No active request, or the // resolver owning no pending assignee, means they are not an eligible approver. const requestResult = await deps.getActiveApprovalRequest(db, { targetEntityType: LEAVE_REQUEST_TARGET_ENTITY_TYPE, targetEntityId: input.id, }); const request = requestResult.ok ? requestResult.value.approvalRequest : null; if (!request) { return err(new ApprovalStepFailedError(input.id)); } const assignee = await deps.findPendingApproverAssignee(db, { approvalRequestId: request.id, userId: input.resolvedBy, }); if (!assignee) { return err(new NotAssigneeError(input.id)); } const decision = await deps.approveApprovalStep( db, { approvalStepAssigneeId: assignee.approvalStepAssigneeId, comment: input.approverComment }, ctx, ); if (!decision.ok) { switch (decision.error.code) { case APPROVAL_NOT_ACTIVE_ASSIGNEE: return err(new NotAssigneeError(input.id)); case APPROVAL_SELF_DECISION_NOT_ALLOWED: return err(new SelfApprovalError(input.id)); default: return err(new ApprovalStepFailedError(input.id)); } } // By design there is nothing to revert in time-tracking: approveLeave never wrote a leave day // into the attendance record (leave-management does not emit into time-tracking — see approveLeave). // Restoring the LeaveConsumption ledger below is the only side effect of an approved cancellation. const updated = await db .updateTable("LeaveRequest") .set({ status: "CANCELLED", resolvedAt: new Date(), resolvedBy: input.resolvedBy, }) .where("id", "=", input.id) .returningAll() .executeTakeFirstOrThrow(); const consumptions = await db .selectFrom("LeaveConsumption") .selectAll() .where("leaveRequestId", "=", input.id) .where("restoredAt", "is", null) .forUpdate() .execute(); for (const consumption of consumptions) { const grant = await db .selectFrom("LeaveGrant") .selectAll() .where("id", "=", consumption.leaveGrantId) .forUpdate() .executeTakeFirst(); if (grant) { const restoredRemaining = ( Number(grant.remainingDays) + Number(consumption.daysConsumed) ).toString(); await db .updateTable("LeaveGrant") .set({ remainingDays: restoredRemaining }) .where("id", "=", grant.id) .execute(); } await db .updateTable("LeaveConsumption") .set({ restoredAt: new Date() }) .where("id", "=", consumption.id) .execute(); } return ok({ leaveRequest: updated }); }