import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { ProductionItemNotFoundError, ProductionItemNotManufacturableError, InvalidPlannedQuantityError, InvalidSiteScopeError, CrossScopeMasterReferenceError, } from "../lib/errors.generated"; import type { ItemManagementQueries, OrganizationQueries } from "../module"; export interface CreateProductionOrderInput { orderedItemId: string; companyId: string; siteId: string; plannedQuantity: number; plannedStartDate: Date; plannedEndDate: Date; selectedBomVersionId?: string | null; selectedRoutingRevisionId?: string | null; } /** * Function: createProductionOrder * * Creates a draft manufacturing request with produced item, quantity, site, * and planned dates. Records planner intent without freezing BOM, routing, * work orders, or cost assumptions. */ export async function run>( db: Transaction, input: CreateProductionOrderInput & CF, ctx: CommandContext, itemManagementQueries: Pick, organizationQueries: Pick, ) { const { orderedItemId, companyId, siteId, plannedQuantity, plannedStartDate, plannedEndDate, selectedBomVersionId, selectedRoutingRevisionId, ...customFields } = input; // 1. Validate planned quantity > 0 if (plannedQuantity <= 0) { return err(new InvalidPlannedQuantityError(orderedItemId)); } // 2. Validate scope if (!companyId || !siteId) { return err(new InvalidSiteScopeError(orderedItemId)); } // 3. Validate produced item exists const { item } = (await itemManagementQueries.getItem(db, { id: orderedItemId }, ctx)).value; if (!item) { return err(new ProductionItemNotFoundError(orderedItemId)); } // 4. Validate item is manufacturable if (item && "isManufacturable" in item && item.isManufacturable === false) { return err(new ProductionItemNotManufacturableError(orderedItemId)); } // 5. Validate site belongs to company scope const { site } = (await organizationQueries.getSite(db, { id: siteId }, ctx)).value; if (!site) { return err(new InvalidSiteScopeError(orderedItemId)); } // 5. Validate optional BOM reference scope if (selectedBomVersionId) { const bom = await db .selectFrom("BillOfMaterial") .selectAll() .where("id", "=", selectedBomVersionId) .executeTakeFirst(); if (bom && (bom.companyId !== companyId || (bom.siteId != null && bom.siteId !== siteId))) { return err(new CrossScopeMasterReferenceError(orderedItemId)); } } // 6. Validate optional routing reference scope if (selectedRoutingRevisionId) { const routing = await db .selectFrom("Routing") .selectAll() .where("id", "=", selectedRoutingRevisionId) .executeTakeFirst(); if ( routing && (routing.companyId !== companyId || (routing.siteId != null && routing.siteId !== siteId)) ) { return err(new CrossScopeMasterReferenceError(orderedItemId)); } } // 7. Create draft production order const productionOrder = await db .insertInto("ProductionOrder") .values({ ...(customFields as Record), orderedItemId, companyId, siteId, plannedQuantity, plannedStartDate, plannedEndDate, selectedBomVersionId: selectedBomVersionId ?? null, selectedRoutingRevisionId: selectedRoutingRevisionId ?? null, status: "DRAFT", }) .returningAll() .executeTakeFirstOrThrow(); return ok({ productionOrder }); }