From 7336693ca2df367706a9575be83ada5f13b93840 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Thu, 2 Mar 2023 19:36:57 +0100 Subject: [PATCH 1/3] Optimized `stock_missing_products` view (fixes #2154) --- migrations/0215.sql | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 migrations/0215.sql diff --git a/migrations/0215.sql b/migrations/0215.sql new file mode 100644 index 00000000..a272247d --- /dev/null +++ b/migrations/0215.sql @@ -0,0 +1,64 @@ +DROP VIEW stock_missing_products; +CREATE VIEW stock_missing_products +AS + +SELECT * +FROM ( + +-- Products WITHOUT sub products where the amount of the sub products SHOULD NOT be cumulated +SELECT + p.id, + MAX(p.name) AS name, + p.min_stock_amount - IFNULL(SUM(s.amount), 0) + (CASE WHEN p.treat_opened_as_out_of_stock = 1 THEN IFNULL(SUM(s.amount_opened), 0) ELSE 0 END) AS amount_missing, + CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock +FROM products_view p +LEFT JOIN stock_current s + ON p.id = s.product_id +WHERE p.min_stock_amount != 0 + AND p.cumulate_min_stock_amount_of_sub_products = 0 + AND p.has_sub_products = 0 + AND p.parent_product_id IS NULL + AND IFNULL(p.active, 0) = 1 +GROUP BY p.id + +UNION + +-- Parent products WITH sub products where the amount of the sub products SHOULD be cumulated +SELECT + p.id, + MAX(p.name) AS name, + SUM(sub_p.min_stock_amount) - IFNULL(SUM(s.amount_aggregated), 0) + (CASE WHEN p.treat_opened_as_out_of_stock = 1 THEN IFNULL(SUM(s.amount_opened_aggregated), 0) ELSE 0 END) AS amount_missing, + CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock +FROM products_view p +JOIN products_resolved pr + ON p.id = pr.parent_product_id +JOIN products sub_p + ON pr.sub_product_id = sub_p.id +LEFT JOIN stock_current s + ON pr.sub_product_id = s.product_id +WHERE sub_p.min_stock_amount != 0 + AND p.cumulate_min_stock_amount_of_sub_products = 1 + AND IFNULL(p.active, 0) = 1 +GROUP BY p.id + +UNION + +-- Sub products where the amount SHOULD NOT be cumulated into the parent product +SELECT + sub_p.id, + MAX(sub_p.name) AS name, + SUM(sub_p.min_stock_amount) - IFNULL(SUM(s.amount_aggregated), 0) + (CASE WHEN p.treat_opened_as_out_of_stock = 1 THEN IFNULL(SUM(s.amount_opened_aggregated), 0) ELSE 0 END) AS amount_missing, + CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock +FROM products p +JOIN products_resolved pr + ON p.id = pr.parent_product_id +JOIN products sub_p + ON pr.sub_product_id = sub_p.id +LEFT JOIN stock_current s + ON pr.sub_product_id = s.product_id +WHERE sub_p.min_stock_amount != 0 + AND p.cumulate_min_stock_amount_of_sub_products = 0 + AND IFNULL(p.active, 0) = 1 +GROUP BY sub_p.id +) x +WHERE x.amount_missing > 0; From 792c710bdce24128d07447d1ee671d0652ca05a7 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Sun, 12 Mar 2023 16:35:18 +0100 Subject: [PATCH 2/3] Fixed produces product handling when consuming meal plan shadow recipes (fixes #2160) --- changelog/70_UNRELEASED_xxxx.xx.xx.md | 1 + services/RecipesService.php | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/changelog/70_UNRELEASED_xxxx.xx.xx.md b/changelog/70_UNRELEASED_xxxx.xx.xx.md index 55bcd8a9..bacdb590 100644 --- a/changelog/70_UNRELEASED_xxxx.xx.xx.md +++ b/changelog/70_UNRELEASED_xxxx.xx.xx.md @@ -49,6 +49,7 @@ ### Meal plan - Added a new sub feature flag `FEATURE_FLAG_RECIPES_MEALPLAN` (in `config.php`) to only disable the meal plan if not needed (thanks @webysther) +- Fixed that consuming a recipe from the meal plan didn't add its "Produces product"-product to stock (if any) ### Chores diff --git a/services/RecipesService.php b/services/RecipesService.php index 8a24d26a..fd104169 100644 --- a/services/RecipesService.php +++ b/services/RecipesService.php @@ -101,12 +101,20 @@ class RecipesService extends BaseService } $this->getDatabaseService()->GetDbConnectionRaw()->commit(); - $recipeRow = $this->getDatabase()->recipes()->where('id = :1', $recipeId)->fetch(); - if (!empty($recipeRow->product_id)) + $recipe = $this->getDatabase()->recipes()->where('id = :1', $recipeId)->fetch(); + $productId = $recipe->product_id; + if ($recipe->type == self::RECIPE_TYPE_MEALPLAN_SHADOW) { - $product = $this->getDatabase()->products()->where('id = :1', $recipeRow->product_id)->fetch(); + // Use "Produces product" of the original recipe + $mealPlanEntry = $this->getDatabase()->meal_plan()->where('id = :1', explode('#', $recipe->name)[1])->fetch(); + $productId = $this->getDatabase()->recipes()->where('id = :1', $mealPlanEntry->recipe_id)->fetch()->product_id; + } + + if (!empty($productId)) + { + $product = $this->getDatabase()->products()->where('id = :1', $productId)->fetch(); $recipeResolvedRow = $this->getDatabase()->recipes_resolved()->where('recipe_id = :1', $recipeId)->fetch(); - $this->getStockService()->AddProduct($recipeRow->product_id, $recipeRow->desired_servings, null, StockService::TRANSACTION_TYPE_SELF_PRODUCTION, date('Y-m-d'), $recipeResolvedRow->costs_per_serving, null, null, $dummyTransactionId, $product->default_stock_label_type, true); + $this->getStockService()->AddProduct($productId, $recipe->desired_servings, null, StockService::TRANSACTION_TYPE_SELF_PRODUCTION, date('Y-m-d'), $recipeResolvedRow->costs_per_serving, null, null, $dummyTransactionId, $product->default_stock_label_type, true); } } From 6857796ef05537ca751aaa066b946f969f53e062 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Sun, 12 Mar 2023 22:45:04 +0100 Subject: [PATCH 3/3] Fixed produces product amount handling when consuming meal plan shadow recipes (references #2160) --- services/ChoresService.php | 2 +- services/DatabaseMigrationService.php | 2 +- services/RecipesService.php | 9 ++++++--- services/StockService.php | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/services/ChoresService.php b/services/ChoresService.php index 3a18bab1..d2f8622f 100644 --- a/services/ChoresService.php +++ b/services/ChoresService.php @@ -271,7 +271,7 @@ class ChoresService extends BaseService $this->getDatabaseService()->ExecuteDbStatement('UPDATE chores_log SET chore_id = ' . $choreIdToKeep . ' WHERE chore_id = ' . $choreIdToRemove); $this->getDatabaseService()->ExecuteDbStatement('DELETE FROM chores WHERE id = ' . $choreIdToRemove); } - catch (Exception $ex) + catch (\Exception $ex) { $this->getDatabaseService()->GetDbConnectionRaw()->rollback(); throw $ex; diff --git a/services/DatabaseMigrationService.php b/services/DatabaseMigrationService.php index 646211df..0cff2b57 100644 --- a/services/DatabaseMigrationService.php +++ b/services/DatabaseMigrationService.php @@ -74,7 +74,7 @@ class DatabaseMigrationService extends BaseService $migrationCounter++; } } - catch (Exception $ex) + catch (\Exception $ex) { $this->getDatabaseService()->GetDbConnectionRaw()->rollback(); throw $ex; diff --git a/services/RecipesService.php b/services/RecipesService.php index fd104169..9aa8ab92 100644 --- a/services/RecipesService.php +++ b/services/RecipesService.php @@ -94,7 +94,7 @@ class RecipesService extends BaseService } } } - catch (Exception $ex) + catch (\Exception $ex) { $this->getDatabaseService()->GetDbConnectionRaw()->rollback(); throw $ex; @@ -103,18 +103,21 @@ class RecipesService extends BaseService $recipe = $this->getDatabase()->recipes()->where('id = :1', $recipeId)->fetch(); $productId = $recipe->product_id; + $amount = $recipe->desired_servings; if ($recipe->type == self::RECIPE_TYPE_MEALPLAN_SHADOW) { // Use "Produces product" of the original recipe $mealPlanEntry = $this->getDatabase()->meal_plan()->where('id = :1', explode('#', $recipe->name)[1])->fetch(); - $productId = $this->getDatabase()->recipes()->where('id = :1', $mealPlanEntry->recipe_id)->fetch()->product_id; + $recipe = $this->getDatabase()->recipes()->where('id = :1', $mealPlanEntry->recipe_id)->fetch(); + $productId = $recipe->product_id; + $amount = $mealPlanEntry->recipe_servings; } if (!empty($productId)) { $product = $this->getDatabase()->products()->where('id = :1', $productId)->fetch(); $recipeResolvedRow = $this->getDatabase()->recipes_resolved()->where('recipe_id = :1', $recipeId)->fetch(); - $this->getStockService()->AddProduct($productId, $recipe->desired_servings, null, StockService::TRANSACTION_TYPE_SELF_PRODUCTION, date('Y-m-d'), $recipeResolvedRow->costs_per_serving, null, null, $dummyTransactionId, $product->default_stock_label_type, true); + $this->getStockService()->AddProduct($productId, $amount, null, StockService::TRANSACTION_TYPE_SELF_PRODUCTION, date('Y-m-d'), $recipeResolvedRow->costs_per_serving, null, null, $dummyTransactionId, $product->default_stock_label_type, true); } } diff --git a/services/StockService.php b/services/StockService.php index 70eca912..90483b1f 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -1702,7 +1702,7 @@ class StockService extends BaseService $this->getDatabaseService()->ExecuteDbStatement('UPDATE shopping_list SET product_id = ' . $productIdToKeep . ', amount = amount * ' . $factor . ' WHERE product_id = ' . $productIdToRemove); $this->getDatabaseService()->ExecuteDbStatement('DELETE FROM products WHERE id = ' . $productIdToRemove); } - catch (Exception $ex) + catch (\Exception $ex) { $this->getDatabaseService()->GetDbConnectionRaw()->rollback(); throw $ex; @@ -1749,7 +1749,7 @@ class StockService extends BaseService } } } - catch (Exception $ex) + catch (\Exception $ex) { $this->getDatabaseService()->GetDbConnectionRaw()->rollback(); throw $ex;