import { string } from 'alga-js' import refreshTokenHelper from '../../utils/refreshTokenHelper' import forceLogoutHelper from '../../utils/forceLogoutHelper' import errorHandlingHelper from '../../utils/errorHandlingHelper' import fetchHelper from '../../utils/fetchHelper' import getTokenHelper from '../../utils/getTokenHelper' const handleFunc = async (event: any, authToken: any = null) => { const token = authToken ?? await getTokenHelper(event) const query = getQuery(event) const role = getCookie(event, 'logship_role') const cookieOrgId = getCookie(event, 'logship_organization_id') const newUserRole = JSON.parse(role ?? '{}') const queryOrgId = query?.org_id as string const orgId = !newUserRole?.IsClientAdministrator ? (queryOrgId || cookieOrgId) : queryOrgId if (!orgId) { return { status: 400, message: 'Organization ID is required', locators: [], products: [], totalLocators: 0, totalProducts: 0 } } const now = new Date() const queryMonth = query?.month ? Number(query.month) : null const queryYear = query?.year ? Number(query.year) : null const selectedYear = queryYear || now.getFullYear() const selectedMonth = queryMonth || (now.getMonth() + 1) const startDate = `${selectedYear}-${String(selectedMonth).padStart(2, '0')}-01` const nextMonth = selectedMonth === 12 ? 1 : selectedMonth + 1 const nextYear = selectedMonth === 12 ? selectedYear + 1 : selectedYear const endDate = `${nextYear}-${String(nextMonth).padStart(2, '0')}-01` // Fetch ALL transactions up to end of selected month (signed MovementQty so we can // compute the running balance per (locator, product) at the start of the month). const filter = string.urlEncode( `MovementDate lt '${endDate}' and AD_Org_ID eq ${orgId}` ) const select = string.urlEncode('M_Locator_ID,MovementDate,MovementQty,M_Product_ID,AD_Org_ID') const expand = string.urlEncode('M_Product_ID($select=Name,Value,SKU)') const orderby = string.urlEncode('MovementDate') const pageSize = 5000 let skip = 0 let allRecords: any[] = [] let orgName = '' while (true) { const res: any = await fetchHelper( event, `models/m_transaction?$select=${select}&$expand=${expand}&$filter=${filter}&$orderby=${orderby}&$top=${pageSize}&$skip=${skip}`, 'GET', token, null ) const records = res?.records || [] if (records.length > 0 && !orgName) { orgName = records[0]?.AD_Org_ID?.identifier || '' } allRecords = allRecords.concat(records) if (records.length < pageSize) break skip += pageSize if (skip > 500000) break } // Group per (locator, product): cumulative qty up to end of M, and any movement in M. // A pair (L, P) is "stored during M" iff end-of-M qty >= 1 OR there was any movement in M // (any movement in M proves the item was physically there at some point — incoming brings // it in, outgoing/sale means it had to have been there to leave). type Group = { locatorId: number locatorName: string productId: number productName: string productValue: string productSku: string endQty: number firstDateInM: string | null lastDateInM: string | null hadMovementInM: boolean } const groups = new Map() for (const rec of allRecords) { const locator = rec?.M_Locator_ID const product = rec?.M_Product_ID if (!locator?.id || !product?.id) continue const date = rec?.MovementDate ? String(rec.MovementDate).slice(0, 10) : null if (!date) continue const qty = Number(rec?.MovementQty || 0) const inM = date >= startDate && date < endDate const key = `${locator.id}-${product.id}` let g = groups.get(key) if (!g) { g = { locatorId: locator.id, locatorName: locator.identifier || `Locator-${locator.id}`, productId: product.id, productName: product.Name || product.identifier || `Product-${product.id}`, productValue: product.Value || '', productSku: product.SKU || '', endQty: 0, firstDateInM: null, lastDateInM: null, hadMovementInM: false } groups.set(key, g) } g.endQty += qty if (inM) { g.hadMovementInM = true if (!g.firstDateInM || date < g.firstDateInM) g.firstDateInM = date if (!g.lastDateInM || date > g.lastDateInM) g.lastDateInM = date } } // Aggregate to unique locators and products const locatorMap = new Map() const productMap = new Map() const upsert = (map: Map, id: number, base: Record, g: Group, hasStock: boolean) => { let entry = map.get(id) if (!entry) { entry = { id, ...base, firstDate: null, hasStock: false, hadMovementInM: false } map.set(id, entry) } entry.hasStock = entry.hasStock || hasStock entry.hadMovementInM = entry.hadMovementInM || g.hadMovementInM if (g.firstDateInM) { if (!entry.firstDate || g.firstDateInM < entry.firstDate) entry.firstDate = g.firstDateInM } } for (const g of groups.values()) { const hasStock = g.endQty >= 1 const used = hasStock || g.hadMovementInM if (!used) continue upsert(locatorMap, g.locatorId, { name: g.locatorName }, g, hasStock) upsert(productMap, g.productId, { name: g.productName, value: g.productValue, sku: g.productSku }, g, hasStock) } const withSource = (entry: any) => ({ ...entry, source: entry.hasStock && entry.hadMovementInM ? 'both' : entry.hasStock ? 'on-hand' : 'movement-only' }) const sortByName = (a: any, b: any) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }) const locators = Array.from(locatorMap.values()).map(withSource).sort(sortByName) const products = Array.from(productMap.values()).map(withSource).sort(sortByName) return { status: 200, locators, products, totalLocators: locators.length, totalProducts: products.length, selectedMonth, selectedYear, startDate, endDate, orgId: Number(orgId), orgName } } export default defineEventHandler(async (event) => { let data: any = {} try { data = await handleFunc(event) } catch (err: any) { try { const authToken: any = await refreshTokenHelper(event) data = await handleFunc(event, authToken) } catch (error: any) { data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) forceLogoutHelper(event, data) } } return data })