import { ok, err, type ReadonlyDB, type PaginationInput, buildPaginatedResult, DEFAULT_PAGE_SIZE, } from "@tailor-platform/erp-kit/core"; import { Decimal } from "decimal.js"; import type { DB } from "../generated/kysely-tailordb"; import { sumOpenStockReservationQuantity } from "../internal/stockReservation"; import { NoFilterProvidedError } from "../lib/errors.generated"; type OrderByField = "createdAt"; export interface ListStockLevelsInput extends PaginationInput { itemId?: string; storageLocationId?: string; siteId?: string; } function reservationByItemLocation( reservations: Array<{ itemId: string; storageLocationId: string | null; reservedQuantity: string; consumedQuantity: string; }>, ) { const byKey = new Map(); for (const reservation of reservations) { if (!reservation.storageLocationId) { continue; } const key = `${reservation.itemId}:${reservation.storageLocationId}`; const openQuantity = sumOpenStockReservationQuantity([reservation]); byKey.set(key, (byKey.get(key) ?? new Decimal(0)).plus(openQuantity)); } return byKey; } export async function run(db: ReadonlyDB, input: ListStockLevelsInput) { if (!input.itemId && !input.storageLocationId && !input.siteId) { return err(new NoFilterProvidedError("listStockLevels")); } const limit = input.limit ?? DEFAULT_PAGE_SIZE; const offset = input.offset ?? 0; const orderBy = input.orderBy ?? "createdAt"; const orderDirection = input.orderDirection ?? "asc"; if (input.siteId) { const locationIds = await db .selectFrom("StorageLocation") .select("id") .where("siteId", "=", input.siteId) .execute(); if (locationIds.length === 0) { return ok(buildPaginatedResult([], limit)); } let query = db .selectFrom("StockLevel") .selectAll() .where( "storageLocationId", "in", locationIds.map((l) => l.id), ); if (input.itemId) { query = query.where("itemId", "=", input.itemId); } const stockLevels = await query .orderBy(orderBy, orderDirection) .limit(limit + 1) .offset(offset) .execute(); let reservationQuery = db .selectFrom("StockReservation") .selectAll() .where( "storageLocationId", "in", locationIds.map((l) => l.id), ) .where("status", "=", "OPEN"); if (input.itemId) { reservationQuery = reservationQuery.where("itemId", "=", input.itemId); } const reservationsResult = await reservationQuery.execute(); const reservations = Array.isArray(reservationsResult) ? reservationsResult : []; const reservedByKey = reservationByItemLocation(reservations); const items = stockLevels.map((row) => ({ ...row, availableQuantity: row.stockType === "AVAILABLE" ? new Decimal(row.quantity) .minus(reservedByKey.get(`${row.itemId}:${row.storageLocationId}`) ?? 0) .toString() : "0", })); return ok(buildPaginatedResult(items, limit)); } let query = db.selectFrom("StockLevel").selectAll(); if (input.itemId) { query = query.where("itemId", "=", input.itemId); } if (input.storageLocationId) { query = query.where("storageLocationId", "=", input.storageLocationId); } const stockLevels = await query .orderBy(orderBy, orderDirection) .limit(limit + 1) .offset(offset) .execute(); let reservationQuery = db.selectFrom("StockReservation").selectAll(); if (input.itemId) { reservationQuery = reservationQuery.where("itemId", "=", input.itemId); } if (input.storageLocationId) { reservationQuery = reservationQuery.where("storageLocationId", "=", input.storageLocationId); } reservationQuery = reservationQuery.where("status", "=", "OPEN"); const reservationsResult = await reservationQuery.execute(); const reservations = Array.isArray(reservationsResult) ? reservationsResult : []; const reservedByKey = reservationByItemLocation(reservations); const items = stockLevels.map((row) => ({ ...row, availableQuantity: row.stockType === "AVAILABLE" ? new Decimal(row.quantity) .minus(reservedByKey.get(`${row.itemId}:${row.storageLocationId}`) ?? 0) .toString() : "0", })); return ok(buildPaginatedResult(items, limit)); }