diff --git a/client/src/modules/inventory/InventoryListPage.tsx b/client/src/modules/inventory/InventoryListPage.tsx index 57e1cbf..21dad4d 100644 --- a/client/src/modules/inventory/InventoryListPage.tsx +++ b/client/src/modules/inventory/InventoryListPage.tsx @@ -109,7 +109,7 @@ export function InventoryListPage() { Type Status UOM - Flags + Qty BOM Updated @@ -131,8 +131,8 @@ export function InventoryListPage() { {item.unitOfMeasure} -
{item.isSellable ? "Sellable" : "Not sellable"}
-
{item.isPurchasable ? "Purchasable" : "Not purchasable"}
+
Total {item.onHandQuantity}
+
Available {item.availableQuantity}
{item.bomLineCount} lines {new Date(item.updatedAt).toLocaleDateString()} diff --git a/server/src/modules/inventory/service.ts b/server/src/modules/inventory/service.ts index 93ef6b7..a680467 100644 --- a/server/src/modules/inventory/service.ts +++ b/server/src/modules/inventory/service.ts @@ -341,6 +341,23 @@ function buildStockBalances(transactions: InventoryTransactionRecord[], reservat ); } +function calculateSummaryQuantities( + transactions: Array<{ transactionType: string; quantity: number }>, + reservations: Array<{ status: string; quantity: number }> +) { + const onHandQuantity = transactions.reduce((sum, transaction) => { + return sum + getSignedQuantity(transaction.transactionType as InventoryTransactionType, transaction.quantity); + }, 0); + const reservedQuantity = reservations.reduce((sum, reservation) => { + return reservation.status === "ACTIVE" ? sum + reservation.quantity : sum; + }, 0); + + return { + onHandQuantity, + availableQuantity: onHandQuantity - reservedQuantity, + }; +} + function mapSummary(record: { id: string; sku: string; @@ -351,8 +368,12 @@ function mapSummary(record: { isSellable: boolean; isPurchasable: boolean; updatedAt: Date; + inventoryTransactions?: Array<{ transactionType: string; quantity: number }>; + reservations?: Array<{ status: string; quantity: number }>; _count: { bomLines: number }; }): InventoryItemSummaryDto { + const quantities = calculateSummaryQuantities(record.inventoryTransactions ?? [], record.reservations ?? []); + return { id: record.id, sku: record.sku, @@ -362,6 +383,8 @@ function mapSummary(record: { unitOfMeasure: record.unitOfMeasure as InventoryUnitOfMeasure, isSellable: record.isSellable, isPurchasable: record.isPurchasable, + onHandQuantity: quantities.onHandQuantity, + availableQuantity: quantities.availableQuantity, bomLineCount: record._count.bomLines, updatedAt: record.updatedAt.toISOString(), }; @@ -391,6 +414,14 @@ function mapDetail(record: InventoryDetailRecord): InventoryItemDetailDto { isSellable: record.isSellable, isPurchasable: record.isPurchasable, updatedAt: record.updatedAt, + inventoryTransactions: record.inventoryTransactions.map((transaction) => ({ + transactionType: transaction.transactionType, + quantity: transaction.quantity, + })), + reservations: record.reservations.map((reservation) => ({ + status: reservation.status, + quantity: reservation.quantity, + })), _count: { bomLines: record.bomLines.length }, }), description: record.description, @@ -645,6 +676,18 @@ export async function listInventoryItems(filters: InventoryListFilters = {}) { const items = await prisma.inventoryItem.findMany({ where: buildWhereClause(filters), include: { + inventoryTransactions: { + select: { + transactionType: true, + quantity: true, + }, + }, + reservations: { + select: { + status: true, + quantity: true, + }, + }, _count: { select: { bomLines: true, diff --git a/shared/src/inventory/types.ts b/shared/src/inventory/types.ts index 1873ef9..e7e0bd6 100644 --- a/shared/src/inventory/types.ts +++ b/shared/src/inventory/types.ts @@ -114,6 +114,8 @@ export interface InventoryItemSummaryDto { unitOfMeasure: InventoryUnitOfMeasure; isSellable: boolean; isPurchasable: boolean; + onHandQuantity: number; + availableQuantity: number; bomLineCount: number; updatedAt: string; }