2024-05-19 03:43:59 +00:00
|
|
|
import units from '@/units.js';
|
|
|
|
|
|
2024-05-19 11:31:58 +00:00
|
|
|
function scaleFactor(meal, recipe) {
|
|
|
|
|
const numYield = recipe.serves;
|
|
|
|
|
const numConsumers = meal.consumers.length;
|
|
|
|
|
|
|
|
|
|
return numConsumers / numYield;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-12 03:32:18 +00:00
|
|
|
export function mealToShoppingListSources(meal) {
|
|
|
|
|
const sources = [];
|
|
|
|
|
for (const recipe of meal.recipes) {
|
2024-05-19 11:31:58 +00:00
|
|
|
const scale = scaleFactor(meal, recipe);
|
2024-05-12 03:32:18 +00:00
|
|
|
for (const ingredient of recipe.ingredients) {
|
2024-05-19 11:31:58 +00:00
|
|
|
const quantity = ingredient.quantity * scale;
|
|
|
|
|
sources.push({ meal, recipe, ingredient: { ...ingredient, quantity, }, });
|
2024-05-12 03:32:18 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const ingredient of meal.extra_ingredients) {
|
|
|
|
|
sources.push({ ingredient, meal, });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sources;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-18 07:05:28 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-12 03:32:18 +00:00
|
|
|
export function removeMealFromShoppingListSources(sources, meal) {
|
2024-05-12 06:32:28 +00:00
|
|
|
return sources.filter(source => source.meal?.id !== meal.id);
|
2024-05-12 03:32:18 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-19 03:43:59 +00:00
|
|
|
export function groupingToIngredients(grouping) {
|
|
|
|
|
const product = grouping.product;
|
|
|
|
|
const totals = [];
|
|
|
|
|
for (const total of grouping.totals) {
|
|
|
|
|
totals.push({ id: 0, line: `${total.quantity} ${total.unit} of ${product.name}`, preparation: '', name: product.name, product, ...total });
|
|
|
|
|
}
|
|
|
|
|
return totals;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function calculateTotals(sources) {
|
2024-05-19 11:57:28 +00:00
|
|
|
let byBaseUnit = {};
|
2024-05-19 10:21:49 +00:00
|
|
|
for (const { quantity, unit } of sources) {
|
|
|
|
|
const conversion = units.getConversionFactor(unit) ?? { unit, factor: 1 };
|
2024-05-19 11:57:28 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-19 03:43:59 +00:00
|
|
|
|
2024-05-19 11:57:28 +00:00
|
|
|
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;
|
2024-05-19 03:43:59 +00:00
|
|
|
}
|
|
|
|
|
else {
|
2024-05-19 11:57:28 +00:00
|
|
|
let total = 0;
|
|
|
|
|
for (const unit in byBaseUnit[baseUnit]) {
|
|
|
|
|
const { quantity, factor } = byBaseUnit[baseUnit][unit];
|
|
|
|
|
total += quantity / factor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
totals[baseUnit] = total;
|
2024-05-19 03:43:59 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-19 10:21:49 +00:00
|
|
|
return totals;
|
2024-05-19 03:43:59 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-19 10:21:49 +00:00
|
|
|
function groupBy(groups, keyFn) {
|
2024-05-12 03:32:18 +00:00
|
|
|
const grouped = {};
|
2024-05-19 10:21:49 +00:00
|
|
|
for (const group of groups) {
|
|
|
|
|
const key = keyFn(group);
|
2024-05-12 06:32:28 +00:00
|
|
|
if (!grouped[key]) {
|
2024-05-19 10:21:49 +00:00
|
|
|
grouped[key] = [];
|
2024-05-12 03:32:18 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-19 10:21:49 +00:00
|
|
|
grouped[key].push(group);
|
2024-05-12 03:32:18 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-19 10:21:49 +00:00
|
|
|
return grouped;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-19 12:04:20 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-19 10:21:49 +00:00
|
|
|
function getRemainingIngredients(requiredTotals, foundTotals) {
|
2024-05-19 12:04:20 +00:00
|
|
|
requiredTotals = convertToBaseUnits(requiredTotals);
|
|
|
|
|
foundTotals = convertToBaseUnits(foundTotals);
|
|
|
|
|
|
2024-05-19 10:21:49 +00:00
|
|
|
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,
|
|
|
|
|
});
|
2024-05-19 03:43:59 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-19 10:21:49 +00:00
|
|
|
return results;
|
2024-05-12 03:32:18 +00:00
|
|
|
}
|