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"; /** * createStockAdjustment — raise a DRAFT stock adjustment. * * Thin wrapper over erp-kit's native `createStockAdjustment`. A stock * adjustment reconciles physical stock against the system for a reason: * - CORRECTION — count correction; each line needs an adjustmentDirection * (INCREASE / DECREASE) applied to AVAILABLE stock. * - SCRAP — write off stock; `fromStockCategory` selects AVAILABLE * (default) or BLOCKED stock to scrap. * - BLOCK — move AVAILABLE → BLOCKED. * - UNBLOCK — move BLOCKED → AVAILABLE. * Quantities are always positive; the type/direction determines the effect. * Nothing touches real stock until the adjustment is confirmed. */ export default createResolver({ name: "createStockAdjustment", operation: "mutation", input: { adjustmentType: t.string().description("CORRECTION | SCRAP | BLOCK | UNBLOCK"), reasonCode: t.string().description("Reason the adjustment is being made"), adjustmentDate: t .string({ optional: true }) .description("Adjustment date (YYYY-MM-DD); defaults to today"), lines: t .object( { itemId: t.string().description("Item being adjusted"), storageLocationId: t.string().description("Storage location being adjusted"), quantity: t.string().description("Positive quantity"), adjustmentDirection: t .string({ optional: true }) .description("INCREASE | DECREASE — required for CORRECTION"), fromStockCategory: t .string({ optional: true }) .description("AVAILABLE | BLOCKED — source category for SCRAP (default AVAILABLE)"), unitCost: t.string({ optional: true }).description("Optional unit cost override"), }, { array: true }, ) .description("Adjustment lines"), }, body: async (context) => { const ctx = createContext(context); const db = getDB("main-db"); return db .transaction() .execute(async (trx) => { if (context.input.lines.length === 0) { throw new DomainError("At least one adjustment line is required."); } const result = await invModulesExport.commands.createStockAdjustment( trx, { header: { adjustmentType: context.input.adjustmentType as | "CORRECTION" | "SCRAP" | "BLOCK" | "UNBLOCK", reasonCode: context.input.reasonCode, adjustmentDate: context.input.adjustmentDate ? new Date(context.input.adjustmentDate) : new Date(), }, lines: context.input.lines.map((line) => ({ itemId: line.itemId, storageLocationId: line.storageLocationId, quantity: line.quantity, adjustmentDirection: (line.adjustmentDirection as "INCREASE" | "DECREASE" | undefined) ?? undefined, fromStockCategory: (line.fromStockCategory as "AVAILABLE" | "BLOCKED" | undefined) ?? undefined, unitCost: line.unitCost ?? undefined, })), }, ctx, ); if (!result.ok) { switch (result.error.code) { case "INVENTORY_EMPTY_ADJUSTMENT_LINES": throw new DomainError("At least one adjustment line is required."); case "INVENTORY_REASON_CODE_REQUIRED": throw new DomainError("A reason code is required."); case "INVENTORY_ITEM_NOT_FOUND": throw new DomainError(`Item not found: ${result.error.message}`); case "INVENTORY_ITEM_NOT_ACTIVE": throw new DomainError(`Item is not active: ${result.error.message}`); case "INVENTORY_STORAGE_LOCATION_NOT_FOUND": throw new DomainError(`Storage location not found: ${result.error.message}`); case "INVENTORY_STORAGE_LOCATION_NOT_ACTIVE": throw new DomainError(`Storage location is not active: ${result.error.message}`); case "INVENTORY_INVALID_QUANTITY": throw new DomainError(`Invalid quantity: ${result.error.message}`); case "INVENTORY_INVALID_FROM_STOCK_CATEGORY": throw new DomainError(`Invalid source stock category: ${result.error.message}`); 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.stockAdjustment.id, status: result.value.stockAdjustment.status, }; }) .catch((err: unknown) => { if (err instanceof DomainError) throw err; throw new Error("Failed to create the stock adjustment", { cause: err }); }); }, output: t .object({ id: t.string().description("Stock adjustment ID"), status: t.string().description("Document status (DRAFT)"), }) .description("CreateStockAdjustment response"), });