import refreshTokenHelper from "../../../utils/refreshTokenHelper" import forceLogoutHelper from "../../../utils/forceLogoutHelper" import errorHandlingHelper from "../../../utils/errorHandlingHelper" import fetchHelper from "../../../utils/fetchHelper" const chunkArray = (arr: any[], size: number) => { const chunks: any[] = [] for(let i = 0; i < arr.length; i += size) { chunks.push(arr.slice(i, i + size)) } return chunks } const handleFunc = async (event: any, authToken: any = null) => { let data: any = {} const token = authToken ?? await getTokenHelper(event) const query = getQuery(event) // Cross-org by default: staff manage BOM productions for all customer orgs the // role can read. Pass ?organizationId= to narrow to a single org. const orgFilterId = query.organizationId ? Number(query.organizationId) : null // 1. All active BOM products (role/tenant scoped by the token) const productFilter = `IsBOM eq true and IsActive eq true` const productRes: any = await fetchHelper(event, `models/m_product?$filter=${encodeURIComponent(productFilter)}&$top=1000`, 'GET', token, null) const bomProducts: any[] = productRes?.records || [] if(bomProducts.length === 0) { return { records: [], status: 200, message: '' } } const productById: any = {} for(const p of bomProducts) { productById[p.id] = p } // 2. Open (reserved) order lines for those products — QtyReserved > 0 only exists // on completed, not-yet-shipped sales order lines, which is exactly the open set. const openLines: any[] = [] for(const chunk of chunkArray(bomProducts.map((p: any) => p.id), 20)) { const lineFilter = `(${chunk.map((id: number) => `M_Product_ID eq ${id}`).join(' or ')}) and QtyReserved gt 0 and IsActive eq true` const lineRes: any = await fetchHelper(event, `models/c_orderline?$filter=${encodeURIComponent(lineFilter)}&$expand=C_Order_ID&$orderby=${encodeURIComponent('Created desc')}&$top=500`, 'GET', token, null) for(const line of (lineRes?.records || [])) { const order = line.C_Order_ID if(!order) continue if(order.IsSOTrx !== true) continue if(!['CO', 'IP'].includes(order.DocStatus?.id)) continue if(orgFilterId && Number(order.AD_Org_ID?.id) !== orgFilterId) continue openLines.push(line) } } if(openLines.length === 0) { return { records: [], status: 200, message: '' } } // 3. Existing productions linked to those order lines (any status, to show progress) const productionsByLine: any = {} for(const chunk of chunkArray(openLines.map((l: any) => l.id), 20)) { const prodFilter = `(${chunk.map((id: number) => `C_OrderLine_ID eq ${id}`).join(' or ')})` const prodRes: any = await fetchHelper(event, `models/m_production?$filter=${encodeURIComponent(prodFilter)}&$top=500`, 'GET', token, null) for(const prod of (prodRes?.records || [])) { const lineId = prod.C_OrderLine_ID?.id if(!lineId) continue if(!productionsByLine[lineId]) productionsByLine[lineId] = [] productionsByLine[lineId].push(prod) } } // 4. Build rows with open/missing quantities const records = openLines.map((line: any) => { const order = line.C_Order_ID const product = productById[line.M_Product_ID?.id] || {} const qtyOrdered = Number(line.QtyOrdered ?? 0) const qtyDelivered = Number(line.QtyDelivered ?? 0) const openQty = qtyOrdered - qtyDelivered const productions = (productionsByLine[line.id] || []) .filter((p: any) => !['VO', 'RE'].includes(p.DocStatus?.id)) .map((p: any) => ({ id: p.id, documentNo: p.DocumentNo, docStatusId: p.DocStatus?.id || '', docStatus: p.DocStatus?.identifier || '', isCreated: p.IsCreated?.id === 'Y', qty: Number(p.ProductionQty ?? 0) })) // Split by document state: completed productions are FINISHED (the end // product is already booked in) — only unfinished ones are "in production". const inProductionQty = productions.filter((p: any) => p.docStatusId !== 'CO').reduce((sum: number, p: any) => sum + p.qty, 0) const producedQty = productions.filter((p: any) => p.docStatusId === 'CO').reduce((sum: number, p: any) => sum + p.qty, 0) return { orderLineId: line.id, lineNo: line.Line, orderId: order.id, orderDocumentNo: order.DocumentNo, orderDocStatusId: order.DocStatus?.id || '', dateOrdered: order.DateOrdered, datePromised: order.DatePromised, partnerId: order.C_BPartner_ID?.id || '', partner: order.C_BPartner_ID?.identifier || '', warehouseId: order.M_Warehouse_ID?.id || '', warehouse: order.M_Warehouse_ID?.identifier || '', organizationId: order.AD_Org_ID?.id || '', organization: order.AD_Org_ID?.identifier || '', productId: line.M_Product_ID?.id || '', productValue: product.Value || '', productName: product.Name || line.M_Product_ID?.identifier || '', // FBA relabel code on the order line (alternative barcode Amazon requires) printLabelCode: String(line.printLabelCode ?? line.PrintLabelCode ?? '').trim(), qtyOrdered, qtyDelivered, openQty, inProductionQty, producedQty, missingQty: Math.max(0, openQty - inProductionQty - producedQty), productions } }) records.sort((a: any, b: any) => String(b.dateOrdered).localeCompare(String(a.dateOrdered)) || b.orderId - a.orderId) data = { records, status: 200, message: '' } 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) { data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) forceLogoutHelper(event, data) } } return data })