import { createContext, DomainError } from "@tailor-platform/erp-kit/app"; import { createResolver, t } from "@tailor-platform/sdk"; import { getDB } from "@/generated/kysely-tailordb"; import { inboundShipmentModulesExport } from "@/modules"; /** * Post a draft Inbound Shipment (DRAFT → POSTED). * * Posting moves stock via the inventory ledger and pushes the received * quantities onto the linked purchase-order lines, rolling up the PO's * receipt status in the same transaction. The resolver name stays * `settleInboundShipment` for frontend compatibility. */ export default createResolver({ name: "settleInboundShipment", operation: "mutation", input: { id: t.string().description("Inbound shipment (InboundShipment) ID"), }, body: async (context) => { const ctx = createContext(context); const db = getDB("main-db"); return db .transaction() .execute(async (trx) => { const result = await inboundShipmentModulesExport.commands.postInboundShipment( trx, { id: context.input.id }, ctx, ); if (!result.ok) { switch (result.error.code) { case "INBOUND_SHIPMENT_INVALID_STATUS": throw new DomainError( `Inbound shipment is not in a postable state: ${result.error.message}`, ); case "INBOUND_SHIPMENT_EMPTY_SHIPMENT_LINES": throw new DomainError("Cannot post an inbound shipment with no lines"); case "INBOUND_SHIPMENT_PURCHASE_ORDER_RECEIPT_SYNC_FAILED": throw new DomainError( "The linked purchase order receipt status could not be updated.", ); case "INVENTORY_INSUFFICIENT_STOCK": throw new DomainError( `Insufficient stock to post the shipment: ${result.error.message}`, ); case "INVENTORY_INVENTORY_SUPPLY_PLAN_NOT_FOUND": throw new DomainError("A related supply plan was not found."); case "INVENTORY_SUPPLY_PLAN_NOT_OPEN": throw new DomainError("A related supply plan is not open."); case "INVENTORY_STOCK_RESERVATION_NOT_FOUND": throw new DomainError("A related stock reservation was not found."); case "INVENTORY_STOCK_RESERVATION_NOT_OPEN": throw new DomainError("A related stock reservation is not open."); case "INVENTORY_ACCOUNTING_PERIOD_NOT_FOUND": throw new DomainError("No accounting period covers the posting date."); case "INVENTORY_JOURNAL_ENTRY_CREATE_FAILED": throw new DomainError("Failed to create the inventory journal entry."); case "INVENTORY_JOURNAL_ENTRY_POST_FAILED": throw new DomainError("Failed to post the inventory journal entry."); case "INVENTORY_CONSUMPTION_TREATMENT_NOT_SUPPORTED": throw new DomainError("An item's consumption treatment is not supported."); case "INVENTORY_UNIT_COST_REQUIRED": throw new DomainError("A unit cost is required to post the shipment."); case "INVENTORY_AVERAGE_COST_NOT_FOUND": throw new DomainError("No average cost is available for an item."); case "INVENTORY_FIFO_COST_NOT_FOUND": throw new DomainError("No FIFO cost layer is available for an item."); case "INVENTORY_STANDARD_COST_NOT_FOUND": throw new DomainError("No standard cost is defined for an item."); case "INVENTORY_COSTING_EVENT_NOT_SUPPORTED": throw new DomainError( "The costing event is not supported by an item's valuation policy.", ); case "INVENTORY_SITE_NOT_FOUND": throw new DomainError("A referenced site was not found."); case "INVENTORY_DEFAULT_VALUATION_POLICY_NOT_FOUND": throw new DomainError("No default valuation policy is defined for the company."); case "INVENTORY_INVALID_SUPPLY_PLAN_QUANTITIES": throw new DomainError("Supply plan quantities are invalid."); case "INVENTORY_INVALID_RESERVATION_QUANTITIES": throw new DomainError("Reservation quantities are invalid."); case "INVENTORY_EMPTY_MOVEMENT_LINES": throw new DomainError("The shipment has no movement lines."); case "INVENTORY_INVALID_QUANTITY": throw new DomainError("A movement line quantity is invalid."); case "INVENTORY_INVALID_UNIT_COST": throw new DomainError("A movement line unit cost is invalid."); case "INVENTORY_ITEM_NOT_FOUND": throw new DomainError("A referenced item was not found."); case "INVENTORY_STORAGE_LOCATION_NOT_FOUND": throw new DomainError("A referenced storage location was not found."); 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; } } return { id: result.value.inboundShipmentId, // A successful transition implies the target status. status: "POSTED", }; }) .catch((err: unknown) => { if (err instanceof DomainError) throw err; throw new Error("Failed to settle the inbound shipment", { cause: err }); }); }, output: t .object({ id: t.string().description("Inbound shipment (InboundShipment) ID"), status: t.string().description("Inbound shipment status"), }) .description("SettleInboundShipment response"), });