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