inventory

This commit is contained in:
2026-03-14 21:23:22 -05:00
parent d21e2e3c0b
commit 472c36915c
14 changed files with 730 additions and 1 deletions

View File

@@ -3,6 +3,11 @@ import type {
InventoryBomLineInput,
InventoryItemDetailDto,
InventoryItemInput,
WarehouseDetailDto,
WarehouseInput,
WarehouseLocationDto,
WarehouseLocationInput,
WarehouseSummaryDto,
InventoryItemStatus,
InventoryItemSummaryDto,
InventoryItemType,
@@ -41,6 +46,23 @@ type InventoryDetailRecord = {
bomLines: BomLineRecord[];
};
type WarehouseLocationRecord = {
id: string;
code: string;
name: string;
notes: string;
};
type WarehouseDetailRecord = {
id: string;
code: string;
name: string;
notes: string;
createdAt: Date;
updatedAt: Date;
locations: WarehouseLocationRecord[];
};
function mapBomLine(record: BomLineRecord): InventoryBomLineDto {
return {
id: record.id,
@@ -54,6 +76,15 @@ function mapBomLine(record: BomLineRecord): InventoryBomLineDto {
};
}
function mapWarehouseLocation(record: WarehouseLocationRecord): WarehouseLocationDto {
return {
id: record.id,
code: record.code,
name: record.name,
notes: record.notes,
};
}
function mapSummary(record: {
id: string;
sku: string;
@@ -102,6 +133,37 @@ function mapDetail(record: InventoryDetailRecord): InventoryItemDetailDto {
};
}
function mapWarehouseSummary(record: {
id: string;
code: string;
name: string;
updatedAt: Date;
_count: { locations: number };
}): WarehouseSummaryDto {
return {
id: record.id,
code: record.code,
name: record.name,
locationCount: record._count.locations,
updatedAt: record.updatedAt.toISOString(),
};
}
function mapWarehouseDetail(record: WarehouseDetailRecord): WarehouseDetailDto {
return {
...mapWarehouseSummary({
id: record.id,
code: record.code,
name: record.name,
updatedAt: record.updatedAt,
_count: { locations: record.locations.length },
}),
notes: record.notes,
createdAt: record.createdAt.toISOString(),
locations: record.locations.map(mapWarehouseLocation),
};
}
interface InventoryListFilters {
query?: string;
status?: InventoryItemStatus;
@@ -138,6 +200,16 @@ function normalizeBomLines(bomLines: InventoryBomLineInput[]) {
.filter((line) => line.componentItemId.trim().length > 0);
}
function normalizeWarehouseLocations(locations: WarehouseLocationInput[]) {
return locations
.map((location) => ({
code: location.code.trim(),
name: location.name.trim(),
notes: location.notes,
}))
.filter((location) => location.code.length > 0 && location.name.length > 0);
}
async function validateBomLines(parentItemId: string | null, bomLines: InventoryBomLineInput[]) {
const normalized = normalizeBomLines(bomLines);
@@ -325,3 +397,87 @@ export async function updateInventoryItem(itemId: string, payload: InventoryItem
return mapDetail(item);
}
export async function listWarehouses() {
const warehouses = await prisma.warehouse.findMany({
include: {
_count: {
select: {
locations: true,
},
},
},
orderBy: [{ code: "asc" }],
});
return warehouses.map(mapWarehouseSummary);
}
export async function getWarehouseById(warehouseId: string) {
const warehouse = await prisma.warehouse.findUnique({
where: { id: warehouseId },
include: {
locations: {
orderBy: [{ code: "asc" }],
},
},
});
return warehouse ? mapWarehouseDetail(warehouse) : null;
}
export async function createWarehouse(payload: WarehouseInput) {
const locations = normalizeWarehouseLocations(payload.locations);
const warehouse = await prisma.warehouse.create({
data: {
code: payload.code.trim(),
name: payload.name.trim(),
notes: payload.notes,
locations: locations.length
? {
create: locations,
}
: undefined,
},
include: {
locations: {
orderBy: [{ code: "asc" }],
},
},
});
return mapWarehouseDetail(warehouse);
}
export async function updateWarehouse(warehouseId: string, payload: WarehouseInput) {
const existingWarehouse = await prisma.warehouse.findUnique({
where: { id: warehouseId },
});
if (!existingWarehouse) {
return null;
}
const locations = normalizeWarehouseLocations(payload.locations);
const warehouse = await prisma.warehouse.update({
where: { id: warehouseId },
data: {
code: payload.code.trim(),
name: payload.name.trim(),
notes: payload.notes,
locations: {
deleteMany: {},
create: locations,
},
},
include: {
locations: {
orderBy: [{ code: "asc" }],
},
},
});
return mapWarehouseDetail(warehouse);
}