Files
recipe-app/backend/src/inventory/inventory.service.ts
T

260 lines
6.4 KiB
TypeScript

import { ConsumeInventoryDto } from './dto/consume-inventory.dto';
import { Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { CreateInventoryDto } from './dto/create-inventory.dto';
import { UpdateInventoryDto } from './dto/update-inventory.dto';
type InventoryQuery = {
location?: string;
sort?: string;
};
@Injectable()
export class InventoryService {
constructor(private prisma: PrismaService) {}
async findAll(query?: InventoryQuery) {
const where: Prisma.InventoryItemWhereInput = {};
const orderBy: Prisma.InventoryItemOrderByWithRelationInput[] = [];
if (query?.location) {
where.location = query.location;
}
if (query?.sort === 'bestBeforeAsc') {
orderBy.push({ bestBeforeDate: 'asc' });
} else if (query?.sort === 'bestBeforeDesc') {
orderBy.push({ bestBeforeDate: 'desc' });
} else if (query?.sort === 'nameAsc') {
orderBy.push({ product: { name: 'asc' } } as any);
} else if (query?.sort === 'purchaseDateAsc') {
orderBy.push({ purchaseDate: 'asc' });
} else if (query?.sort === 'purchaseDateDesc') {
orderBy.push({ purchaseDate: 'desc' });
} else {
orderBy.push({ createdAt: 'desc' });
}
return this.prisma.inventoryItem.findMany({
where,
include: {
product: true,
},
orderBy,
});
}
async consume(id: number, data: ConsumeInventoryDto) {
const existing = await this.prisma.inventoryItem.findUnique({
where: { id },
include: {
product: true,
},
});
if (!existing) {
throw new NotFoundException(`Inventory item with id ${id} not found`);
}
const currentQuantity = Number(existing.quantity);
const newQuantity = Math.max(0, currentQuantity - data.amountUsed);
return this.prisma.$transaction(async (tx) => {
const updatedItem = await tx.inventoryItem.update({
where: { id },
data: {
quantity: new Prisma.Decimal(newQuantity),
},
include: {
product: true,
},
});
await tx.inventoryConsumption.create({
data: {
inventoryItemId: id,
amountUsed: new Prisma.Decimal(data.amountUsed),
comment: data.comment?.trim() || null,
},
});
return updatedItem;
});
}
async findConsumptionHistory(id: number) {
const existing = await this.prisma.inventoryItem.findUnique({
where: { id },
});
if (!existing) {
throw new NotFoundException(`Inventory item with id ${id} not found`);
}
return this.prisma.inventoryConsumption.findMany({
where: {
inventoryItemId: id,
},
select: {
id: true,
inventoryItemId: true,
amountUsed: true,
comment: true,
createdAt: true,
inventoryItem: {
select: { unit: true },
},
},
orderBy: {
createdAt: 'desc',
},
});
}
async findExpiring() {
const now = new Date();
return this.prisma.inventoryItem.findMany({
where: {
bestBeforeDate: {
not: null,
gte: now,
},
},
include: {
product: true,
},
orderBy: [{ bestBeforeDate: 'asc' }, { createdAt: 'desc' }],
});
}
async create(data: CreateInventoryDto) {
const product = await this.prisma.product.findUnique({
where: { id: data.productId },
});
if (!product) {
throw new NotFoundException('Product not found');
}
return this.prisma.inventoryItem.create({
data: {
...data,
quantity: new Prisma.Decimal(data.quantity),
location: data.location?.trim() || undefined,
brand: data.brand?.trim() || undefined,
suitableFor: data.suitableFor?.trim() || undefined,
comment: data.comment?.trim() || undefined,
purchaseDate: data.purchaseDate
? new Date(data.purchaseDate)
: undefined,
bestBeforeDate: data.bestBeforeDate
? new Date(data.bestBeforeDate)
: undefined,
},
include: {
product: true,
},
});
}
async update(id: number, data: UpdateInventoryDto) {
const existing = await this.prisma.inventoryItem.findUnique({
where: { id },
});
if (!existing) {
throw new NotFoundException(`Inventory item with id ${id} not found`);
}
if (typeof data.productId === 'number') {
const product = await this.prisma.product.findUnique({
where: { id: data.productId },
});
if (!product) {
throw new NotFoundException('Product not found');
}
}
const updateData: Prisma.InventoryItemUpdateInput = {};
if (typeof data.productId === 'number') {
updateData.product = {
connect: { id: data.productId },
};
}
if (typeof data.quantity === 'number') {
updateData.quantity = new Prisma.Decimal(data.quantity);
}
if (typeof data.unit === 'string') {
updateData.unit = data.unit.trim();
}
if (typeof data.location === 'string') {
updateData.location = data.location.trim();
}
if (typeof data.brand === 'string') {
updateData.brand = data.brand.trim();
}
if (typeof data.priority === 'number') {
updateData.priority = data.priority;
}
if (typeof data.purchaseDate === 'string') {
updateData.purchaseDate = data.purchaseDate
? new Date(data.purchaseDate)
: null;
}
if (typeof data.bestBeforeDate === 'string') {
updateData.bestBeforeDate = data.bestBeforeDate
? new Date(data.bestBeforeDate)
: null;
}
if (typeof data.opened === 'boolean') {
updateData.opened = data.opened;
}
if (typeof data.shelfNote === 'string') {
updateData.shelfNote = data.shelfNote.trim();
}
if (typeof data.suitableFor === 'string') {
updateData.suitableFor = data.suitableFor.trim();
}
if (typeof data.isOnSale === 'boolean') {
updateData.isOnSale = data.isOnSale;
}
if (typeof data.priceLevel === 'number') {
updateData.priceLevel = data.priceLevel;
}
if (typeof data.proteinType === 'string') {
updateData.proteinType = data.proteinType.trim();
}
if (typeof data.isLeftover === 'boolean') {
updateData.isLeftover = data.isLeftover;
}
if (typeof data.comment === 'string') {
updateData.comment = data.comment.trim();
}
return this.prisma.inventoryItem.update({
where: { id },
data: updateData,
include: {
product: true,
},
});
}
}