import { string } from 'alga-js' import refreshTokenHelper from "../../utils/refreshTokenHelper" import forceLogoutHelper from "../../utils/forceLogoutHelper" import errorHandlingHelper from "../../utils/errorHandlingHelper" import getTokenHelper from "../../utils/getTokenHelper" import { odataQuote } from "../../utils/odataHelper" const handleFunc = async (event: any, authToken: any = null) => { const token = authToken ?? await getTokenHelper(event) const body = await readBody(event) const locatorValue = body?.locatorValue || '' if (!locatorValue) { throw createError({ statusCode: 400, statusMessage: 'Locator value is required' }) } // Step 1: Find all locators matching the value (across organizations) const locatorFilter = string.urlEncode(`Value eq '${odataQuote(locatorValue)}' AND IsActive eq true`) const locatorsRes: any = await event.context.fetch( `models/m_locator?$filter=${locatorFilter}`, 'GET', token, null ) if (!locatorsRes?.records || locatorsRes.records.length === 0) { throw createError({ statusCode: 404, statusMessage: 'Locator not found' }) } const locators = locatorsRes.records // Use a map to group articles by productId + orgId (unique key) const articleMap: Map = new Map() // Step 2: ONE storage query for all matching locators (was one query per // locator), with an explicit $top — without it iDempiere's default page // size silently truncated high-SKU locators. const locatorById = new Map(locators.map((l: any) => [Number(l.id), l])) const storageFilter = string.urlEncode( '(' + locators.map((l: any) => `M_Locator_ID eq ${Number(l.id)}`).join(' OR ') + ') AND QtyOnHand gt 0' ) const storageRes: any = await event.context.fetch( `models/M_StorageOnHand?$filter=${storageFilter}&$expand=${string.urlEncode('M_Product_ID')}&$top=5000`, 'GET', token, null ) { if (storageRes?.records && storageRes.records.length > 0) { for (const storage of storageRes.records) { const locator = locatorById.get(Number(storage.M_Locator_ID?.id ?? storage.M_Locator_ID)) if (!locator) continue const locatorId = locator.id const orgId = locator.AD_Org_ID?.id || locator.AD_Org_ID const orgName = locator.AD_Org_ID?.identifier || locator.AD_Org_ID?.Name || 'Unknown' const warehouseId = locator.M_Warehouse_ID?.id || locator.M_Warehouse_ID const warehouseName = locator.M_Warehouse_ID?.identifier || locator.M_Warehouse_ID?.Name || 'Unknown' const product = storage.M_Product_ID || {} const qtyOnHand = Number(storage.QtyOnHand) || 0 if (qtyOnHand > 0 && product.id) { // Create unique key: productId + orgId const key = `${product.id}-${orgId}` if (articleMap.has(key)) { // Sum up the quantity const existing = articleMap.get(key) existing.qtyOnHand += qtyOnHand } else { // Add new article entry articleMap.set(key, { // Product info productId: product.id, productName: product.Name || 'Unknown Product', productValue: product.Value || '', productSKU: product.SKU || '', productMPN: product.mpn || product.MPN || '', productUPC: product.UPC || '', productWeight: product.Weight || 0, // Strapi info for image strapiProductDocumentId: product.Strapi_Product_documentId || null, // Quantity (will be summed) qtyOnHand: qtyOnHand, // Locator info locatorId: locatorId, locatorValue: locator.Value || '', // Organization info orgId: orgId, orgName: orgName, // Warehouse info warehouseId: warehouseId, warehouseName: warehouseName }) } } } } } // Convert map to array const articles = Array.from(articleMap.values()) // Sort by organization name, then by product name articles.sort((a, b) => { if (a.orgName !== b.orgName) { return a.orgName.localeCompare(b.orgName) } return a.productName.localeCompare(b.productName) }) return { locatorValue: locatorValue, totalArticles: articles.length, articles: articles } } export default defineEventHandler(async (event) => { // Auth-only retry: a business 404 no longer triggers a token refresh. return await withAuthRetry(event, handleFunc) })