feat(meal-plan): add servings field to MealPlanEntry and update related functionality

feat(products): implement bulk update for product categories

feat(recipes): add servings input to WriteRecipePage and update MealPlanClient for servings management

refactor(types): enhance Product and Category types with additional properties
This commit is contained in:
Nils-Johan Gynther
2026-04-17 22:50:41 +02:00
parent a81bd6b460
commit 21dc06829a
12 changed files with 323 additions and 52 deletions
@@ -1,4 +1,4 @@
import { IsDateString, IsInt, IsPositive } from 'class-validator';
import { IsDateString, IsInt, IsOptional, IsPositive, Min } from 'class-validator';
export class CreateMealPlanEntryDto {
@IsDateString()
@@ -7,4 +7,9 @@ export class CreateMealPlanEntryDto {
@IsInt()
@IsPositive()
recipeId: number;
@IsOptional()
@IsInt()
@Min(1)
servings?: number;
}
+13 -6
View File
@@ -6,6 +6,7 @@ const recipeSelect = {
id: true,
name: true,
imageUrl: true,
servings: true,
ingredients: {
select: {
quantity: true,
@@ -36,8 +37,8 @@ export class MealPlanService {
const date = new Date(dto.date);
return this.prisma.mealPlanEntry.upsert({
where: { date },
create: { date, recipeId: dto.recipeId },
update: { recipeId: dto.recipeId },
create: { date, recipeId: dto.recipeId, servings: dto.servings ?? null },
update: { recipeId: dto.recipeId, servings: dto.servings ?? null },
include: { recipe: { select: recipeSelect } },
});
}
@@ -55,13 +56,16 @@ export class MealPlanService {
async shoppingList(from: string, to: string) {
const entries = await this.findByRange(from, to);
// Summera ingredienser per produkt+enhet
// Summera ingredienser per produkt+enhet (skalat per portionsantal)
const map = new Map<string, { productId: number; name: string; quantity: number; unit: string }>();
for (const entry of entries) {
const recipeServings = (entry.recipe as any).servings as number | null;
const entryServings = (entry as any).servings as number | null;
const scale = recipeServings && entryServings ? entryServings / recipeServings : 1;
for (const ing of entry.recipe.ingredients) {
const key = `${ing.product.id}-${ing.unit}`;
const existing = map.get(key);
const qty = Number(ing.quantity);
const qty = Number(ing.quantity) * scale;
if (existing) {
existing.quantity += qty;
} else {
@@ -82,12 +86,15 @@ export class MealPlanService {
async inventoryCompare(from: string, to: string) {
const entries = await this.findByRange(from, to);
// Aggregera ingredienser per produkt+enhet
// Aggregera ingredienser per produkt+enhet (skalat per portionsantal)
const map = new Map<string, { productId: number; name: string; required: number; unit: string }>();
for (const entry of entries) {
const recipeServings = (entry.recipe as any).servings as number | null;
const entryServings = (entry as any).servings as number | null;
const scale = recipeServings && entryServings ? entryServings / recipeServings : 1;
for (const ing of entry.recipe.ingredients) {
const key = `${ing.product.id}-${ing.unit}`;
const qty = Number(ing.quantity);
const qty = Number(ing.quantity) * scale;
const existing = map.get(key);
if (existing) {
existing.required += qty;