import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction, Updateable } from "../generated/kysely-tailordb"; import { ProductionOrderNotFoundError, ProductionOrderNotMutableError, InvalidPlannedQuantityError, CrossScopeMasterReferenceError, } from "../lib/errors.generated"; export type UpdateProductionOrderInput = { id: string; plannedQuantity?: number; plannedStartDate?: Date; plannedEndDate?: Date; selectedBomVersionId?: string | null; selectedRoutingRevisionId?: string | null; }; /** * Function: updateProductionOrder * * Revises mutable draft planning fields such as dates, quantity, and * preferred BOM or routing before release freezes execution assumptions. */ export async function run>( db: Transaction, input: UpdateProductionOrderInput & Omit, "status">, _ctx: CommandContext, ) { const { id, plannedQuantity, plannedStartDate, plannedEndDate, selectedBomVersionId, selectedRoutingRevisionId, ...customFields } = 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. Only DRAFT orders are mutable if (order.status !== "DRAFT") { return err(new ProductionOrderNotMutableError(id)); } // 3. Validate planned quantity if provided if (plannedQuantity != null && plannedQuantity <= 0) { return err(new InvalidPlannedQuantityError(id)); } // 4. Validate optional BOM reference scope const effectiveBomId = selectedBomVersionId !== undefined ? selectedBomVersionId : order.selectedBomVersionId; if (effectiveBomId) { const bom = await db .selectFrom("BillOfMaterial") .selectAll() .where("id", "=", effectiveBomId) .executeTakeFirst(); if ( bom && (bom.companyId !== order.companyId || (bom.siteId != null && bom.siteId !== order.siteId)) ) { return err(new CrossScopeMasterReferenceError(id)); } } // 5. Validate optional routing reference scope const effectiveRoutingId = selectedRoutingRevisionId !== undefined ? selectedRoutingRevisionId : order.selectedRoutingRevisionId; if (effectiveRoutingId) { const routing = await db .selectFrom("Routing") .selectAll() .where("id", "=", effectiveRoutingId) .executeTakeFirst(); if ( routing && (routing.companyId !== order.companyId || (routing.siteId != null && routing.siteId !== order.siteId)) ) { return err(new CrossScopeMasterReferenceError(id)); } } // 6. Build update set const updateSet: Updateable<"ProductionOrder"> = { ...(customFields as Updateable<"ProductionOrder">), }; if (plannedQuantity !== undefined) updateSet.plannedQuantity = plannedQuantity; if (plannedStartDate !== undefined) updateSet.plannedStartDate = plannedStartDate; if (plannedEndDate !== undefined) updateSet.plannedEndDate = plannedEndDate; if (selectedBomVersionId !== undefined) updateSet.selectedBomVersionId = selectedBomVersionId; if (selectedRoutingRevisionId !== undefined) updateSet.selectedRoutingRevisionId = selectedRoutingRevisionId; if (Object.keys(updateSet).length === 0) { return ok({ productionOrder: order }); } // 7. Persist changes const updated = await db .updateTable("ProductionOrder") .set(updateSet) .where("id", "=", id) .returningAll() .executeTakeFirstOrThrow(); return ok({ productionOrder: updated }); }