260 lines
6.4 KiB
TypeScript
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,
|
|
},
|
|
});
|
|
}
|
|
} |