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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { IsArray, IsInt, IsNumber, IsOptional, ArrayMinSize } from 'class-validator';
|
||||
|
||||
export class BulkUpdateProductsDto {
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@IsInt({ each: true })
|
||||
ids: number[];
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
categoryId?: number | null;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import { ProductsService } from './products.service';
|
||||
import { MergeProductsDto } from './dto/merge-products.dto';
|
||||
import { UpdateCanonicalNameDto } from './dto/update-canonical-name.dto';
|
||||
import { SetTagsDto } from './dto/set-tags.dto';
|
||||
import { UpsertNutritionDto } from './dto/upsert-nutrition.dto';
|
||||
import { BulkUpdateProductsDto } from './dto/bulk-update-products.dto';
|
||||
|
||||
@Controller('products')
|
||||
export class ProductsController {
|
||||
@@ -116,4 +116,10 @@ export class ProductsController {
|
||||
resetAll() {
|
||||
return this.productsService.resetAll();
|
||||
}
|
||||
|
||||
@Post('bulk-update')
|
||||
@HttpCode(200)
|
||||
bulkUpdate(@Body() body: BulkUpdateProductsDto) {
|
||||
return this.productsService.bulkUpdate(body.ids, { categoryId: body.categoryId });
|
||||
}
|
||||
}
|
||||
@@ -397,4 +397,14 @@ export class ProductsService {
|
||||
]);
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
async bulkUpdate(ids: number[], data: { categoryId?: number | null }) {
|
||||
const updateData: Record<string, any> = {};
|
||||
if ('categoryId' in data) {
|
||||
updateData.categoryId = data.categoryId;
|
||||
}
|
||||
if (Object.keys(updateData).length === 0) return { updated: 0 };
|
||||
await this.prisma.product.updateMany({ where: { id: { in: ids } }, data: updateData });
|
||||
return { updated: ids.length };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user