import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { ProductionOrderNotFoundError, ProductionOrderNotReschedulableError, ExecutionAlreadyStartedError, InvalidDateRangeError, } from "../lib/errors.generated"; export interface RescheduleProductionOrderInput { id: string; plannedStartDate: Date; plannedEndDate: Date; } /** * Function: rescheduleProductionOrder * * Changes the planned execution dates on a released order before execution * starts. Preserves the released snapshots and keeps the change auditable. */ export async function run( db: Transaction, input: RescheduleProductionOrderInput, _ctx: CommandContext, ) { const { id, plannedStartDate, plannedEndDate } = input; // 1. Fetch production order with lock const order = await db .selectFrom("ProductionOrder") .selectAll() .where("id", "=", id) .forUpdate() .executeTakeFirst(); if (!order) { return err(new ProductionOrderNotFoundError(id)); } // 2. Validate status is reschedulable if (order.status !== "RELEASED") { return err(new ProductionOrderNotReschedulableError(id)); } // 3. Check no work order has execution evidence const workOrders = await db .selectFrom("WorkOrder") .selectAll() .where("productionOrderId", "=", id) .execute(); const hasExecution = workOrders.some( (wo) => wo.status === "IN_PROGRESS" || wo.status === "PAUSED" || wo.status === "COMPLETE" || wo.completedQuantity > 0 || wo.actualStartDate != null, ); if (hasExecution) { return err(new ExecutionAlreadyStartedError(id)); } // 4. Validate revised dates if (!plannedStartDate || !plannedEndDate) { return err(new InvalidDateRangeError(id)); } const start = new Date(plannedStartDate); const end = new Date(plannedEndDate); if (isNaN(start.getTime()) || isNaN(end.getTime()) || start >= end) { return err(new InvalidDateRangeError(id)); } // 5. Persist scheduling update const rescheduled = await db .updateTable("ProductionOrder") .set({ plannedStartDate, plannedEndDate, }) .where("id", "=", id) .returningAll() .executeTakeFirstOrThrow(); return ok({ productionOrder: rescheduled }); }