import refreshTokenHelper from "../../../utils/refreshTokenHelper" import errorHandlingHelper from "../../../utils/errorHandlingHelper" import fetchHelper from "../../../utils/fetchHelper" // GET /api/plentyone/stocks?orderSourceId=123&variationIds=1,2,3 // Fetches current stock from PlentyOne for given variation IDs const handleFunc = async (event: any, authToken: any = null) => { const token = authToken ?? await getTokenHelper(event) const query = getQuery(event) const orderSourceId = query.orderSourceId as string const variationIdsParam = query.variationIds as string if(!orderSourceId) { return { status: 400, message: 'orderSourceId is required' } } if(!variationIdsParam) { return { status: 400, message: 'variationIds is required' } } const variationIds = variationIdsParam.split(',').map(id => id.trim()).filter(Boolean) if(variationIds.length === 0) { return { status: 400, message: 'No valid variationIds provided' } } // Load order source to verify it's PlentyOne 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' } } if(!(os?.Marketplace?.id === '6' || os?.Marketplace?.identifier === 'plentyone')) { return { status: 400, message: 'Selected Order Source is not PlentyOne' } } // Get PlentyOne credentials from order source const baseUrl = os?.marketplace_url || os?.SHOPWARE_URL || '' const clientId = os?.marketplace_key || '' const clientSecret = os?.marketplace_secret || '' if(!baseUrl) { return { status: 400, message: 'No marketplace_url configured for this Order Source' } } if(!clientId || !clientSecret) { return { status: 400, message: 'No marketplace_key or marketplace_secret configured for this Order Source' } } // Get PlentyOne warehouse ID (default: 102) const warehouseId = os?.PlentyOne_Warehouse_ID || 102 try { // Normalize base URL let host = baseUrl.trim() if(!/^https?:\/\//i.test(host)) host = `https://${host}` host = host.replace(/\/$/, '') // Remove /rest suffix if present for token endpoint const baseHost = host.replace(/\/rest\/?$/, '') // Get OAuth access token from PlentyOne const tokenUrl = `${baseHost}/rest/login` let accessToken = '' try { const tokenRes: any = await $fetch(tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: { username: clientId, password: clientSecret } }) accessToken = tokenRes?.accessToken || tokenRes?.access_token || '' } catch (tokenErr: any) { console.error('PlentyOne OAuth error:', tokenErr?.data || tokenErr?.message) return { status: 401, message: 'Failed to authenticate with PlentyOne. Check marketplace_key and marketplace_secret.' } } if(!accessToken) { return { status: 401, message: 'No access token received from PlentyOne' } } // Use baseHost for API calls (without /rest suffix, we'll add it) const apiBase = `${baseHost}/rest` // Fetch all stock for the warehouse, then filter by variation IDs // GET /rest/stockmanagement/warehouses/{warehouseId}/stock const stocks: Record = {} const itemIds: Record = {} // Map variationId -> itemId for sync endpoint const errors: Record = {} // Initialize all requested variations with 0 for(const varId of variationIds) { stocks[varId] = 0 } // Create a Set for fast lookup const variationIdSet = new Set(variationIds.map(id => String(id))) // Fetch warehouse stock with pagination let page = 1 const itemsPerPage = 250 let hasMore = true let debugResponse = null while(hasMore) { try { const stockUrl = `${apiBase}/stockmanagement/warehouses/${warehouseId}/stock?page=${page}&itemsPerPage=${itemsPerPage}` const stockRes: any = await $fetch(stockUrl, { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', 'Accept': 'application/json' } }) // Save first response for debugging if(page === 1) { debugResponse = { url: stockUrl, firstItems: Array.isArray(stockRes) ? stockRes.slice(0, 3) : stockRes?.entries ? stockRes.entries.slice(0, 3) : stockRes } } // Parse the response let entries: any[] = [] if(Array.isArray(stockRes)) { entries = stockRes } else if(stockRes?.entries && Array.isArray(stockRes.entries)) { entries = stockRes.entries } // Process entries and match with our variation IDs for(const entry of entries) { const entryVarId = String(entry?.variationId || entry?.variation_id || '') if(variationIdSet.has(entryVarId)) { // Add physical stock (or net stock as fallback) const qty = Number(entry?.stockPhysical || entry?.physicalStock || entry?.stockNet || entry?.netStock || entry?.quantity || 0) stocks[entryVarId] = (stocks[entryVarId] || 0) + qty // Store itemId for sync endpoint if(entry?.itemId) { itemIds[entryVarId] = Number(entry.itemId) } } } // Check if there are more pages if(entries.length < itemsPerPage) { hasMore = false } else { page++ // Safety limit to prevent infinite loops if(page > 100) hasMore = false } } catch (err: any) { console.error('Failed to fetch warehouse stock:', err?.message || err) hasMore = false // If warehouse endpoint fails, try individual variation lookups as fallback if(page === 1) { debugResponse = { error: err?.message, url: `${apiBase}/stockmanagement/warehouses/${warehouseId}/stock` } // Fallback: try to get stock per variation for(const varId of variationIds) { try { const varStockUrl = `${apiBase}/stockmanagement/stock?variationId=${varId}&warehouseId=${warehouseId}` const varRes: any = await $fetch(varStockUrl, { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', 'Accept': 'application/json' } }) if(Array.isArray(varRes) && varRes.length > 0) { stocks[varId] = varRes.reduce((sum: number, e: any) => sum + Number(e?.stockPhysical || e?.physicalStock || e?.stockNet || 0), 0) } else if(varRes?.stockPhysical !== undefined) { stocks[varId] = Number(varRes.stockPhysical) } } catch (varErr: any) { errors[varId] = varErr?.message || 'Failed' } } } } } return { status: 200, stocks, itemIds, // Map variationId -> itemId for sync endpoint errors: Object.keys(errors).length > 0 ? errors : undefined, warehouseId, variationCount: variationIds.length, _debug: debugResponse // Shows raw API response for debugging } } catch (err: any) { const data = errorHandlingHelper(err?.data ?? err, err?.data ?? err) return { status: Number(data?.status || 500), message: data?.message || 'Failed to fetch PlentyOne stocks' } } } 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 } } })