import { ok, type ReadonlyDB } from "@tailor-platform/erp-kit/core"; import { Decimal } from "decimal.js"; import type { DB } from "../generated/kysely-tailordb"; import { getOpenStockReservationQuantity } from "../internal/stockReservation"; export interface GetSiteStockSummaryInput { siteId: string; itemId?: string; } export async function run(db: ReadonlyDB, input: GetSiteStockSummaryInput) { const activeLocationIds = await db .selectFrom("StorageLocation") .select("id") .where("siteId", "=", input.siteId) .where("status", "=", "ACTIVE") .execute(); if (activeLocationIds.length === 0) { return ok({ summary: [] }); } const locationIds = activeLocationIds.map((loc) => loc.id); let query = db .selectFrom("StockLevel") .select("itemId") .where("storageLocationId", "in", locationIds); if (input.itemId) { query = query.where("itemId", "=", input.itemId); } const stockLevels = await query.selectAll().execute(); let reservationQuery = db .selectFrom("StockReservation") .selectAll() .where("siteId", "=", input.siteId) .where("status", "=", "OPEN"); if (input.itemId) { reservationQuery = reservationQuery.where("itemId", "=", input.itemId); } const reservations = (await reservationQuery.execute()) ?? []; const reservedByItem = new Map(); for (const reservation of reservations) { const openReservedQuantity = getOpenStockReservationQuantity(reservation); reservedByItem.set( reservation.itemId, (reservedByItem.get(reservation.itemId) ?? new Decimal(0)).plus(openReservedQuantity), ); } // Aggregate in-memory grouped by itemId const grouped = new Map< string, { itemId: string; totalOnHand: Decimal; totalReserved: Decimal; totalBlocked: Decimal; totalAvailable: Decimal; } >(); for (const sl of stockLevels) { const existing = grouped.get(sl.itemId); const blocked = sl.stockType === "BLOCKED" ? new Decimal(sl.quantity) : new Decimal(0); const onHand = sl.stockType === "IN_TRANSIT" ? new Decimal(0) : new Decimal(sl.quantity); if (existing) { existing.totalOnHand = existing.totalOnHand.plus(onHand); existing.totalBlocked = existing.totalBlocked.plus(blocked); existing.totalAvailable = existing.totalAvailable.plus( sl.stockType === "AVAILABLE" ? new Decimal(sl.quantity) : 0, ); } else { grouped.set(sl.itemId, { itemId: sl.itemId, totalOnHand: onHand, totalReserved: reservedByItem.get(sl.itemId) ?? new Decimal(0), totalBlocked: blocked, totalAvailable: sl.stockType === "AVAILABLE" ? new Decimal(sl.quantity) : new Decimal(0), }); } } const summary = Array.from(grouped.values()).map((entry) => ({ itemId: entry.itemId, totalOnHand: entry.totalOnHand.toString(), totalReserved: entry.totalReserved.toString(), totalBlocked: entry.totalBlocked.toString(), totalAvailable: entry.totalAvailable.minus(entry.totalReserved).toString(), })); return ok({ summary }); }