feat: add support for alternative ingredients; implement JSON storage and parsing logic
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -25,6 +25,11 @@ class CreateRecipeIngredientDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
note?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsInt({ each: true })
|
||||
alternativeProductIds?: number[];
|
||||
}
|
||||
|
||||
export class CreateRecipeDto {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user