feat: add servings field to Recipe model and implement inventory comparison functionality

This commit is contained in:
Nils-Johan Gynther
2026-04-17 18:48:08 +02:00
parent 8a86b0aebd
commit 8e0aed032c
10 changed files with 260 additions and 20 deletions
@@ -16,6 +16,11 @@ export class MealPlanController {
return this.mealPlanService.shoppingList(from, to);
}
@Get('inventory-compare')
inventoryCompare(@Query('from') from: string, @Query('to') to: string) {
return this.mealPlanService.inventoryCompare(from, to);
}
@Post()
upsert(@Body() dto: CreateMealPlanEntryDto) {
return this.mealPlanService.upsert(dto);
@@ -77,4 +77,55 @@ export class MealPlanService {
return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name, 'sv'));
}
/** Jämför veckans ingrediensbehov mot inventariet */
async inventoryCompare(from: string, to: string) {
const entries = await this.findByRange(from, to);
// Aggregera ingredienser per produkt+enhet
const map = new Map<string, { productId: number; name: string; required: number; unit: string }>();
for (const entry of entries) {
for (const ing of entry.recipe.ingredients) {
const key = `${ing.product.id}-${ing.unit}`;
const qty = Number(ing.quantity);
const existing = map.get(key);
if (existing) {
existing.required += qty;
} else {
map.set(key, {
productId: ing.product.id,
name: ing.product.canonicalName || ing.product.name,
required: qty,
unit: ing.unit,
});
}
}
}
// Kontrollera inventariet för varje ingrediens
const result = await Promise.all(
Array.from(map.values()).map(async (item) => {
const inventoryItems = await this.prisma.inventoryItem.findMany({
where: { productId: item.productId },
});
const available = inventoryItems
.filter((i: any) => i.unit.trim().toLowerCase() === item.unit.trim().toLowerCase())
.reduce((sum: number, i: any) => sum + Number(i.quantity), 0);
return {
productId: item.productId,
name: item.name,
required: item.required,
unit: item.unit,
available,
missing: Math.max(0, item.required - available),
status: (available >= item.required ? 'enough' : 'missing') as 'enough' | 'missing',
};
}),
);
return result.sort((a, b) => {
if (a.status !== b.status) return a.status === 'missing' ? -1 : 1;
return a.name.localeCompare(b.name, 'sv');
});
}
}