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 fetchHelper from "../../utils/fetchHelper" const handleFunc = async (event: any, authToken: any = null) => { let data: any = {} const token = authToken ?? await getTokenHelper(event) const body = await readBody(event) const searchTerm = body?.search || '' if (!searchTerm) { throw createError({ statusCode: 400, statusMessage: 'Search term is required' }) } // Search by Value, SKU, or UPC const filter = string.urlEncode( `(Value eq '${searchTerm}' OR SKU eq '${searchTerm}' OR UPC eq '${searchTerm}') AND (isActive eq true)` ) const res: any = await fetchHelper( event, `models/m_product?$filter=${filter}`, 'GET', token, null ) if (res?.records && res.records.length > 0) { const product = res.records[0] // Fetch storages for this product with locator and warehouse info expanded const storagesRes: any = await fetchHelper( event, `models/m_storage?$filter=${string.urlEncode('M_Product_ID eq ' + product.id)}&$expand=${string.urlEncode('M_Locator_ID,M_Warehouse')}&$select=${string.urlEncode('QtyOnHand,M_Locator_ID')}&$top=1000`, 'GET', token, null ) const storages = Array.isArray(storagesRes?.records) ? storagesRes.records : [] // Aggregate total qty on hand and per-locator quantities let qtyonhand = 0 const locatorMap: Record = {} let warehouseIdFromStorage = null for (const s of storages) { const qty = Number(s?.QtyOnHand ?? 0) if (!qty) continue qtyonhand += qty const locObj = s?.M_Locator_ID || s?.m_locator_id || {} const locId = locObj?.id ?? String(locObj) ?? 'unknown' const locCode = locObj?.Value || locObj?.identifier || locObj?.Name || locObj?.value || String(locId) // Extract warehouse ID from M_Warehouse array const warehouseArray = s?.M_Warehouse || s?.m_warehouse || [] const locWarehouseId = Array.isArray(warehouseArray) && warehouseArray.length > 0 ? warehouseArray[0]?.id : null // Extract organization ID and X, Y, Z directly from expanded locator const locOrgId = locObj?.AD_Org_ID?.id || locObj?.AD_Org_ID || null const locX = locObj?.X || undefined const locY = locObj?.Y || undefined const locZ = locObj?.Z || undefined // Capture warehouse ID from first locator with inventory if (!warehouseIdFromStorage && locWarehouseId) { warehouseIdFromStorage = locWarehouseId } if (!locatorMap[locId]) { locatorMap[locId] = { id: locId, code: locCode, qtyOnHand: 0, warehouseId: locWarehouseId, orgId: locOrgId, x: locX, y: locY, z: locZ } } locatorMap[locId].qtyOnHand += qty } const productOrgId = product.AD_Org_ID?.id || product.AD_Org_ID const warehouseId = body?.warehouseId let locators = Object.values(locatorMap) .filter(l => l.qtyOnHand > 0 && (!l.orgId || l.orgId === productOrgId)) .sort((a, b) => b.qtyOnHand - a.qtyOnHand) // Fetch locators for the product's organization in the warehouse let availableLocators: any[] = [] console.log('[product-lookup] warehouseId:', warehouseId, 'productOrgId:', productOrgId) if (warehouseId || productOrgId) { // Build filter: use warehouse + org when they match, otherwise just org // This handles the case where the product's org differs from the session org/warehouse let locatorFilter = 'IsActive eq true' if (warehouseIdFromStorage) { // Use the warehouse from the product's actual storage (most accurate) locatorFilter = 'M_Warehouse_ID eq ' + warehouseIdFromStorage + ' AND AD_Org_ID eq ' + productOrgId + ' AND ' + locatorFilter } else if (warehouseId && productOrgId) { // Try with warehouse + org when both are available locatorFilter = 'M_Warehouse_ID eq ' + warehouseId + ' AND AD_Org_ID eq ' + productOrgId + ' AND ' + locatorFilter } else if (productOrgId) { // Fallback to org-only filter when no warehouse is available locatorFilter = 'AD_Org_ID eq ' + productOrgId + ' AND ' + locatorFilter } let locatorsRes: any = await fetchHelper( event, `models/m_locator?$filter=${string.urlEncode(locatorFilter)}&$orderby=${string.urlEncode('IsDefault desc, PriorityNo desc')}`, 'GET', token, null ) console.log('[product-lookup] Locators for product org (with warehouse):', locatorsRes?.records?.length || 0) // If no locators found with warehouse filter and product org differs, try without warehouse filter if ((!locatorsRes?.records || locatorsRes.records.length === 0) && productOrgId) { console.log('[product-lookup] Retrying locator search with only org filter for orgId:', productOrgId) locatorsRes = await fetchHelper( event, `models/m_locator?$filter=${string.urlEncode('AD_Org_ID eq ' + productOrgId + ' AND IsActive eq true')}&$orderby=${string.urlEncode('IsDefault desc, PriorityNo desc')}`, 'GET', token, null ) console.log('[product-lookup] Locators for product org (org only):', locatorsRes?.records?.length || 0) } if (locatorsRes?.records && locatorsRes.records.length > 0) { availableLocators = locatorsRes.records.map((loc: any) => ({ id: loc.id, code: loc.Value || loc.identifier || loc.Name, qtyOnHand: 0, isDefault: loc.IsDefault || false, warehouseId: loc.M_Warehouse_ID?.id || loc.M_Warehouse_ID, orgId: loc.AD_Org_ID?.id || loc.AD_Org_ID })) // If no locators with qty, use the default locator or first locator as initial if (locators.length === 0) { const defaultLocator = availableLocators.find(l => l.isDefault) || availableLocators[0] if (defaultLocator) { locators = [defaultLocator] } } } } // Extract warehouse ID - prioritize from storage, then availableLocators, then session fallback let warehouseIdForProduct = warehouseIdFromStorage if (!warehouseIdForProduct && availableLocators.length > 0 && availableLocators[0].warehouseId) { warehouseIdForProduct = availableLocators[0].warehouseId } if (!warehouseIdForProduct) { warehouseIdForProduct = body?.warehouseId } data = { id: product.id, Name: product.Name, Value: product.Value, SKU: product.SKU, UPC: product.UPC, Description: product.Description, ImageURL: product.ImageURL || null, labelPrinterId: product.labelprinter_rma?.id || null, labelPrinter: product.labelprinter_rma?.identifier || null, qtyonhand: qtyonhand, qtyreserved: product.QtyReserved || 0, qtyavailable: product.QtyAvailable || 0, qtyordered: product.QtyOrdered || 0, // Sales orders (customer orders) qtypurchased: product.QtyOrdered_PO || 0, // Purchase orders Weight: product.Weight || 0, ShelfWidth: product.ShelfWidth || 0, ShelfHeight: product.ShelfHeight || 0, ShelfDepth: product.ShelfDepth || 0, Strapi_Product_documentId: product.Strapi_Product_documentId || null, AD_Org_ID: product.AD_Org_ID || null, M_Warehouse_ID: warehouseIdForProduct, locators: locators, availableLocators: availableLocators } } else { throw createError({ statusCode: 404, statusMessage: 'Product not found' }) } return data } export default defineEventHandler(async (event) => { let data: any = {} try { data = await handleFunc(event) } catch(err: any) { try { let authToken: any = await refreshTokenHelper(event) data = await handleFunc(event, authToken) } catch(error: any) { data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) forceLogoutHelper(event, data) throw createError({ statusCode: err?.statusCode || 500, statusMessage: err?.statusMessage || 'Error searching product', data: data }) } } return data })