import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core"; import { productionOrderLifecycle } from "../db/productionOrder.lifecycle.generated"; import type { Transaction } from "../generated/kysely-tailordb"; import { ProductionOrderNotFoundError, ProductionOrderNotTechnicallyCompletableError, ExecutionExceptionRemainsError, PendingMaterialIssueRequestsError, CostSummaryNotReadyError, } from "../lib/errors.generated"; export interface TechnicallyCompleteProductionOrderInput { id: string; } /** * Function: technicallyCompleteProductionOrder * * Freezes the production order after physical completion and moves the linked * manufacturing cost summary into variance review. Marks the point where no * more normal execution or rescheduling is expected. */ export async function run( db: Transaction, input: TechnicallyCompleteProductionOrderInput, _ctx: CommandContext, ) { const { id } = 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 const nextStatus = productionOrderLifecycle.tryTransition(order.status, "technicallyComplete"); if (!nextStatus) { return err(new ProductionOrderNotTechnicallyCompletableError(id)); } // 3. Check for open execution exceptions (work orders not COMPLETE or CANCELLED) const workOrders = await db .selectFrom("WorkOrder") .selectAll() .where("productionOrderId", "=", id) .execute(); const hasExecutionExceptions = workOrders.some( (wo) => wo.status === "IN_PROGRESS" || wo.status === "PAUSED", ); if (hasExecutionExceptions) { return err(new ExecutionExceptionRemainsError(id)); } // 4. Check for pending material issue requests (work orders still PENDING) const hasPendingWork = workOrders.some((wo) => wo.status === "PENDING"); if (hasPendingWork) { return err(new PendingMaterialIssueRequestsError(id)); } // 5. Find and validate cost summary const costSummary = await db .selectFrom("ManufacturingCostSummary") .selectAll() .where("productionOrderId", "=", id) .forUpdate() .executeTakeFirst(); if (costSummary?.status !== "COLLECTING") { return err(new CostSummaryNotReadyError(id)); } // 6. Move cost summary to PENDING_VARIANCE_REVIEW await db .updateTable("ManufacturingCostSummary") .set({ status: "PENDING_VARIANCE_REVIEW", }) .where("id", "=", costSummary.id) .execute(); // 7. Set order status to TECHNICALLY_COMPLETE const techCompleteOrder = await db .updateTable("ProductionOrder") .set({ status: nextStatus, }) .where("id", "=", id) .returningAll() .executeTakeFirstOrThrow(); return ok({ productionOrder: techCompleteOrder }); }