/** * POST /api/orders/stock-lines body: { orgId, priceListId?, warehouseId? } * * Every active article of the organization that has stock (QtyOnHand > 0 summed over all locators), * with its price from the given price list (newest version) — the "Alle Artikel mit Bestand" action * of the order line table (a merchant leaving us: one order with everything in stock, qty = on hand). * `warehouseId` restricts the stock to one warehouse's locators (omitted = every warehouse). * Return locators (non-shippable) are reported separately (`returnQtyOnHand`) but included in `qtyOnHand`. * * -> { status, lines: [{ productId, name, value, sku, ean, uom, uomId, qtyOnHand, returnQtyOnHand, priceList, priceStd, hasPrice, isBom }] } */ import { string } from 'alga-js' import fetchHelper from '../../utils/fetchHelper' import getTokenHelper from '../../utils/getTokenHelper' import refreshTokenHelper from '../../utils/refreshTokenHelper' import errorHandlingHelper from '../../utils/errorHandlingHelper' import { fetchProductsWithStorage } from '../../utils/productStorageHelper' import getReturnLocatorIds from '../../utils/returnLocatorHelper' const num = (v: any) => (typeof v === 'number' ? v : parseFloat(v) || 0) const flag = (v: any) => v === true || v === 'Y' const handleFunc = async (event: any, body: any, authToken: string | null = null) => { const token = authToken ?? await getTokenHelper(event) const orgId = Number(body?.orgId) if (!orgId) return { status: 400, message: 'orgId missing', lines: [] } const priceListId = Number(body?.priceListId) || 0 const warehouseId = Number(body?.warehouseId) || 0 // 1) products of the org + their storage rows (paged join in productStorageHelper, no child $expand) const productUrl = `models/m_product?$select=${string.urlEncode('M_Product_ID,Name,Value,SKU,UPC,IsBOM,isJtlBom,isEnabledBom,C_UOM_ID')}&$expand=${string.urlEncode('C_UOM_ID($select=Name)')}&$filter=${string.urlEncode(`AD_Org_ID eq ${orgId} and IsActive eq true`)}&$orderby=${string.urlEncode('Name asc')}&$top=5000` const [res, returnLocatorIds] = await Promise.all([ fetchProductsWithStorage(event, token, productUrl), getReturnLocatorIds(event, token, null).catch(() => new Set()) ]) const stocked: any[] = [] for (const p of (res?.records || [])) { let qtyOnHand = 0, returnQtyOnHand = 0 for (const s of (p?.M_Storage || [])) { if (warehouseId) { const wh = Number(s?.M_Locator_ID?.M_Warehouse_ID?.id ?? s?.M_Warehouse_ID?.id ?? s?.M_Warehouse_ID) if (Number.isFinite(wh) && wh > 0 && wh !== warehouseId) continue } const q = num(s?.QtyOnHand) qtyOnHand += q const loc = Number(s?.M_Locator_ID?.id ?? s?.M_Locator_ID) if (Number.isFinite(loc) && returnLocatorIds.has(loc)) returnQtyOnHand += q } if (qtyOnHand <= 0) continue stocked.push({ productId: Number(p.id || p.M_Product_ID), name: p.Name || '', value: p.Value || '', sku: p.SKU || '', ean: p.UPC || '', uom: p?.C_UOM_ID?.Name || p?.C_UOM_ID?.identifier || 'Each', uomId: p?.C_UOM_ID?.id || 100, qtyOnHand: Math.round(qtyOnHand * 1000) / 1000, returnQtyOnHand: Math.round(returnQtyOnHand * 1000) / 1000, priceList: 0, priceStd: 0, hasPrice: false, isBom: flag(p.IsBOM) || (flag(p.isJtlBom) && !flag(p.isEnabledBom)) }) } // 2) prices from the newest version of the price list (chunked `or` filters, no child $expand) if (priceListId && stocked.length) { const ver: any = await fetchHelper(event, `models/m_pricelist_version?$filter=${string.urlEncode(`M_PriceList_ID eq ${priceListId}`)}&$orderby=${string.urlEncode('M_PriceList_Version_ID desc')}&$top=1`, 'GET', token, null) const versionId = Number(ver?.records?.[0]?.id) || 0 if (versionId) { const byId = new Map(stocked.map((l) => [l.productId, l])) const ids = stocked.map((l) => l.productId) for (let i = 0; i < ids.length; i += 60) { const chunk = ids.slice(i, i + 60) const f = `M_PriceList_Version_ID eq ${versionId} and (${chunk.map((id) => `M_Product_ID eq ${id}`).join(' or ')})` const pr: any = await fetchHelper(event, `models/m_productprice?$select=${string.urlEncode('M_Product_ID,PriceList,PriceStd')}&$filter=${string.urlEncode(f)}&$top=500`, 'GET', token, null) for (const row of (pr?.records || [])) { const l = byId.get(Number(row?.M_Product_ID?.id ?? row?.M_Product_ID)) if (!l) continue l.priceList = num(row.PriceList); l.priceStd = num(row.PriceStd); l.hasPrice = true } } } } return { status: 200, lines: stocked, orgId, priceListId, warehouseId } } export default defineEventHandler(async (event) => { const body = await readBody(event) let data: any = {} try { data = await handleFunc(event, body) } catch (err: any) { try { const authToken = await refreshTokenHelper(event) data = await handleFunc(event, body, authToken) } catch (error: any) { data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) } } return data })