mirror of
https://github.com/grocy/grocy.git
synced 2025-04-30 10:05:45 +00:00
Improved page loading time of /recipes and /mealplan when having a big meal plan (closes #695)
This commit is contained in:
parent
6660e1ff73
commit
7ee15946c7
@ -57,6 +57,7 @@
|
|||||||
- Recipe printing improvements (thanks @Ape)
|
- Recipe printing improvements (thanks @Ape)
|
||||||
- Calories are now always displayed per single serving (on the recipe and meal plan page)
|
- Calories are now always displayed per single serving (on the recipe and meal plan page)
|
||||||
- The note of an ingredient will now also be added to the corresponding shopping list item when using "Put missing products on the shopping list"
|
- The note of an ingredient will now also be added to the corresponding shopping list item when using "Put missing products on the shopping list"
|
||||||
|
- Fixed that the recipe page was slow when there were a lot meal plan recipe entries
|
||||||
- Fixed that "Only check if any amount is in stock" (recipe ingredient option) didn't work for stock amounts < 1
|
- Fixed that "Only check if any amount is in stock" (recipe ingredient option) didn't work for stock amounts < 1
|
||||||
- Fixed that when adding missing items to the shopping list, on the popup deselected items got also added
|
- Fixed that when adding missing items to the shopping list, on the popup deselected items got also added
|
||||||
- Fixed that the amount of self produced products with tare weight handling enabled was wrong ("Produces product" recipe option)
|
- Fixed that the amount of self produced products with tare weight handling enabled was wrong ("Produces product" recipe option)
|
||||||
@ -64,6 +65,7 @@
|
|||||||
- Fixed that the ingredient amount was wrong when using a to the product indirectly related quantity unit
|
- Fixed that the ingredient amount was wrong when using a to the product indirectly related quantity unit
|
||||||
|
|
||||||
### Meal plan fixes
|
### Meal plan fixes
|
||||||
|
- Improved the meal plan page loading time (drastically when having a big history of meal plan entries)
|
||||||
- Fixed that stock fulfillment checking used the desired servings of the recipe (those selected on the recipes page, not them from the meal plan entry)
|
- Fixed that stock fulfillment checking used the desired servings of the recipe (those selected on the recipes page, not them from the meal plan entry)
|
||||||
|
|
||||||
### Chores fixes
|
### Chores fixes
|
||||||
|
@ -8,11 +8,22 @@ class RecipesController extends BaseController
|
|||||||
{
|
{
|
||||||
public function MealPlan(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
public function MealPlan(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
{
|
{
|
||||||
|
// Load +- 8 days to always include week boundaries
|
||||||
|
|
||||||
|
if (isset($request->getQueryParams()['week']) && IsIsoDate($request->getQueryParams()['week']))
|
||||||
|
{
|
||||||
|
$week = $request->getQueryParams()['week'];
|
||||||
|
$mealPlanWhereTimespan = "day BETWEEN DATE('$week', '-8 day') AND DATE('$week', '+8 day')";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$mealPlanWhereTimespan = "day BETWEEN DATE(DATE('now', 'localtime'), '-8 day') AND DATE(DATE('now', 'localtime'), '+8 day')";
|
||||||
|
}
|
||||||
|
|
||||||
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll();
|
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll();
|
||||||
|
|
||||||
$events = [];
|
$events = [];
|
||||||
|
foreach ($this->getDatabase()->meal_plan()->where($mealPlanWhereTimespan) as $mealPlanEntry)
|
||||||
foreach ($this->getDatabase()->meal_plan() as $mealPlanEntry)
|
|
||||||
{
|
{
|
||||||
$recipe = FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id']);
|
$recipe = FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id']);
|
||||||
$title = '';
|
$title = '';
|
||||||
@ -23,7 +34,6 @@ class RecipesController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$productDetails = null;
|
$productDetails = null;
|
||||||
|
|
||||||
if ($mealPlanEntry['product_id'] !== null)
|
if ($mealPlanEntry['product_id'] !== null)
|
||||||
{
|
{
|
||||||
$productDetails = $this->getStockService()->GetProductDetails($mealPlanEntry['product_id']);
|
$productDetails = $this->getStockService()->GetProductDetails($mealPlanEntry['product_id']);
|
||||||
@ -44,8 +54,8 @@ class RecipesController extends BaseController
|
|||||||
return $this->renderPage($response, 'mealplan', [
|
return $this->renderPage($response, 'mealplan', [
|
||||||
'fullcalendarEventSources' => $events,
|
'fullcalendarEventSources' => $events,
|
||||||
'recipes' => $recipes,
|
'recipes' => $recipes,
|
||||||
'internalRecipes' => $this->getDatabase()->recipes()->whereNot('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(),
|
'internalRecipes' => $this->getDatabase()->recipes()->where("id IN (SELECT recipe_id FROM meal_plan_internal_recipe_relation WHERE $mealPlanWhereTimespan)")->fetchAll(),
|
||||||
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(),
|
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved2("recipe_id IN (SELECT recipe_id FROM meal_plan_internal_recipe_relation WHERE $mealPlanWhereTimespan)"),
|
||||||
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
|
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
|
||||||
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
|
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
|
||||||
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
|
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
|
||||||
@ -55,7 +65,7 @@ class RecipesController extends BaseController
|
|||||||
public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
{
|
{
|
||||||
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name', 'COLLATE NOCASE');
|
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name', 'COLLATE NOCASE');
|
||||||
$recipesResolved = $this->getRecipesService()->GetRecipesResolved();
|
$recipesResolved = $this->getRecipesService()->GetRecipesResolved('recipe_id > 0');
|
||||||
|
|
||||||
$selectedRecipe = null;
|
$selectedRecipe = null;
|
||||||
|
|
||||||
|
43
migrations/0141.sql
Normal file
43
migrations/0141.sql
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
CREATE VIEW meal_plan_internal_recipe_relation
|
||||||
|
AS
|
||||||
|
|
||||||
|
-- Relation between a meal plan (day) and the corresponding internal recipe(s)
|
||||||
|
|
||||||
|
SELECT mp.day, r.id AS recipe_id
|
||||||
|
FROM meal_plan mp
|
||||||
|
JOIN recipes r
|
||||||
|
ON r.name = CAST(mp.day AS TEXT)
|
||||||
|
AND r.type = 'mealplan-day'
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT mp.day, r.id AS recipe_id
|
||||||
|
FROM meal_plan mp
|
||||||
|
JOIN recipes r
|
||||||
|
ON r.name = STRFTIME('%Y-%W', mp.day)
|
||||||
|
AND r.type = 'mealplan-week'
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT mp.day, r.id AS recipe_id
|
||||||
|
FROM meal_plan mp
|
||||||
|
JOIN recipes r
|
||||||
|
ON r.name = CAST(mp.day AS TEXT) || '#' || CAST(mp.id AS TEXT)
|
||||||
|
AND r.type = 'mealplan-shadow';
|
||||||
|
|
||||||
|
CREATE VIEW recipes_resolved2
|
||||||
|
AS
|
||||||
|
|
||||||
|
-- The same as recipes_resolved but without the column "missing_products_count" to improve performance when this is not needed
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
1 AS id, -- Dummy, LessQL needs an id column
|
||||||
|
r.id AS recipe_id,
|
||||||
|
IFNULL(MIN(rpr.need_fulfilled), 1) AS need_fulfilled,
|
||||||
|
IFNULL(MIN(rpr.need_fulfilled_with_shopping_list), 1) AS need_fulfilled_with_shopping_list,
|
||||||
|
IFNULL(SUM(rpr.costs), 0) AS costs,
|
||||||
|
IFNULL(SUM(rpr.calories), 0) AS calories
|
||||||
|
FROM recipes r
|
||||||
|
LEFT JOIN recipes_pos_resolved rpr
|
||||||
|
ON r.id = rpr.recipe_id
|
||||||
|
GROUP BY r.id;
|
@ -25,17 +25,9 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
"defaultView": ($(window).width() < 768) ? "basicDay" : "basicWeek",
|
"defaultView": ($(window).width() < 768) ? "basicDay" : "basicWeek",
|
||||||
"firstDay": firstDay,
|
"firstDay": firstDay,
|
||||||
"height": "auto",
|
"height": "auto",
|
||||||
|
"defaultDate": GetUriParam("week"),
|
||||||
"viewRender": function(view)
|
"viewRender": function(view)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
firstRender = false
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UpdateUriParam("week", view.start.format("YYYY-MM-DD"));
|
|
||||||
}
|
|
||||||
|
|
||||||
$(".fc-day-header").prepend('\
|
$(".fc-day-header").prepend('\
|
||||||
<div class="btn-group mr-2 my-1"> \
|
<div class="btn-group mr-2 my-1"> \
|
||||||
<button type="button" class="btn btn-outline-dark btn-xs add-recipe-button""><i class="fas fa-plus"></i></a></button> \
|
<button type="button" class="btn btn-outline-dark btn-xs add-recipe-button""><i class="fas fa-plus"></i></a></button> \
|
||||||
@ -273,15 +265,21 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
},
|
},
|
||||||
"eventAfterAllRender": function(view)
|
"eventAfterAllRender": function(view)
|
||||||
{
|
{
|
||||||
|
UpdateUriParam("week", view.start.format("YYYY-MM-DD"));
|
||||||
|
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
firstRender = false
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
RefreshLocaleNumberDisplay();
|
RefreshLocaleNumberDisplay();
|
||||||
LoadImagesLazy();
|
LoadImagesLazy();
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
|
||||||
if (GetUriParam("week") !== undefined)
|
|
||||||
{
|
|
||||||
$("#calendar").fullCalendar("gotoDate", GetUriParam("week"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK)
|
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK)
|
||||||
{
|
{
|
||||||
$(".recipe-order-missing-button").addClass("d-none");
|
$(".recipe-order-missing-button").addClass("d-none");
|
||||||
@ -498,7 +496,7 @@ $('#save-add-product-button').on('click', function(e)
|
|||||||
delete jsonData.display_amount;
|
delete jsonData.display_amount;
|
||||||
jsonData.product_amount = jsonData.amount;
|
jsonData.product_amount = jsonData.amount;
|
||||||
delete jsonData.amount;
|
delete jsonData.amount;
|
||||||
jsonData.product_qu_id = jsonData.qu_id;
|
jsonData.product_qu_id = $("#qu_id").val();;
|
||||||
delete jsonData.qu_id;
|
delete jsonData.qu_id;
|
||||||
|
|
||||||
if (Grocy.IsMealPlanEntryEditAction)
|
if (Grocy.IsMealPlanEntryEditAction)
|
||||||
@ -803,10 +801,10 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
|||||||
function(productDetails)
|
function(productDetails)
|
||||||
{
|
{
|
||||||
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
|
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
|
||||||
|
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id);
|
||||||
|
|
||||||
$('#display_amount').val(1);
|
$('#display_amount').val(1);
|
||||||
RefreshLocaleNumberInput();
|
RefreshLocaleNumberInput();
|
||||||
$(".input-group-productamountpicker").trigger("change");
|
|
||||||
$('#display_amount').focus();
|
$('#display_amount').focus();
|
||||||
$('#display_amount').select();
|
$('#display_amount').select();
|
||||||
$(".input-group-productamountpicker").trigger("change");
|
$(".input-group-productamountpicker").trigger("change");
|
||||||
|
@ -87,10 +87,30 @@ class RecipesService extends BaseService
|
|||||||
return $this->getDataBaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
return $this->getDataBaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetRecipesResolved(): Result
|
public function GetRecipesResolved($customWhere = null): Result
|
||||||
|
{
|
||||||
|
if ($customWhere == null)
|
||||||
{
|
{
|
||||||
return $this->getDatabase()->recipes_resolved();
|
return $this->getDatabase()->recipes_resolved();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->getDatabase()->recipes_resolved()->where($customWhere);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The same as GetRecipesResolved but without the column "missing_products_count" to improve performance when this is not needed
|
||||||
|
public function GetRecipesResolved2($customWhere = null): Result
|
||||||
|
{
|
||||||
|
if ($customWhere == null)
|
||||||
|
{
|
||||||
|
return $this->getDatabase()->recipes_resolved2();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->getDatabase()->recipes_resolved2()->where($customWhere);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user