import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { InvalidExpirationDateError, InvalidGrantedDaysError, LeaveTypeNotFoundError, WorkerNotFoundError, } from "../lib/errors.generated"; // The erp-kit Result union, narrowed to what grantLeave reads. type Result = { ok: true; value: T } | { ok: false; error: { code: string } }; /** Injected workforce query seam: the Worker the grant is issued to (existence check). */ export interface GrantLeaveDeps { getWorker: ( db: Transaction, input: { id: string }, ctx: CommandContext, ) => Promise>; } function isValidHalfDayIncrement(value: number): boolean { return Number.isFinite(value) && Math.abs(value * 2 - Math.round(value * 2)) < 1e-9; } export interface GrantLeaveInput { workerId: string; leaveTypeKey: string; grantType: "STATUTORY" | "COMPENSATORY" | "MANUAL"; /** Positive decimal day amount as a string, e.g. "1", "0.5", "10" (half-day increments). */ grantedDays: string; grantedDate: Date; /** * Required for all grant types; must be after grantedDate. The caller/policy decides the * expiration window — e.g. the admin/UI resolves it from the effective AccrualPlan's * expirationMonths for STATUTORY, or from company policy for COMPENSATORY/MANUAL. This * command validates but does not itself query AccrualPlan. */ expirationDate?: Date; } export async function run( db: Transaction, input: GrantLeaveInput, ctx: CommandContext, deps: GrantLeaveDeps, ) { // The grant is issued to a workforce Worker — validate it exists through the injected query seam // (cross-module reads go through injected queries, not FK-only). const workerResult = await deps.getWorker(db, { id: input.workerId }, ctx); if (!workerResult.ok || !workerResult.value.worker) { return err(new WorkerNotFoundError(input.workerId)); } const leaveType = await db .selectFrom("LeaveType") .selectAll() .where("key", "=", input.leaveTypeKey) .executeTakeFirst(); if (!leaveType) { return err(new LeaveTypeNotFoundError(input.leaveTypeKey)); } const days = Number(input.grantedDays); if (!(days > 0) || !isValidHalfDayIncrement(days)) { return err(new InvalidGrantedDaysError(input.grantedDays)); } const grantedDays = input.grantedDays; if (!input.expirationDate || input.expirationDate.getTime() <= input.grantedDate.getTime()) { return err(new InvalidExpirationDateError(input.grantedDays)); } const expirationDate = input.expirationDate; const leaveGrant = await db .insertInto("LeaveGrant") .values({ workerId: input.workerId, leaveTypeKey: input.leaveTypeKey, grantType: input.grantType, grantSource: "MANUAL", grantedDays, remainingDays: grantedDays, grantedDate: input.grantedDate, expirationDate, expiredAt: null, }) .returningAll() .executeTakeFirstOrThrow(); return ok({ leaveGrant }); }