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, ProductionOrderNotCompletableError, OpenWorkOrderRemainsError, FinalOutputRequiredError, FinalReceiptRequiredError, } from "../lib/errors.generated"; export interface CompleteProductionOrderInput { id: string; } /** * Function: completeProductionOrder * * Marks physical production complete once required work orders are finished * and final receipt obligations are satisfied. The command freezes production * execution while still allowing later technical completion and review. */ export async function run( db: Transaction, input: CompleteProductionOrderInput, _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 is completable const nextStatus = productionOrderLifecycle.tryTransition(order.status, "complete"); if (!nextStatus) { return err(new ProductionOrderNotCompletableError(id)); } // 3. Check all required work orders are COMPLETE or CANCELLED const workOrders = await db .selectFrom("WorkOrder") .selectAll() .where("productionOrderId", "=", id) .execute(); const hasOpenWorkOrders = workOrders.some( (wo) => wo.status !== "COMPLETE" && wo.status !== "CANCELLED", ); if (hasOpenWorkOrders) { return err(new OpenWorkOrderRemainsError(id)); } // 4. Check final output has been reported const hasCompletedOutput = workOrders.some( (wo) => wo.status === "COMPLETE" && wo.completedQuantity > 0, ); if (!hasCompletedOutput) { return err(new FinalOutputRequiredError(id)); } // 5. Check receipt handoff evidence exists const costSummary = await db .selectFrom("ManufacturingCostSummary") .selectAll() .where("productionOrderId", "=", id) .executeTakeFirst(); if (!costSummary || costSummary.actualMaterialCost <= 0) { return err(new FinalReceiptRequiredError(id)); } // 6. Set status to COMPLETED const completedOrder = await db .updateTable("ProductionOrder") .set({ status: nextStatus, }) .where("id", "=", id) .returningAll() .executeTakeFirstOrThrow(); return ok({ productionOrder: completedOrder }); }