import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction, Updateable } from "../generated/kysely-tailordb"; import { RoutingNotFoundError, RoutingNotMutableError, DuplicateOperationSequenceError, InvalidStandardTimeError, WorkCenterNotFoundError, CrossCompanyWorkCenterError, } from "../lib/errors.generated"; export interface UpdateRoutingOperationInput { sequenceNumber: number; operationDescription?: string | null; workCenterId: string; standardSetupTime: number; standardRunTime: number; operatorInstructions?: string | null; } export type UpdateRoutingInput = { id: string; revisionNumber?: string | null; operations: UpdateRoutingOperationInput[]; }; /** * Function: updateRouting * * Updates a draft routing's operations. Only DRAFT routings are mutable. * Replaces all existing operations with the revised set. */ export async function run>( db: Transaction, input: UpdateRoutingInput & Omit, "status">, _ctx: CommandContext, ) { const { id, revisionNumber, operations, ...customFields } = input; // 1. Fetch routing const routing = await db .selectFrom("Routing") .selectAll() .where("id", "=", id) .forUpdate() .executeTakeFirst(); if (!routing) { return err(new RoutingNotFoundError(id)); } // 2. Verify DRAFT status if (routing.status !== "DRAFT") { return err(new RoutingNotMutableError(id)); } // 3. Validate unique sequence numbers const seqNumbers = operations.map((op) => op.sequenceNumber); const uniqueSeqs = new Set(seqNumbers); if (uniqueSeqs.size !== seqNumbers.length) { return err(new DuplicateOperationSequenceError(id)); } // 4. Validate standard times >= 0 for (const op of operations) { if (op.standardSetupTime < 0 || op.standardRunTime < 0) { return err(new InvalidStandardTimeError(id)); } } // 5. Validate work centers exist and belong to same company for (const op of operations) { const workCenter = await db .selectFrom("WorkCenter") .selectAll() .where("id", "=", op.workCenterId) .executeTakeFirst(); if (!workCenter) { return err(new WorkCenterNotFoundError(op.workCenterId)); } if (workCenter.companyId !== routing.companyId) { return err(new CrossCompanyWorkCenterError(op.workCenterId)); } } // 6. Update routing header const updateBody: Updateable<"Routing"> = { ...(customFields as Updateable<"Routing">), }; if (revisionNumber !== undefined) updateBody.revisionNumber = revisionNumber; const updatedRouting = Object.keys(updateBody).length === 0 ? routing : await db .updateTable("Routing") .set(updateBody) .where("id", "=", id) .returningAll() .executeTakeFirstOrThrow(); // 7. Delete existing operations and replace await db.deleteFrom("RoutingOperation").where("routingId", "=", id).execute(); const createdOperations = operations.length ? await db .insertInto("RoutingOperation") .values( operations.map((op) => ({ routingId: id, sequenceNumber: op.sequenceNumber, operationDescription: op.operationDescription ?? null, workCenterId: op.workCenterId, standardSetupTime: op.standardSetupTime, standardRunTime: op.standardRunTime, operatorInstructions: op.operatorInstructions ?? null, })), ) .returningAll() .execute() : []; return ok({ routing: updatedRouting, routingOperations: createdOperations }); }