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;
}