"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RecipeAnalysisService = void 0; const common_1 = require("@nestjs/common"); const prisma_service_1 = require("../prisma/prisma.service"); const units_1 = require("../common/utils/units"); let RecipeAnalysisService = class RecipeAnalysisService { constructor(prisma) { this.prisma = prisma; } async getAccessibleRecipe(id, userId) { const recipe = await this.prisma.recipe.findFirst({ where: { id, OR: [ { isPublic: true }, { ownerId: userId }, { shares: { some: { userId } } }, ], }, include: { ingredients: { include: { product: true, }, orderBy: { id: 'asc' }, }, }, }); if (!recipe) { throw new common_1.NotFoundException(`Recipe with id ${id} not found`); } return recipe; } calculateAvailableQuantity(inventoryItems, requiredUnit) { if (!requiredUnit) { return inventoryItems.reduce((sum, item) => sum + Number(item.quantity ?? 0), 0); } const normalizedRequiredUnit = requiredUnit.trim().toLowerCase(); const sameUnit = inventoryItems .filter((item) => item.unit.trim().toLowerCase() === normalizedRequiredUnit) .reduce((sum, item) => sum + Number(item.quantity ?? 0), 0); const converted = inventoryItems .filter((item) => item.unit.trim().toLowerCase() !== normalizedRequiredUnit) .reduce((sum, item) => { if (!(0, units_1.canConvert)(item.unit, requiredUnit)) return sum; try { return sum + (0, units_1.convertUnit)(Number(item.quantity ?? 0), item.unit, requiredUnit); } catch { return sum; } }, 0); return sameUnit + converted; } async analyzeRecipeIngredients(id, userId) { const recipe = await this.getAccessibleRecipe(id, userId); const pantryItems = await this.prisma.pantryItem.findMany({ where: { userId }, select: { productId: true }, }); const pantryProductIds = new Set(pantryItems.map((p) => p.productId)); const ingredients = await Promise.all(recipe.ingredients.map(async (ingredient) => { const requiredQuantity = Number(ingredient.quantity ?? 0); const requiredUnit = (ingredient.unit ?? '').trim(); const rawName = (ingredient.rawName ?? '').trim() || 'Okänd ingrediens'; if (!ingredient.productId || !ingredient.product) { return { ingredientId: ingredient.id, rawName, quantity: requiredQuantity, unit: requiredUnit, note: ingredient.note ?? null, status: 'missing', matchedProductId: null, matchedProductName: null, source: null, availableQuantity: 0, missingQuantity: requiredQuantity, }; } if (pantryProductIds.has(ingredient.productId)) { return { ingredientId: ingredient.id, rawName, quantity: requiredQuantity, unit: requiredUnit, note: ingredient.note ?? null, status: 'covered_by_pantry', matchedProductId: ingredient.productId, matchedProductName: ingredient.product.canonicalName || ingredient.product.name, source: 'pantry', availableQuantity: requiredQuantity, missingQuantity: 0, }; } const inventoryItems = await this.prisma.inventoryItem.findMany({ where: { productId: ingredient.productId }, select: { quantity: true, unit: true }, }); const availableQuantity = this.calculateAvailableQuantity(inventoryItems, requiredUnit); if (availableQuantity >= requiredQuantity) { return { ingredientId: ingredient.id, rawName, quantity: requiredQuantity, unit: requiredUnit, note: ingredient.note ?? null, status: 'exact_match', matchedProductId: ingredient.productId, matchedProductName: ingredient.product.canonicalName || ingredient.product.name, source: 'inventory', availableQuantity, missingQuantity: 0, }; } const alternativeProductIds = Array.isArray(ingredient.alternativeProductIds) ? ingredient.alternativeProductIds.filter((id) => typeof id === 'number') : []; for (const altProductId of alternativeProductIds) { if (pantryProductIds.has(altProductId)) { const altProduct = await this.prisma.product.findUnique({ where: { id: altProductId }, select: { id: true, name: true, canonicalName: true }, }); return { ingredientId: ingredient.id, rawName, quantity: requiredQuantity, unit: requiredUnit, note: ingredient.note ?? null, status: 'substitutable', matchedProductId: altProduct?.id ?? altProductId, matchedProductName: altProduct?.canonicalName || altProduct?.name || null, source: 'pantry_substitute', availableQuantity: requiredQuantity, missingQuantity: 0, }; } const altInventoryItems = await this.prisma.inventoryItem.findMany({ where: { productId: altProductId }, select: { quantity: true, unit: true }, }); const altAvailable = this.calculateAvailableQuantity(altInventoryItems, requiredUnit); if (altAvailable > 0) { const altProduct = await this.prisma.product.findUnique({ where: { id: altProductId }, select: { id: true, name: true, canonicalName: true }, }); return { ingredientId: ingredient.id, rawName, quantity: requiredQuantity, unit: requiredUnit, note: ingredient.note ?? null, status: 'substitutable', matchedProductId: altProduct?.id ?? altProductId, matchedProductName: altProduct?.canonicalName || altProduct?.name || null, source: 'inventory_substitute', availableQuantity: altAvailable, missingQuantity: Math.max(0, requiredQuantity - altAvailable), }; } } return { ingredientId: ingredient.id, rawName, quantity: requiredQuantity, unit: requiredUnit, note: ingredient.note ?? null, status: 'missing', matchedProductId: ingredient.productId, matchedProductName: ingredient.product.canonicalName || ingredient.product.name, source: null, availableQuantity, missingQuantity: Math.max(0, requiredQuantity - availableQuantity), }; })); const summary = { exactCount: ingredients.filter((i) => i.status === 'exact_match').length, pantryCount: ingredients.filter((i) => i.status === 'covered_by_pantry').length, substituteCount: ingredients.filter((i) => i.status === 'substitutable').length, missingCount: ingredients.filter((i) => i.status === 'missing').length, }; const shoppingListCandidates = ingredients .filter((i) => i.status === 'missing') .map((i) => ({ ingredientId: i.ingredientId, rawName: i.rawName, quantity: i.quantity, unit: i.unit, missingQuantity: i.missingQuantity, })); return { recipeId: recipe.id, ingredients, summary, shoppingListCandidates, }; } }; exports.RecipeAnalysisService = RecipeAnalysisService; exports.RecipeAnalysisService = RecipeAnalysisService = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [prisma_service_1.PrismaService]) ], RecipeAnalysisService); //# sourceMappingURL=recipe-analysis.service.js.map