/** Keys used to pick one inventory row when an item has multiple stock-UOM buckets. */ export type InventoryBucketPick = { itemId?: number | null; itemCode?: string | null; itemName?: string | null; uomId?: number | null; stockUomId?: number | null; uom?: string | null; shortUom?: string | null; stockUom?: string | null; }; export type InventoryBucketRow = { itemId?: number | null; itemCode?: string | null; itemName?: string | null; stockUomId?: number | null; uomCode?: string | null; uomUdfudesc?: string | null; uomShortDesc?: string | null; availableQty?: number | null; onHandQty?: number | null; unavailableQty?: number | null; }; const norm = (s?: string | null) => (s ?? "").trim().toLowerCase(); /** Available = onHand − unavailable. Do not treat 0 as missing (`||` is wrong). */ export function inventoryAvailableQty(inv: InventoryBucketRow): number { if (inv.availableQty != null && !Number.isNaN(Number(inv.availableQty))) { return Number(inv.availableQty); } return Number(inv.onHandQty ?? 0) - Number(inv.unavailableQty ?? 0); } function itemMatches(inv: InventoryBucketRow, pick: InventoryBucketPick): boolean { if (pick.itemId != null && inv.itemId != null) { return Number(inv.itemId) === Number(pick.itemId); } if (pick.itemCode && inv.itemCode) { return inv.itemCode === pick.itemCode; } if (pick.itemName && inv.itemName) { return inv.itemName === pick.itemName; } return false; } function uomMatches(inv: InventoryBucketRow, pick: InventoryBucketPick): boolean { const pickUomId = pick.stockUomId ?? pick.uomId; if (pickUomId != null && inv.stockUomId != null) { return Number(inv.stockUomId) === Number(pickUomId); } const labels = [pick.uom, pick.shortUom, pick.stockUom].map(norm).filter(Boolean); if (labels.length === 0) return true; const invLabels = [inv.uomUdfudesc, inv.uomShortDesc, inv.uomCode].map(norm); return labels.some((l) => invLabels.includes(l)); } export function matchInventoryBucket( inventories: InventoryBucketRow[], pick: InventoryBucketPick, ): InventoryBucketRow | undefined { const itemHits = inventories.filter((inv) => itemMatches(inv, pick)); if (itemHits.length === 0) return undefined; const hasUomPick = pick.stockUomId != null || pick.uomId != null || [pick.uom, pick.shortUom, pick.stockUom].some((s) => Boolean(norm(s))); if (hasUomPick) { return itemHits.find((inv) => uomMatches(inv, pick)); } return itemHits[0]; } export function getStockAvailableFromInventories( inventories: InventoryBucketRow[], pick: InventoryBucketPick, ): number { const inv = matchInventoryBucket(inventories, pick); return inv ? inventoryAvailableQty(inv) : 0; }