import { createContext, DomainError } from "@tailor-platform/erp-kit/app"; import { createResolver, t } from "@tailor-platform/sdk"; import { getDB } from "@/generated/kysely-tailordb"; import { invModulesExport } from "@/modules"; /** * receiveTransferOrder — app-layer "receive the whole shipment" action. * * erp-kit's native `receiveTransferOrder` is line-explicit: it takes * `{ id, effectiveDate, lines: [{ transferOrderLineId, transitStorageLocationId, * destinationStorageLocationId, quantity }] }`. The UI exposes a single * "Receive" click, so this resolver derives the line set (the full * `shippedQuantity − receivedQuantity` remainder per line) and resolves the * transit/destination storage location as the first ACTIVE StorageLocation * under the TO's destination site, then re-reads the header for the response. */ export default createResolver({ name: "receiveTransferOrder", operation: "mutation", input: { id: t.string().description("Transfer order ID"), }, body: async (context) => { const ctx = createContext(context); const db = getDB("main-db"); return db .transaction() .execute(async (trx) => { const transferOrder = await trx .selectFrom("TransferOrder") .select(["destinationSiteId"]) .where("id", "=", context.input.id) .executeTakeFirst(); if (!transferOrder) { throw new DomainError(`Transfer order not found: ${context.input.id}`); } const destinationLocation = await trx .selectFrom("StorageLocation") .select(["id"]) .where("siteId", "=", transferOrder.destinationSiteId) .where("status", "=", "ACTIVE") .limit(1) .executeTakeFirst(); if (!destinationLocation) { throw new DomainError( `No ACTIVE storage location found for the destination site — cannot receive the transfer order.`, ); } const lines = await trx .selectFrom("TransferOrderLine") .select(["id", "shippedQuantity", "receivedQuantity"]) .where("transferOrderId", "=", context.input.id) .execute(); const receiveLines = lines .map((line) => ({ transferOrderLineId: line.id, transitStorageLocationId: destinationLocation.id, destinationStorageLocationId: destinationLocation.id, quantity: String(Number(line.shippedQuantity) - Number(line.receivedQuantity)), })) .filter((line) => Number(line.quantity) > 0); const result = await invModulesExport.commands.receiveTransferOrder( trx, { id: context.input.id, effectiveDate: new Date(), lines: receiveLines, }, ctx, ); if (!result.ok) { switch (result.error.code) { case "INVENTORY_TRANSFER_ORDER_NOT_FOUND": throw new DomainError(`Transfer order not found: ${context.input.id}`); case "INVENTORY_INVALID_STATE_TRANSITION": throw new DomainError( `Transfer order cannot be received in its current status: ${context.input.id}`, ); case "INVENTORY_EMPTY_TRANSFER_LINES": throw new DomainError( "Nothing to receive — the transfer order has no shipped quantity remaining", ); case "INVENTORY_INVALID_QUANTITY": throw new DomainError(`Invalid receive quantity: ${result.error.message}`); case "INVENTORY_INVALID_TRANSFER_LOCATIONS": throw new DomainError( `Invalid transit/destination locations: ${result.error.message}`, ); case "INVENTORY_STORAGE_LOCATION_NOT_FOUND": throw new DomainError(`Storage location not found: ${result.error.message}`); case "INVENTORY_INSUFFICIENT_STOCK": throw new DomainError(`Insufficient in-transit stock: ${result.error.message}`); case "INVENTORY_TRANSFER_ORDER_LINE_NOT_FOUND": throw new DomainError(`Transfer order line not found: ${result.error.message}`); case "INVENTORY_ITEM_NOT_FOUND": throw new DomainError("Item not found"); case "INVENTORY_SITE_NOT_FOUND": throw new DomainError("Site not found"); case "INVENTORY_EMPTY_MOVEMENT_LINES": throw new DomainError("The receipt has no movement lines"); case "INVENTORY_INVALID_UNIT_COST": throw new DomainError("Invalid unit cost"); case "INVENTORY_UNIT_COST_REQUIRED": throw new DomainError("A unit cost is required to receive this transfer order"); case "INVENTORY_ACCOUNTING_PERIOD_NOT_FOUND": throw new DomainError("No accounting period covers the receipt date"); case "INVENTORY_JOURNAL_ENTRY_CREATE_FAILED": throw new DomainError("Failed to create the journal entry for this receipt"); case "INVENTORY_JOURNAL_ENTRY_POST_FAILED": throw new DomainError("Failed to post the journal entry for this receipt"); case "INVENTORY_INVENTORY_SUPPLY_PLAN_NOT_FOUND": throw new DomainError("Inventory supply plan not found"); case "INVENTORY_SUPPLY_PLAN_NOT_OPEN": throw new DomainError("The inventory supply plan is not open"); case "INVENTORY_INVALID_SUPPLY_PLAN_QUANTITIES": throw new DomainError("Invalid supply plan quantities"); case "INVENTORY_STOCK_RESERVATION_NOT_FOUND": throw new DomainError("Stock reservation not found"); case "INVENTORY_STOCK_RESERVATION_NOT_OPEN": throw new DomainError("The stock reservation is not open"); case "INVENTORY_INVALID_RESERVATION_QUANTITIES": throw new DomainError("Invalid reservation quantities"); case "INVENTORY_DEFAULT_VALUATION_POLICY_NOT_FOUND": throw new DomainError("No default valuation policy is configured for the company"); case "INVENTORY_AVERAGE_COST_NOT_FOUND": throw new DomainError( "No average cost is available for an item on this transfer order", ); case "INVENTORY_FIFO_COST_NOT_FOUND": throw new DomainError("No FIFO cost is available for an item on this transfer order"); case "INVENTORY_STANDARD_COST_NOT_FOUND": throw new DomainError( "No standard cost is published for an item on this transfer order", ); case "INVENTORY_COSTING_EVENT_NOT_SUPPORTED": throw new DomainError("The costing event is not supported"); case "INVENTORY_CONSUMPTION_TREATMENT_NOT_SUPPORTED": throw new DomainError("The item's consumption treatment is not supported"); case "UNAUTHENTICATED": throw new DomainError("Authentication is required"); case "INSUFFICIENT_PERMISSION": throw new DomainError("You do not have permission to perform this action"); default: throw result.error satisfies never; } } // Native receive returns the receipt `inventoryExecution`, not the TO // header — re-read the header for the response. const header = await trx .selectFrom("TransferOrder") .select(["id", "status"]) .where("id", "=", context.input.id) .executeTakeFirstOrThrow(); return { id: header.id, status: header.status }; }) .catch((err: unknown) => { if (err instanceof DomainError) throw err; throw new Error("Failed to receive the transfer order", { cause: err }); }); }, output: t .object({ id: t.string().description("Transfer order ID"), status: t.string().description("New document status"), }) .description("ReceiveTransferOrder response"), });