import units from '@/units.js'; function scaleFactor(meal, recipe) { const numYield = recipe.serves; const numConsumers = meal.consumers.length; return numConsumers / numYield; } export function mealToShoppingListSources(meal) { const sources = []; for (const recipe of meal.recipes) { const scale = scaleFactor(meal, recipe); for (const ingredient of recipe.ingredients) { const quantity = ingredient.quantity * scale; sources.push({ meal, recipe, ingredient: { ...ingredient, quantity, }, }); } } for (const ingredient of meal.extra_ingredients) { sources.push({ ingredient, meal, }); } return sources; } export function requestsToSources(requests) { const sources = []; for (const request of requests) { if (request.ingredient) { sources.push(request); } else if (request.meal) { sources.push(...mealToShoppingListSources(request.meal)); } } return sources; } export function removeMealFromShoppingListSources(sources, meal) { return sources.filter(source => source.meal?.id !== meal.id); } export function groupingToIngredients(grouping) { const product = grouping.product; const totals = []; for (const total of grouping.totals) { totals.push({ id: -1, line: `${total.quantity} ${total.unit} of ${product.name}`, preparation: '', name: product.name, product, ...total }); } return totals; } function calculateTotals(sources) { let byBaseUnit = {}; for (const { quantity, unit } of sources) { const conversion = units.getConversionFactor(unit) ?? { unit, factor: 1 }; if (!byBaseUnit[conversion.unit]) { byBaseUnit[conversion.unit] = {}; } if (!byBaseUnit[conversion.unit][unit]) { byBaseUnit[conversion.unit][unit] = { quantity, factor: conversion.factor }; } else { byBaseUnit[conversion.unit][unit].quantity += quantity; } } const totals = {}; for (const baseUnit in byBaseUnit) { if (Object.keys(byBaseUnit[baseUnit]).length === 1) { const unit = Object.keys(byBaseUnit[baseUnit])[0]; const { quantity, } = byBaseUnit[baseUnit][unit]; totals[unit] = quantity; } else { let total = 0; for (const unit in byBaseUnit[baseUnit]) { const { quantity, factor } = byBaseUnit[baseUnit][unit]; total += quantity / factor; } totals[baseUnit] = total; } } return totals; } function groupBy(groups, keyFn) { const grouped = {}; for (const group of groups) { const key = keyFn(group); if (!grouped[key]) { grouped[key] = []; } grouped[key].push(group); } return grouped; } function convertToBaseUnits(total) { const baseUnits = {}; for (const unit in total) { const conversion = units.getConversionFactor(unit) ?? { unit, factor: 1 }; baseUnits[conversion.unit] = (baseUnits[conversion.unit] ?? 0) + total[unit] / conversion.factor; } return baseUnits; } function getRemainingIngredients(requiredTotals, foundTotals) { requiredTotals = convertToBaseUnits(requiredTotals); foundTotals = convertToBaseUnits(foundTotals); const remaining = {}; for (const unit in requiredTotals) { const required = requiredTotals[unit]; const found = foundTotals[unit] ?? 0; const remainingQuantity = required - found; if (remainingQuantity > 0) { remaining[unit] = remainingQuantity; } } return remaining; } export function toProductsModel(shoppingList) { const flattenedIngredients = requestsToSources(shoppingList.requests); const foundIngredientsByProduct = groupBy(shoppingList.results.filter(f => f.found_date), found => found.product_id); const requiredIngredientsByProduct = groupBy(flattenedIngredients, source => source.ingredient?.product?.id ?? "no-product"); const results = []; for (const product_id in requiredIngredientsByProduct) { const sources = requiredIngredientsByProduct[product_id]; const requiredIngredients = sources.map(source => source.ingredient); const requiredTotals = calculateTotals(requiredIngredients); const foundTotals = calculateTotals(foundIngredientsByProduct[product_id] ?? []); let fullyFound = null, foundDate = null; if (Object.keys(foundTotals).length === 0) { foundDate = null; fullyFound = false; } else { foundDate = foundIngredientsByProduct[product_id][0].found_date; fullyFound = Object.keys(getRemainingIngredients(requiredTotals, foundTotals)).length === 0; } results.push({ product: requiredIngredients[0].product, totals: Object.entries(requiredTotals).map(([unit, quantity]) => ({ unit, quantity, })), fullyFound, foundDate, sources, }); } return results; }