diff --git a/changelog/55_UNRELEASED_2019-xx-xx.md b/changelog/55_UNRELEASED_2019-xx-xx.md index 9ce03268..7904e63a 100644 --- a/changelog/55_UNRELEASED_2019-xx-xx.md +++ b/changelog/55_UNRELEASED_2019-xx-xx.md @@ -28,6 +28,7 @@ ### Meal plan improvements/fixes - It's now possible to add notes per day (in the dropdown of the add button in the header of each day column) +- It's now possible to products directly (also in the dropdown of the add button in the header of each day column, maybe useful in combination with the new "Self produced products" feature) - Added that the calories per serving are now also shown - Added that the total costs and calories per day are displayed in the header of each day column - Fixed that when `FEATURE_FLAG_STOCK_PRICE_TRACKING` was set to `false`, prices were still shown (thanks @kriddles) diff --git a/controllers/RecipesController.php b/controllers/RecipesController.php index ed0d8005..7f7ca71e 100644 --- a/controllers/RecipesController.php +++ b/controllers/RecipesController.php @@ -3,6 +3,7 @@ namespace Grocy\Controllers; use \Grocy\Services\RecipesService; +use \Grocy\Services\StockService; use \Grocy\Services\UserfieldsService; class RecipesController extends BaseController @@ -11,10 +12,12 @@ class RecipesController extends BaseController { parent::__construct($container); $this->RecipesService = new RecipesService(); + $this->StockService = new StockService(); $this->UserfieldsService = new UserfieldsService(); } protected $RecipesService; + protected $StockService; protected $UserfieldsService; public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) @@ -143,6 +146,12 @@ class RecipesController extends BaseController $title = $recipe->name; } + $productDetails = null; + if ($mealPlanEntry['product_id'] !== null) + { + $productDetails = $this->StockService->GetProductDetails($mealPlanEntry['product_id']); + } + $events[] = array( 'id' => $mealPlanEntry['id'], 'title' => $title, @@ -150,7 +159,8 @@ class RecipesController extends BaseController 'date_format' => 'date', 'recipe' => json_encode($recipe), 'mealPlanEntry' => json_encode($mealPlanEntry), - 'type' => $mealPlanEntry['type'] + 'type' => $mealPlanEntry['type'], + 'productDetails' => json_encode($productDetails) ); } @@ -158,7 +168,10 @@ class RecipesController extends BaseController 'fullcalendarEventSources' => $events, 'recipes' => $recipes, 'internalRecipes' => $this->Database->recipes()->whereNot('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(), - 'recipesResolved' => $this->RecipesService->GetRecipesResolved() + 'recipesResolved' => $this->RecipesService->GetRecipesResolved(), + 'products' => $this->Database->products()->orderBy('name'), + 'quantityUnits' => $this->Database->quantity_units()->orderBy('name'), + 'quantityUnitConversionsResolved' => $this->Database->quantity_unit_conversions_resolved() ]); } } diff --git a/localization/strings.pot b/localization/strings.pot index bf238383..96863ba1 100644 --- a/localization/strings.pot +++ b/localization/strings.pot @@ -1651,3 +1651,9 @@ msgstr "" msgid "Only undone items" msgstr "" + +msgid "Add product" +msgstr "" + +msgid "Add product to %s" +msgstr "" diff --git a/migrations/0096.sql b/migrations/0096.sql index a3e66a30..9c2421d5 100644 --- a/migrations/0096.sql +++ b/migrations/0096.sql @@ -9,6 +9,9 @@ CREATE TABLE meal_plan ( recipe_id INTEGER, recipe_servings INTEGER DEFAULT 1, note TEXT, + product_id INTEGER, + product_amount REAL DEFAULT 0, + product_qu_id INTEGER, row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) ); @@ -67,6 +70,26 @@ BEGIN AND type = 'recipe' AND recipe_id IS NOT NULL GROUP BY recipe_id; + + -- Add all products for this day as ingredients in the day-recipe + INSERT INTO recipes_pos + (recipe_id, product_id, amount, qu_id) + SELECT (SELECT id FROM recipes WHERE name = NEW.day AND type = 'mealplan-day'), product_id, SUM(product_amount), product_qu_id + FROM meal_plan + WHERE day = NEW.day + AND type = 'product' + AND product_id IS NOT NULL + GROUP BY product_id, product_qu_id; + + -- Add all products for this week as ingredients recipes in the week-recipe + INSERT INTO recipes_pos + (recipe_id, product_id, amount, qu_id) + SELECT (SELECT id FROM recipes WHERE name = LTRIM(STRFTIME('%Y-%W', NEW.day), '0') AND type = 'mealplan-week'), product_id, SUM(product_amount), product_qu_id + FROM meal_plan + WHERE STRFTIME('%Y-%W', day) = STRFTIME('%Y-%W', NEW.day) + AND type = 'product' + AND product_id IS NOT NULL + GROUP BY product_id, product_qu_id; END; CREATE TRIGGER remove_internal_recipe AFTER DELETE ON meal_plan @@ -117,4 +140,24 @@ BEGIN AND type = 'recipe' AND recipe_id IS NOT NULL GROUP BY recipe_id; + + -- Add all products for this day as ingredients in the day-recipe + INSERT INTO recipes_pos + (recipe_id, product_id, amount, qu_id) + SELECT (SELECT id FROM recipes WHERE name = OLD.day AND type = 'mealplan-day'), product_id, SUM(product_amount), product_qu_id + FROM meal_plan + WHERE day = OLD.day + AND type = 'product' + AND product_id IS NOT NULL + GROUP BY product_id, product_qu_id; + + -- Add all products for this week as ingredients recipes in the week-recipe + INSERT INTO recipes_pos + (recipe_id, product_id, amount, qu_id) + SELECT (SELECT id FROM recipes WHERE name = LTRIM(STRFTIME('%Y-%W', OLD.day), '0') AND type = 'mealplan-week'), product_id, SUM(product_amount), product_qu_id + FROM meal_plan + WHERE STRFTIME('%Y-%W', day) = STRFTIME('%Y-%W', OLD.day) + AND type = 'product' + AND product_id IS NOT NULL + GROUP BY product_id, product_qu_id; END; diff --git a/public/viewjs/mealplan.js b/public/viewjs/mealplan.js index 9ddee5be..fa826db5 100644 --- a/public/viewjs/mealplan.js +++ b/public/viewjs/mealplan.js @@ -35,6 +35,7 @@ var calendar = $("#calendar").fullCalendar({ \ \ '); @@ -157,6 +158,90 @@ var calendar = $("#calendar").fullCalendar({ } } } + if (event.type == "product") + { + var productDetails = JSON.parse(event.productDetails); + if (productDetails === null || productDetails === undefined) + { + return false; + } + + if (productDetails.last_price === null) + { + productDetails.last_price = 0; + } + + element.attr("data-product-details", event.productDetails); + + var productOrderMissingButtonDisabledClasses = "disabled"; + if (productDetails.stock_amount_aggregated < mealPlanEntry.product_amount) + { + productOrderMissingButtonDisabledClasses = ""; + } + + var productConsumeButtonDisabledClasses = "disabled"; + if (productDetails.stock_amount_aggregated >= mealPlanEntry.product_amount) + { + productConsumeButtonDisabledClasses = ""; + } + + var fulfillmentInfoHtml = __t('Enough in stock'); + var fulfillmentIconHtml = ''; + if (productDetails.stock_amount_aggregated < mealPlanEntry.product_amount) + { + fulfillmentInfoHtml = __t('Not enough in stock'); + var fulfillmentIconHtml = ''; + } + + var costsAndCaloriesPerServing = "" + if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) + { + costsAndCaloriesPerServing = '
' + productDetails.last_price * mealPlanEntry.product_amount + ' / ' + productDetails.product.calories * mealPlanEntry.product_amount + ' kcal ' + '
'; + } + else + { + costsAndCaloriesPerServing = '
' + productDetails.product.calories * mealPlanEntry.product_amount + ' kcal ' + '
'; + } + + element.html('\ +
\ +
' + productDetails.product.name + '
\ +
" + __n(mealPlanEntry.product_amount, productDetails.quantity_unit_purchase.name, productDetails.quantity_unit_purchase.name_plural) + '
\ +
' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '
\ + ' + costsAndCaloriesPerServing + ' \ +
\ + \ +
\ +
'); + + if (productDetails.product.picture_file_name && !productDetails.product.picture_file_name.isEmpty()) + { + element.html(element.html() + '
') + } + + var dayRecipeName = event.start.format("YYYY-MM-DD"); + if (!$("#day-summary-" + dayRecipeName).length) // This runs for every event/recipe, so maybe multiple times per day, so only add the day summary once + { + var dayRecipe = FindObjectInArrayByPropertyValue(internalRecipes, "name", dayRecipeName); + if (dayRecipe != null) + { + var dayRecipeResolved = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", dayRecipe.id); + + var costsAndCaloriesPerDay = "" + if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) + { + costsAndCaloriesPerDay = '
' + dayRecipeResolved.costs + ' / ' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; + } + else + { + costsAndCaloriesPerDay = '
' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; + } + $(".fc-day-header[data-date='" + dayRecipeName + "']").append('
' + costsAndCaloriesPerDay + '
'); + } + } + } else if (event.type == "note") { element.html('\ @@ -203,6 +288,17 @@ $(document).on("click", ".add-note-button", function(e) Grocy.FrontendHelpers.ValidateForm("add-note-form"); }); +$(document).on("click", ".add-product-button", function(e) +{ + var day = $(this).parent().parent().parent().data("date"); + + $("#add-product-modal-title").text(__t("Add product to %s", day.toString())); + $("#day").val(day.toString()); + Grocy.Components.ProductPicker.Clear(); + $("#add-product-modal").modal("show"); + Grocy.FrontendHelpers.ValidateForm("add-product-form"); +}); + $("#add-recipe-modal").on("shown.bs.modal", function(e) { Grocy.Components.RecipePicker.GetInputElement().focus(); @@ -213,23 +309,12 @@ $("#add-note-modal").on("shown.bs.modal", function (e) $("#note").focus(); }) -$(document).on("click", ".remove-recipe-button", function(e) +$("#add-product-modal").on("shown.bs.modal", function (e) { - var mealPlanEntry = JSON.parse($(this).parents(".fc-h-event:first").attr("data-meal-plan-entry")); + Grocy.Components.ProductPicker.GetInputElement().focus(); +}) - Grocy.Api.Delete('objects/meal_plan/' + mealPlanEntry.id.toString(), { }, - function(result) - { - window.location.reload(); - }, - function(xhr) - { - Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) - } - ); -}); - -$(document).on("click", ".remove-note-button", function(e) +$(document).on("click", ".remove-recipe-button, .remove-note-button, .remove-product-button", function(e) { var mealPlanEntry = JSON.parse($(this).parents(".fc-h-event:first").attr("data-meal-plan-entry")); @@ -289,6 +374,34 @@ $('#save-add-note-button').on('click', function(e) ); }); +$('#save-add-product-button').on('click', function(e) +{ + e.preventDefault(); + + if (document.getElementById("add-product-form").checkValidity() === false) //There is at least one validation error + { + return false; + } + + var jsonData = $('#add-product-form').serializeJSON(); + jsonData.day = $("#day").val(); + delete jsonData.display_amount; + jsonData.product_amount = jsonData.amount; + delete jsonData.amount; + jsonData.product_qu_id = jsonData.qu_id; + delete jsonData.qu_id; + Grocy.Api.Post('objects/meal_plan', jsonData, + function(result) + { + window.location.reload(); + }, + function(xhr) + { + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); +}); + Grocy.Components.RecipePicker.GetInputElement().keydown(function(event) { if (event.keyCode === 13) //Enter @@ -461,3 +574,27 @@ $(window).on("resize", function() calendar.fullCalendar("changeView", "basicWeek"); } }); + +Grocy.Components.ProductPicker.GetPicker().on('change', function(e) +{ + var productId = $(e.target).val(); + + if (productId) + { + Grocy.Api.Get('stock/products/' + productId, + function(productDetails) + { + Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id, true); + + $('#display_amount').val(1); + $('#display_amount').focus(); + $(".input-group-productamountpicker").trigger("change"); + Grocy.FrontendHelpers.ValidateForm('add-product-form'); + }, + function(xhr) + { + console.error(xhr); + } + ); + } +}); diff --git a/views/mealplan.blade.php b/views/mealplan.blade.php index e4759cad..5eec2e83 100644 --- a/views/mealplan.blade.php +++ b/views/mealplan.blade.php @@ -18,6 +18,9 @@ var fullcalendarEventSources = {!! json_encode(array($fullcalendarEventSources)) !!} var internalRecipes = {!! json_encode($internalRecipes) !!} var recipesResolved = {!! json_encode($recipesResolved) !!} + + Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!}; + Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
@@ -95,4 +98,35 @@
+ + @stop