munch-ease-frontend/src/components/shopping/shopping.js

164 lines
5.1 KiB
JavaScript
Raw Normal View History

import units from '@/units.js';
function scaleFactor(mealRecipe) {
const numRecipe = mealRecipe.recipe.serves;
const numRequested = mealRecipe.servings;
return numRequested / numRecipe;
}
2024-05-12 03:32:18 +00:00
export function mealToShoppingListSources(meal) {
const sources = [];
for (const mealRecipe of meal.recipes) {
const scale = scaleFactor(mealRecipe);
for (const ingredient of mealRecipe.recipe.ingredients) {
const quantity = ingredient.quantity * scale;
sources.push({ meal, mealRecipe, recipe: mealRecipe.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
}
export function groupingToIngredients(grouping) {
const product = grouping.product;
const totals = [];
for (const total of grouping.totals) {
2024-05-20 12:16:39 +00:00
totals.push({ id: -1, 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 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;
}
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 10:21:49 +00:00
return totals;
}
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 10:21:49 +00:00
return results;
2024-05-12 03:32:18 +00:00
}