diff --git a/config-dist.php b/config-dist.php index 85793b05..549d7bbf 100644 --- a/config-dist.php +++ b/config-dist.php @@ -108,6 +108,9 @@ DefaultUserSetting('batteries_due_soon_days', 5); # Tasks settings DefaultUserSetting('tasks_due_soon_days', 5); +# Recipe settings +DefaultUserSetting('recipe_ingredient_display_product_group', false); // Display the product group information in the ingredient list + # If the page should be automatically reloaded when there was # an external change DefaultUserSetting('auto_reload_on_db_change', true); diff --git a/controllers/RecipesController.php b/controllers/RecipesController.php index 521d2dd0..5750cd3a 100644 --- a/controllers/RecipesController.php +++ b/controllers/RecipesController.php @@ -37,20 +37,20 @@ class RecipesController extends BaseController if (isset($request->getQueryParams()['recipe'])) { $selectedRecipe = $this->Database->recipes($request->getQueryParams()['recipe']); - $selectedRecipePositionsResolved = $this->Database->recipes_pos_resolved()->where('recipe_id = :1 AND is_nested_recipe_pos = 0', $request->getQueryParams()['recipe'])->orderBy('ingredient_group'); + $selectedRecipePositionsResolved = $this->Database->recipes_pos_resolved()->where('recipe_id = :1 AND is_nested_recipe_pos = 0', $request->getQueryParams()['recipe'])->orderBy('ingredient_group', 'ASC', 'product_group', 'ASC'); } else { foreach ($recipes as $recipe) { $selectedRecipe = $recipe; - $selectedRecipePositionsResolved = $this->Database->recipes_pos_resolved()->where('recipe_id = :1 AND is_nested_recipe_pos = 0', $recipe->id)->orderBy('ingredient_group'); + $selectedRecipePositionsResolved = $this->Database->recipes_pos_resolved()->where('recipe_id = :1 AND is_nested_recipe_pos = 0', $recipe->id)->orderBy('ingredient_group', 'ASC', 'product_group', 'ASC'); break; } } $selectedRecipeSubRecipes = $this->Database->recipes()->where('id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('name')->fetchAll(); - $selectedRecipeSubRecipesPositions = $this->Database->recipes_pos_resolved()->where('recipe_id = :1', $selectedRecipe->id)->orderBy('ingredient_group')->fetchAll(); + $selectedRecipeSubRecipesPositions = $this->Database->recipes_pos_resolved()->where('recipe_id = :1', $selectedRecipe->id)->orderBy('ingredient_group', 'ASC', 'product_group', 'ASC')->fetchAll(); $includedRecipeIdsAbsolute = array(); $includedRecipeIdsAbsolute[] = $selectedRecipe->id; @@ -132,6 +132,11 @@ class RecipesController extends BaseController } } + public function RecipesSettings(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + return $this->AppContainer->view->render($response, 'recipessettings'); + } + public function MealPlan(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { $recipes = $this->Database->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(); diff --git a/migrations/0098.sql b/migrations/0098.sql new file mode 100644 index 00000000..3855eef1 --- /dev/null +++ b/migrations/0098.sql @@ -0,0 +1,96 @@ + +DROP VIEW recipes_pos_resolved; +CREATE VIEW recipes_pos_resolved +AS + +-- Multiplication by 1.0 to force conversion to float (REAL) + +SELECT + r.id AS recipe_id, + rp.id AS recipe_pos_id, + rp.product_id AS product_id, + rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount, + IFNULL(sc.amount_aggregated, 0) AS stock_amount, + CASE WHEN IFNULL(sc.amount_aggregated, 0) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END THEN 1 ELSE 0 END AS need_fulfilled, + CASE WHEN IFNULL(sc.amount_aggregated, 0) - CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END < 0 THEN ABS(IFNULL(sc.amount_aggregated, 0) - (CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END)) ELSE 0 END AS missing_amount, + IFNULL(sl.amount, 0) * p.qu_factor_purchase_to_stock AS amount_on_shopping_list, + CASE WHEN IFNULL(sc.amount_aggregated, 0) + (CASE WHEN r.not_check_shoppinglist = 1 THEN 0 ELSE IFNULL(sl.amount, 0) END * p.qu_factor_purchase_to_stock) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list, + rp.qu_id, + (CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END / p.qu_factor_purchase_to_stock) * pcp.last_price * rp.price_factor AS costs, + CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos, + rp.ingredient_group, + pg.name as product_group, + rp.id, -- Just a dummy id column + rnr.includes_recipe_id as child_recipe_id, + rp.note, + rp.variable_amount AS recipe_variable_amount, + rp.only_check_single_unit_in_stock, + rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * IFNULL(p.calories, 0) AS calories +FROM recipes r +JOIN recipes_nestings_resolved rnr + ON r.id = rnr.recipe_id +JOIN recipes rnrr + ON rnr.includes_recipe_id = rnrr.id +JOIN recipes_pos rp + ON rnr.includes_recipe_id = rp.recipe_id +JOIN products p + ON rp.product_id = p.id +JOIN product_groups pg + ON p.product_group_id = pg.id +LEFT JOIN ( + SELECT product_id, SUM(amount) AS amount + FROM shopping_list + GROUP BY product_id) sl + ON rp.product_id = sl.product_id +LEFT JOIN stock_current sc + ON rp.product_id = sc.product_id +LEFT JOIN products_current_price pcp + ON rp.product_id = pcp.product_id +WHERE rp.not_check_stock_fulfillment = 0 + +UNION + +-- Just add all recipe positions which should not be checked against stock with fulfilled need + +SELECT + r.id AS recipe_id, + rp.id AS recipe_pos_id, + rp.product_id AS product_id, + rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount, + IFNULL(sc.amount_aggregated, 0) AS stock_amount, + 1 AS need_fulfilled, + 0 AS missing_amount, + IFNULL(sl.amount, 0) * p.qu_factor_purchase_to_stock AS amount_on_shopping_list, + 1 AS need_fulfilled_with_shopping_list, + rp.qu_id, + (CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END / p.qu_factor_purchase_to_stock) * pcp.last_price * rp.price_factor AS costs, + CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos, + rp.ingredient_group, + pg.name as product_group, + rp.id, -- Just a dummy id column + rnr.includes_recipe_id as child_recipe_id, + rp.note, + rp.variable_amount AS recipe_variable_amount, + rp.only_check_single_unit_in_stock, + rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * IFNULL(p.calories, 0) AS calories +FROM recipes r +JOIN recipes_nestings_resolved rnr + ON r.id = rnr.recipe_id +JOIN recipes rnrr + ON rnr.includes_recipe_id = rnrr.id +JOIN recipes_pos rp + ON rnr.includes_recipe_id = rp.recipe_id +JOIN products p + ON rp.product_id = p.id +JOIN product_groups pg + ON p.product_group_id = pg.id +LEFT JOIN ( + SELECT product_id, SUM(amount) AS amount + FROM shopping_list + GROUP BY product_id) sl + ON rp.product_id = sl.product_id +LEFT JOIN stock_current sc + ON rp.product_id = sc.product_id +LEFT JOIN products_current_price pcp + ON rp.product_id = pcp.product_id +WHERE rp.not_check_stock_fulfillment = 1; diff --git a/public/viewjs/recipessettings.js b/public/viewjs/recipessettings.js new file mode 100644 index 00000000..0f169949 --- /dev/null +++ b/public/viewjs/recipessettings.js @@ -0,0 +1,4 @@ +if (BoolVal(Grocy.UserSettings.recipe_ingredient_display_product_group)) +{ + $("#recipe_ingredient_display_product_group").prop("checked", true); +} diff --git a/routes.php b/routes.php index df35f7e5..95990ba9 100644 --- a/routes.php +++ b/routes.php @@ -70,6 +70,7 @@ $app->group('', function() $this->get('/recipe/{recipeId}', '\Grocy\Controllers\RecipesController:RecipeEditForm'); $this->get('/recipe/{recipeId}/pos/{recipePosId}', '\Grocy\Controllers\RecipesController:RecipePosEditForm'); $this->get('/mealplan', '\Grocy\Controllers\RecipesController:MealPlan'); + $this->get('/recipessettings', '\Grocy\Controllers\RecipesController:RecipesSettings'); } // Chore routes diff --git a/views/layout/default.blade.php b/views/layout/default.blade.php index 985902f8..fd03d075 100644 --- a/views/layout/default.blade.php +++ b/views/layout/default.blade.php @@ -404,6 +404,9 @@ @if(GROCY_FEATURE_FLAG_TASKS)  {{ $__t('Tasks settings') }} @endif + @if(GROCY_FEATURE_FLAG_RECIPES) +  {{ $__t('Recipes settings') }} + @endif  {{ $__t('Manage users') }} diff --git a/views/recipes.blade.php b/views/recipes.blade.php index 95871ab2..af84fb17 100644 --- a/views/recipes.blade.php +++ b/views/recipes.blade.php @@ -195,12 +195,16 @@ @if(count($selectedRecipeSubRecipePositionsFiltered) > 0)
{{ $__t('Ingredients') }}
@endif @@ -247,12 +252,16 @@ @if($selectedRecipePositionsResolved->count() > 0)
{{ $__t('Ingredients') }}
@endif diff --git a/views/recipessettings.blade.php b/views/recipessettings.blade.php new file mode 100644 index 00000000..562be43a --- /dev/null +++ b/views/recipessettings.blade.php @@ -0,0 +1,24 @@ +@extends('layout.default') + +@section('title', $__t('Recipes settings')) + +@section('viewJsName', 'recipessettings') + +@section('content') +
+
+

@yield('title')

+ +

{{ $__t('Recipe card ingredients') }}

+
+
+ +
+
+ + {{ $__t('OK') }} +
+
+@stop