import refreshTokenHelper from "../../../utils/refreshTokenHelper" import errorHandlingHelper from "../../../utils/errorHandlingHelper" import fetchHelper from "../../../utils/fetchHelper" import { getShopifyNewAccessToken, shopifyNewGraphQL, parseShopifyGid, buildShopifyGid } from "../../../utils/shopifyNewAuthHelper" // POST /api/shopify-new/stocks/sync // Syncs local qty to Shopify via GraphQL inventorySetQuantities mutation // Body: { orderSourceId, variantId (SKU), inventoryItemId?, quantity, sku?, productId?, locationId? } // Returns same response shape as /api/shopify/stocks/sync for frontend compatibility const handleFunc = async (event: any, authToken: any = null) => { const token = authToken ?? await getTokenHelper(event) const body = await readBody(event) const { orderSourceId, variantId, quantity, sku, productId, locationId: bodyLocationId } = body let { inventoryItemId } = body if(!orderSourceId) { return { status: 400, message: 'orderSourceId is required', success: false } } if(!variantId) { return { status: 400, message: 'variantId (SKU) is required', success: false } } if(quantity === undefined || quantity === null) { return { status: 400, message: 'quantity is required', success: false } } // Load order source to verify it's shopify-new and get credentials const os: any = await fetchHelper(event, `models/c_ordersource/${orderSourceId}`, 'GET', token, null) if(!os) { return { status: 404, message: 'Order Source not found', success: false } } if(os?.Marketplace?.identifier !== 'shopify-new') { return { status: 400, message: 'Selected Order Source is not Shopify-New', success: false } } try { const { accessToken, graphqlUrl } = await getShopifyNewAccessToken(os) const targetQty = Number(quantity) const skuToFind = String(variantId).toLowerCase() // If inventoryItemId is provided and already a GID, use it directly if(inventoryItemId) { console.log(`shopify-new: Using provided inventoryItemId: ${inventoryItemId} for SKU "${variantId}"`) } // If inventoryItemId is not provided, look up variant by SKU via GraphQL if(!inventoryItemId) { console.log(`shopify-new: Searching for SKU "${skuToFind}" in Shopify...`) const variantQuery = `query($skuQuery: String!) { productVariants(first: 1, query: $skuQuery) { edges { node { id sku inventoryItem { id } product { id } } } } }` const variantRes = await shopifyNewGraphQL(graphqlUrl, accessToken, variantQuery, { skuQuery: `sku:${variantId}` }) const variantEdges = variantRes?.data?.productVariants?.edges || [] if(variantEdges.length > 0) { inventoryItemId = variantEdges[0].node?.inventoryItem?.id console.log(`shopify-new: Found SKU "${skuToFind}" -> inventoryItemId: ${inventoryItemId}`) } } if(!inventoryItemId) { return { status: 400, success: false, message: `Could not find variant with SKU "${variantId}" in Shopify.`, variantId } } // Resolve location ID // Priority: body param > order source config > fetch from API const osLocationId = os?.Shopify_Location_ID || null let effectiveLocationId = bodyLocationId || osLocationId if(!effectiveLocationId) { // Try to fetch first active location try { const locQuery = `{ locations(first: 5) { edges { node { id name isActive } } } }` const locRes = await shopifyNewGraphQL(graphqlUrl, accessToken, locQuery) const locationEdges = locRes?.data?.locations?.edges || [] const activeLocation = locationEdges.find((e: any) => e.node.isActive) || locationEdges[0] if(activeLocation) { effectiveLocationId = parseShopifyGid(activeLocation.node.id) console.log(`shopify-new: Using location ${effectiveLocationId} (${activeLocation.node.name})`) } } catch (locErr: any) { console.error('shopify-new: Failed to fetch locations:', locErr?.message) return { status: 400, success: false, message: 'Could not determine Shopify location. Please configure Shopify_Location_ID on the order source.', variantId } } } if(!effectiveLocationId) { return { status: 400, success: false, message: 'No Shopify location found. Please configure Shopify_Location_ID on the order source.', variantId } } // Build GIDs for the mutation const inventoryItemGid = buildShopifyGid('InventoryItem', inventoryItemId.includes('gid://') ? parseShopifyGid(inventoryItemId) : inventoryItemId) const locationGid = buildShopifyGid('Location', effectiveLocationId) // Set inventory via inventorySetQuantities mutation const setMutation = `mutation inventorySetQuantities($input: InventorySetQuantitiesInput!) { inventorySetQuantities(input: $input) { inventoryAdjustmentGroup { createdAt changes { name delta quantityAfterChange } } userErrors { field message } } }` const setVariables = { input: { name: "available", reason: "correction", quantities: [ { inventoryItemId: inventoryItemGid, locationId: locationGid, quantity: targetQty } ] } } console.log('shopify-new: inventory set request:', JSON.stringify(setVariables)) const result = await shopifyNewGraphQL(graphqlUrl, accessToken, setMutation, setVariables) // Check for userErrors const userErrors = result?.data?.inventorySetQuantities?.userErrors || [] if(userErrors.length > 0) { const errMsg = userErrors.map((e: any) => `${e.field}: ${e.message}`).join('; ') return { status: 400, success: false, message: `Shopify error: ${errMsg}`, variantId, shopifyResponse: result?.data } } // Get the updated quantity from the response const changes = result?.data?.inventorySetQuantities?.inventoryAdjustmentGroup?.changes || [] const updatedStock = changes.length > 0 ? changes[0].quantityAfterChange : targetQty console.log('shopify-new: inventory set response:', JSON.stringify(result?.data).slice(0, 800)) return { status: 200, success: true, message: `Stock set to ${updatedStock} for SKU "${variantId}" at location ${effectiveLocationId}`, variantId, inventoryItemId: inventoryItemGid, quantity: targetQty, updatedStock, locationId: effectiveLocationId, shopifyResponse: result?.data } } catch (err: any) { const errData = err?.data || err const errMsg = errData?.message || errData?.errors || err?.message || 'Failed to sync stock to Shopify-New' const errStatus = err?.status || err?.response?.status || errData?.status || 500 console.error('shopify-new stock sync error:', { variantId, quantity, error: errData }) return { status: errStatus, success: false, message: typeof errMsg === 'object' ? JSON.stringify(errMsg) : errMsg, error: errData, variantId } } } export default defineEventHandler(async (event) => { try { return await handleFunc(event) } catch (err: any) { try { const authToken = await refreshTokenHelper(event) return await handleFunc(event, authToken) } catch (error: any) { const data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) if([401, 402, 403, 407].includes(Number(data.status))) { //@ts-ignore setCookie(event, 'user', null) } return { ...data, success: false } } } })