feat: add support for alternative ingredients; implement JSON storage and parsing logic
Test Suite / test (24.15.0) (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-04 22:06:57 +02:00
parent 64f63b3392
commit 2c8d6b69ae
9 changed files with 180 additions and 9 deletions
@@ -25,6 +25,11 @@ class CreateRecipeIngredientDto {
@IsOptional()
@IsString()
note?: string;
@IsOptional()
@IsArray()
@IsInt({ each: true })
alternativeProductIds?: number[];
}
export class CreateRecipeDto {
+30 -3
View File
@@ -94,7 +94,16 @@ export class RecipesService {
const ingredientPreviews = await Promise.all(
recipe.ingredients.map(async (ingredient: any) => {
const inventoryItems = await this.prisma.inventoryItem.findMany({
where: { productId: ingredient.productId },
where: {
productId: {
in: [
ingredient.productId,
...(Array.isArray(ingredient.alternativeProductIds)
? ingredient.alternativeProductIds
: []),
],
},
},
orderBy: { createdAt: 'desc' },
});
@@ -276,6 +285,7 @@ export class RecipesService {
quantity: ingredient.quantity,
unit: ingredient.unit,
note: ingredient.note || null,
alternativeProductIds: ingredient.alternativeProductIds ?? [],
})),
},
},
@@ -443,6 +453,7 @@ export class RecipesService {
quantity: ingredient.quantity,
unit: ingredient.unit,
note: ingredient.note || null,
alternativeProductIds: ingredient.alternativeProductIds ?? [],
})),
},
},
@@ -512,9 +523,12 @@ export class RecipesService {
};
const ingredientsWithSuggestions = parsed.ingredients.map((ingredient: ParsedIngredient) => {
const query = normalize(ingredient.rawName);
// Kör matchning mot alla alternativ och slå ihop suggestions
const alternatives = ingredient.alternatives?.length > 1
? ingredient.alternatives
: [ingredient.rawName];
const scored = allProducts
const scoreProduct = (query: string) => allProducts
.map((product) => {
const targetName = normalize(product.canonicalName || product.name);
const targetNormalized = normalize(product.normalizedName);
@@ -538,6 +552,18 @@ export class RecipesService {
})
.filter((s) => s.score >= 40)
.sort((a, b) => b.score - a.score)
.slice(0, 5);
// Slå ihop suggestions från alla alternativ, deduplicera på productId, ta topp 5
const seenIds = new Set<number>();
const scored = alternatives
.flatMap((alt) => scoreProduct(normalize(alt)))
.filter((s) => {
if (seenIds.has(s.product.id)) return false;
seenIds.add(s.product.id);
return true;
})
.sort((a, b) => b.score - a.score)
.slice(0, 5)
.map((s) => ({
productId: s.product.id,
@@ -547,6 +573,7 @@ export class RecipesService {
return {
rawName: ingredient.rawName,
alternatives: ingredient.alternatives ?? [],
quantity: ingredient.quantity,
unit: ingredient.unit,
note: ingredient.note,