diff --git a/controllers/RecipesApiController.php b/controllers/RecipesApiController.php new file mode 100644 index 00000000..fd2cce5a --- /dev/null +++ b/controllers/RecipesApiController.php @@ -0,0 +1,22 @@ +RecipesService = new RecipesService(); + } + + protected $RecipesService; + + public function AddNotFulfilledProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + $this->RecipesService->AddNotFulfilledProductsToShoppingList($args['recipeId']); + return $this->VoidApiActionResponse($response); + } +} diff --git a/migrations/0025.sql b/migrations/0025.sql index e128d4f9..b5e27513 100644 --- a/migrations/0025.sql +++ b/migrations/0025.sql @@ -19,20 +19,32 @@ AS SELECT r.id AS recipe_id, rp.id AS recipe_pos_id, - rp.product_id, + rp.product_id AS product_id, rp.amount AS recipe_amount, sc.amount AS stock_amount, - CASE WHEN sc.amount >= rp.amount THEN 1 ELSE 0 END AS need_fullfiled + CASE WHEN IFNULL(sc.amount, 0) >= rp.amount THEN 1 ELSE 0 END AS need_fulfilled, + CASE WHEN IFNULL(sc.amount, 0) - rp.amount < 0 THEN ABS(sc.amount - rp.amount) ELSE 0 END AS missing_amount, + IFNULL(sl.amount, 0) AS amount_on_shopping_list, + CASE WHEN IFNULL(sc.amount, 0) + IFNULL(sl.amount, 0) >= rp.amount THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list FROM recipes r -LEFT JOIN recipes_pos rp +JOIN recipes_pos rp ON r.id = rp.recipe_id +LEFT JOIN ( + SELECT product_id, SUM(amount + amount_autoadded) 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; CREATE VIEW recipes_fulfillment_sum AS SELECT - rf.recipe_id, - MIN(rf.need_fullfiled) AS need_fullfiled -FROM recipes_fulfillment rf -GROUP BY rf.recipe_id; + r.id AS recipe_id, + IFNULL(MIN(rf.need_fulfilled), 1) AS need_fulfilled, + IFNULL(MIN(rf.need_fulfilled_with_shopping_list), 1) AS need_fulfilled_with_shopping_list, + (SELECT COUNT(*) FROM recipes_fulfillment WHERE recipe_id = rf.recipe_id AND need_fulfilled = 0 AND recipe_pos_id IS NOT NULL) AS missing_products_count +FROM recipes r +LEFT JOIN recipes_fulfillment rf + ON rf.recipe_id = r.id +GROUP BY r.id; diff --git a/package.json b/package.json index dc0b3b0c..8693082e 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "datatables.net-responsive-bs4": "^2.2.3", "datatables.net-colreorder": "^1.5.1", "datatables.net-colreorder-bs4": "^1.5.1", + "datatables.net-select": "^1.2.7", + "datatables.net-select-bs4": "^1.2.7", "jquery": "^3.3.1", "jquery-serializejson": "^2.8.1", "jquery-ui-dist": "^1.12.1", diff --git a/public/viewjs/recipeform.js b/public/viewjs/recipeform.js index 53d82ab1..433e0fa0 100644 --- a/public/viewjs/recipeform.js +++ b/public/viewjs/recipeform.js @@ -96,3 +96,27 @@ $(document).on('click', '.recipe-pos-delete-button', function(e) } }); }); + +$(document).on('click', '.recipe-pos-order-missing-button', function(e) +{ + var productName = $(e.currentTarget).attr('data-product-name'); + var productId = $(e.currentTarget).attr('data-product-id'); + var productAmount = $(e.currentTarget).attr('data-product-amount'); + var recipeName = $(e.currentTarget).attr('data-recipe-name'); + + var jsonData = {}; + jsonData.product_id = productId; + jsonData.amount = productAmount; + jsonData.note = L('Added for recipe #1', recipeName); + + Grocy.Api.Post('add-object/shopping_list', jsonData, + function(result) + { + window.location.href = U('/recipe/' + Grocy.EditObjectId); + }, + function(xhr) + { + console.error(xhr); + } + ); +}); diff --git a/public/viewjs/recipeposform.js b/public/viewjs/recipeposform.js index 7f38c51b..6fa2d7ac 100644 --- a/public/viewjs/recipeposform.js +++ b/public/viewjs/recipeposform.js @@ -46,6 +46,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) { $('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name); $('#amount').focus(); + Grocy.FrontendHelpers.ValidateForm('recipe-pos-form'); }, function(xhr) { diff --git a/public/viewjs/recipes.js b/public/viewjs/recipes.js index 54689654..c96eae2f 100644 --- a/public/viewjs/recipes.js +++ b/public/viewjs/recipes.js @@ -7,7 +7,8 @@ 'language': JSON.parse(L('datatables_localization')), 'scrollY': false, 'colReorder': true, - 'stateSave': true + 'stateSave': true, + 'select': 'single' }); $("#search").on("keyup", function() @@ -56,3 +57,39 @@ $(document).on('click', '.recipe-delete-button', function(e) } }); }); + +$(document).on('click', '.recipe-order-missing-button', function(e) +{ + var objectName = $(e.currentTarget).attr('data-recipe-name'); + var objectId = $(e.currentTarget).attr('data-recipe-id'); + + bootbox.confirm({ + message: L('Are you sure to put all missing ingredients for recipe "#1" on the shopping list?', objectName), + buttons: { + confirm: { + label: L('Yes'), + className: 'btn-success' + }, + cancel: { + label: L('No'), + className: 'btn-danger' + } + }, + callback: function(result) + { + if (result === true) + { + Grocy.Api.Get('recipes/add-not-fulfilled-products-to-shopping-list/' + objectId, + function(result) + { + window.location.href = U('/recipes'); + }, + function(xhr) + { + console.error(xhr); + } + ); + } + } + }); +}); diff --git a/routes.php b/routes.php index aba8932a..2cfc997d 100644 --- a/routes.php +++ b/routes.php @@ -77,6 +77,8 @@ $app->group('/api', function() $this->get('/stock/add-missing-products-to-shoppinglist', 'Grocy\Controllers\StockApiController:AddMissingProductsToShoppingList'); $this->get('/stock/external-barcode-lookup/{barcode}', 'Grocy\Controllers\StockApiController:ExternalBarcodeLookup'); + $this->get('/recipes/add-not-fulfilled-products-to-shopping-list/{recipeId}', 'Grocy\Controllers\RecipesApiController:AddNotFulfilledProductsToShoppingList'); + $this->get('/habits/track-habit-execution/{habitId}', 'Grocy\Controllers\HabitsApiController:TrackHabitExecution'); $this->get('/habits/get-habit-details/{habitId}', 'Grocy\Controllers\HabitsApiController:HabitDetails'); diff --git a/services/BaseService.php b/services/BaseService.php index 82d12abb..43c0af4e 100644 --- a/services/BaseService.php +++ b/services/BaseService.php @@ -3,14 +3,19 @@ namespace Grocy\Services; use \Grocy\Services\DatabaseService; +use \Grocy\Services\LocalizationService; class BaseService { public function __construct() { $this->DatabaseService = new DatabaseService(); $this->Database = $this->DatabaseService->GetDbConnection(); + + $localizationService = new LocalizationService(CULTURE); + $this->LocalizationService = $localizationService; } protected $DatabaseService; protected $Database; + protected $LocalizationService; } diff --git a/services/LocalizationService.php b/services/LocalizationService.php index 56e81d27..5dbe1a48 100644 --- a/services/LocalizationService.php +++ b/services/LocalizationService.php @@ -2,14 +2,12 @@ namespace Grocy\Services; -class LocalizationService extends BaseService +class LocalizationService { const DEFAULT_CULTURE = 'en'; public function __construct(string $culture) { - parent::__construct(); - $this->Culture = $culture; $this->StringsDefaultCulture = $this->LoadLocalizationFile(self::DEFAULT_CULTURE); diff --git a/services/RecipesService.php b/services/RecipesService.php index 02824635..bbad144c 100644 --- a/services/RecipesService.php +++ b/services/RecipesService.php @@ -15,4 +15,27 @@ class RecipesService extends BaseService $sql = 'SELECT * from recipes_fulfillment_sum'; return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); } + + public function AddNotFulfilledProductsToShoppingList($recipeId) + { + $recipe = $this->Database->recipes($recipeId); + + $recipePositions = $this->GetRecipesFulfillment(); + foreach ($recipePositions as $recipePosition) + { + if($recipePosition->recipe_id == $recipeId) + { + $toOrderAmount = $recipePosition->missing_amount - $recipePosition->amount_on_shopping_list; + if($toOrderAmount > 0) + { + $shoppinglistRow = $this->Database->shopping_list()->createRow(array( + 'product_id' => $recipePosition->product_id, + 'amount' => $toOrderAmount, + 'note' => $this->LocalizationService->Localize('Added for recipe #1', $recipe->name) + )); + $shoppinglistRow->save(); + } + } + } + } } diff --git a/views/layout/default.blade.php b/views/layout/default.blade.php index 6fe888f0..171f83fe 100644 --- a/views/layout/default.blade.php +++ b/views/layout/default.blade.php @@ -20,6 +20,7 @@ + @@ -90,7 +91,7 @@ @@ -236,6 +237,8 @@ + + diff --git a/views/recipeform.blade.php b/views/recipeform.blade.php index feabf3ef..d67c093b 100644 --- a/views/recipeform.blade.php +++ b/views/recipeform.blade.php @@ -22,7 +22,7 @@
-
+
@@ -41,7 +41,7 @@
-
+

{{ $L('Ingredients list') }} @@ -60,7 +60,7 @@ @if($mode == "edit") @foreach($recipePositions as $recipePosition) - + @@ -68,13 +68,16 @@ + + + {{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }} {{ $recipePosition->amount }} {{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->qu_id_stock)->name }} - @if(FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->need_fullfiled == 1) {{ $L('Enough in stock') }} @else {{ $L('Not enough in stock') }} @endif + @if(FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->need_fulfilled == 1) {{ $L('Enough in stock') }} @else {{ $L('Not enough in stock, #1 missing, #2 already on shopping list', FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->missing_amount, FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->amount_on_shopping_list) }} @endif @if(strlen($recipePosition->note) > 50) diff --git a/views/recipeposform.blade.php b/views/recipeposform.blade.php index 2b6cbe8e..99045cff 100644 --- a/views/recipeposform.blade.php +++ b/views/recipeposform.blade.php @@ -10,7 +10,7 @@ @section('content')
-
+

@yield('title')

{{ $L('Recipe') }} {{ $recipe->name }}

diff --git a/views/recipes.blade.php b/views/recipes.blade.php index 6435f45f..12404543 100644 --- a/views/recipes.blade.php +++ b/views/recipes.blade.php @@ -35,7 +35,7 @@ @foreach($recipes as $recipe) - + @@ -43,12 +43,16 @@ + + + {{ $recipe->name }} - @if(FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->need_fullfiled == 1){{ $L('Yes') }}@else{{ $L('No') }}@endif + @if(FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->need_fulfilled == 1){{ $L('Yes') }}@else{{ $L('No') }}@endif + @if(FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->need_fulfilled == 1) {{ $L('Enough in stock') }} @else {{ $L('Not enough in stock, #1 ingredients missing', FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->missing_products_count) }} @endif @endforeach diff --git a/views/shoppinglist.blade.php b/views/shoppinglist.blade.php index d53ba95c..51a43871 100644 --- a/views/shoppinglist.blade.php +++ b/views/shoppinglist.blade.php @@ -12,7 +12,7 @@  {{ $L('Add') }} - +  {{ $L('Add products that are below defined min. stock amount') }}

diff --git a/yarn.lock b/yarn.lock index ce8815ce..71ec1a8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -104,6 +104,21 @@ datatables.net-responsive@2.2.3, datatables.net-responsive@^2.2.3: datatables.net "^1.10.15" jquery ">=1.7" +datatables.net-select-bs4@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/datatables.net-select-bs4/-/datatables.net-select-bs4-1.2.7.tgz#5e4ddd8feb412e974b54a15e81b2bb29840ba55b" + dependencies: + datatables.net-bs4 "^1.10.15" + datatables.net-select "1.2.7" + jquery ">=1.7" + +datatables.net-select@1.2.7, datatables.net-select@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/datatables.net-select/-/datatables.net-select-1.2.7.tgz#7d5badfca49c438f8b51df04483d8d77857e917c" + dependencies: + datatables.net "^1.10.15" + jquery ">=1.7" + datatables.net@1.10.16: version "1.10.16" resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.10.16.tgz#4b052d1082824261b68eed9d22741b711d3d2469" @@ -169,10 +184,6 @@ startbootstrap-sb-admin@^4.0.0: jquery "3.3.1" jquery.easing "^1.4.1" -summernote@^0.8.10: - version "0.8.10" - resolved "https://registry.yarnpkg.com/summernote/-/summernote-0.8.10.tgz#21a5d7f18a3b07500b58b60d5907417a54897520" - swagger-ui-dist@^3.17.3: version "3.17.3" resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.17.3.tgz#dfb96408ccc46775155f7369190c5d4b2016fe5c"