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 || '' const includeAllOrgs = body?.includeAllOrgs || false const filterOrgId = body?.organizationId || null if (!searchTerm) { throw createError({ statusCode: 400, statusMessage: 'Search term is required' }) } // Search by Value, SKU, UPC, or MPN (case-insensitive) const lowerSearchTerm = searchTerm.toLowerCase() let filterExpr = `(tolower(Value) eq '${lowerSearchTerm}' OR tolower(SKU) eq '${lowerSearchTerm}' OR tolower(UPC) eq '${lowerSearchTerm}' OR tolower(mpn) eq '${lowerSearchTerm}') AND (isActive eq true)` // Filter by organization if provided (include org 0 = system-wide products) if (filterOrgId && !includeAllOrgs) { filterExpr += ` AND (AD_Org_ID eq ${filterOrgId} OR AD_Org_ID eq 0)` } const filter = string.urlEncode(filterExpr) const res: any = await fetchHelper( event, `models/m_product?$filter=${filter}`, 'GET', token, null ) if (res?.records && res.records.length > 0) { // Multiple products found — return duplicate list for user to choose if (res.records.length > 1) { const duplicates = [] for (const p of res.records) { // Fetch qtyonhand for each product let qtyonhand = 0 try { const storagesRes: any = await fetchHelper( event, `models/m_storage?$filter=${string.urlEncode('M_Product_ID eq ' + p.id)}&$top=1000`, 'GET', token, null ) const storages = Array.isArray(storagesRes?.records) ? storagesRes.records : [] for (const s of storages) { qtyonhand += Number(s?.QtyOnHand ?? 0) } } catch (_e) { /* ignore */ } duplicates.push({ id: p.id, Name: p.Name, Value: p.Value, SKU: p.SKU, UPC: p.UPC, MPN: p.mpn || null, ASIN: p.asin || null, AD_Org_ID: p.AD_Org_ID || null, qtyonhand: qtyonhand }) } data = { duplicates: duplicates, searchTerm: searchTerm } return data } 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')}&$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 locX = locObj?.X const locY = locObj?.Y const locZ = locObj?.Z const locCode = (locX || locY || locZ) ? [locX, locY, locZ].filter(Boolean).join(' - ') : 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 from expanded locator const locOrgId = locObj?.AD_Org_ID?.id || locObj?.AD_Org_ID || null // 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 && (includeAllOrgs || !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[] = [] let orgWarehouseIds: any[] = [] console.log('[product-lookup] warehouseId:', warehouseId, 'productOrgId:', productOrgId) if (warehouseId || productOrgId) { // Find warehouses that belong to the product's org // This ensures we only show locators from correctly owned warehouses let locatorFilter = 'IsActive eq true' if (productOrgId) { const whRes: any = await fetchHelper( event, `models/m_warehouse?$filter=${string.urlEncode('AD_Org_ID eq ' + productOrgId + ' AND IsActive eq true')}`, 'GET', token, null ) orgWarehouseIds = (whRes?.records || []).map((w: any) => w.id).filter(Boolean) console.log('[product-lookup] Warehouses for org', productOrgId, ':', orgWarehouseIds) } if (orgWarehouseIds.length > 0) { // Use warehouses that belong to the product's org const whFilterParts = orgWarehouseIds.map((id: any) => 'M_Warehouse_ID eq ' + id) locatorFilter = '(' + whFilterParts.join(' OR ') + ') AND AD_Org_ID eq ' + productOrgId + ' AND ' + locatorFilter } else if (productOrgId) { // Fallback: org-only filter locatorFilter = 'AD_Org_ID eq ' + productOrgId + ' AND ' + locatorFilter } else if (warehouseId) { // No org available, use session warehouse locatorFilter = 'M_Warehouse_ID eq ' + warehouseId + ' 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.X || loc.Y || loc.Z) ? [loc.X, loc.Y, loc.Z].filter(Boolean).join(' - ') : (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 org's warehouses, then availableLocators, then session fallback // Use the org's own warehouse (not warehouseIdFromStorage which may be wrong) let warehouseIdForProduct = orgWarehouseIds.length > 0 ? orgWarehouseIds[0] : null 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, MPN: product.mpn || null, ASIN: product.asin || null, 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 })