mirror of
https://github.com/grocy/grocy.git
synced 2026-03-27 23:29:25 +01:00
allow add full recipe required ingredient amount
This modifies the bootbox on to add recipe ingredients
to the shopping list in a way, that it
- button to open bootbox is always enabled
- popup lists all* ingredients, independent of fulfillment state
- two new quick-select buttons to check only missing or all ingredients
- radio button selection to define if the full or only missing amount
shall be added to the shopping list
*This still excludes ingredients which have the stock check disabled
in a recipe.
In order to get this information, the RecipesController was modified
that it adds the data from the recipe ingredient setting as new
additional field in the recipePositionsResolved.
closes https://github.com/grocy/grocy/issues/686
This commit is contained in:
parent
2a124a3d47
commit
a42d151c93
|
|
@ -16,13 +16,19 @@ class RecipesApiController extends BaseApiController
|
|||
|
||||
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
|
||||
$excludedProductIds = null;
|
||||
$ignoreStock = false;
|
||||
|
||||
if ($requestBody !== null && array_key_exists('excludedProductIds', $requestBody))
|
||||
{
|
||||
$excludedProductIds = $requestBody['excludedProductIds'];
|
||||
}
|
||||
|
||||
$this->getRecipesService()->AddNotFulfilledProductsToShoppingList($args['recipeId'], $excludedProductIds);
|
||||
if ($requestBody !== null && array_key_exists('ignoreStock', $requestBody))
|
||||
{
|
||||
$ignoreStock = $requestBody['ignoreStock'];
|
||||
}
|
||||
|
||||
$this->getRecipesService()->AddNotFulfilledProductsToShoppingList($args['recipeId'], $excludedProductIds, $ignoreStock);
|
||||
return $this->EmptyApiResponse($response);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -105,10 +105,16 @@ class RecipesController extends BaseController
|
|||
$totalCalories = FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $selectedRecipe->id)->calories;
|
||||
}
|
||||
|
||||
$recipePositionsResolved = $this->getDatabase()->recipes_pos_resolved()->where('recipe_id', $selectedRecipe->id);
|
||||
foreach ($recipePositionsResolved as $pos)
|
||||
{
|
||||
$pos["recipe_pos_data"] = $this->getDatabase()->recipes_pos($pos->recipe_pos_id);
|
||||
}
|
||||
|
||||
$viewData = [
|
||||
'recipes' => $recipes,
|
||||
'recipesResolved' => $recipesResolved,
|
||||
'recipePositionsResolved' => $this->getDatabase()->recipes_pos_resolved()->where('recipe_id', $selectedRecipe->id),
|
||||
'recipePositionsResolved' => $recipePositionsResolved,
|
||||
'selectedRecipe' => $selectedRecipe,
|
||||
'products' => $this->getDatabase()->products(),
|
||||
'quantityUnits' => $this->getDatabase()->quantity_units(),
|
||||
|
|
|
|||
|
|
@ -190,9 +190,13 @@ $(document).on('click', '.recipe-shopping-list', function(e)
|
|||
{
|
||||
var objectName = $(e.currentTarget).attr('data-recipe-name');
|
||||
var objectId = $(e.currentTarget).attr('data-recipe-id');
|
||||
var popUpTemplate = $("#missing-recipe-pos-list")[0]; // save template html for later
|
||||
var popUpHtml = popUpTemplate.outerHTML.replace("d-none", ""); // prepare visible html for current popup
|
||||
var popUpTemplateParent = popUpTemplate.parentElement // remember where the template element was
|
||||
$("#missing-recipe-pos-list").remove() // delete the template from the dom, as we are about to add it into the popup
|
||||
|
||||
bootbox.confirm({
|
||||
message: __t('Are you sure you want to put all missing ingredients for recipe "%s" on the shopping list?', objectName) + "<br><br>" + __t("Uncheck ingredients to not put them on the shopping list") + ":" + $("#missing-recipe-pos-list")[0].outerHTML.replace("d-none", ""),
|
||||
message: __t('Are you sure you want to put all selected ingredients for recipe "%s" on the shopping list?', objectName) + "<br><br>" + __t("Uncheck ingredients to not put them on the shopping list") + ":" + popUpHtml,
|
||||
closeButton: false,
|
||||
buttons: {
|
||||
confirm: {
|
||||
|
|
@ -211,12 +215,16 @@ $(document).on('click', '.recipe-shopping-list', function(e)
|
|||
Grocy.FrontendHelpers.BeginUiBusy();
|
||||
|
||||
var excludedProductIds = new Array();
|
||||
$(".missing-recipe-pos-product-checkbox:checkbox:not(:checked)").each(function()
|
||||
$(".missing-recipe-pos-product-checkbox").each(function()
|
||||
{
|
||||
excludedProductIds.push($(this).data("product-id"));
|
||||
if ($(this).data("ignore") || !$(this)[0].checked)
|
||||
{
|
||||
excludedProductIds.push($(this).data("product-id"));
|
||||
}
|
||||
});
|
||||
var ignoreStock = $("#missing-recipe-pos-list-full-recipe")[0];
|
||||
|
||||
Grocy.Api.Post('recipes/' + objectId + '/add-not-fulfilled-products-to-shoppinglist', { "excludedProductIds": excludedProductIds },
|
||||
Grocy.Api.Post('recipes/' + objectId + '/add-not-fulfilled-products-to-shoppinglist', { "excludedProductIds": excludedProductIds, "ignoreStock": ignoreStock.checked },
|
||||
function(result)
|
||||
{
|
||||
window.location.reload();
|
||||
|
|
@ -228,10 +236,28 @@ $(document).on('click', '.recipe-shopping-list', function(e)
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
popUpTemplateParent.append(popUpTemplate); // restore template, in case we don't reload the page and need the popup again
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#missing-recipe-pos-list-select-missing', function(e)
|
||||
{
|
||||
$(".missing-recipe-pos-product-checkbox").each(function()
|
||||
{
|
||||
$(this)[0].checked = !$(this).data("need-fulfilled");
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#missing-recipe-pos-list-select-all', function(e)
|
||||
{
|
||||
$(".missing-recipe-pos-product-checkbox").each(function()
|
||||
{
|
||||
$(this)[0].checked = true;
|
||||
});
|
||||
});
|
||||
|
||||
$(".recipe-consume").on('click', function(e)
|
||||
{
|
||||
var objectName = $(e.currentTarget).attr('data-recipe-name');
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class RecipesService extends BaseService
|
|||
const RECIPE_TYPE_MEALPLAN_SHADOW = 'mealplan-shadow'; // A recipe per meal plan recipe (for separated stock fulfillment checking) => name = YYYY-MM-DD#<meal_plan.id>
|
||||
const RECIPE_TYPE_NORMAL = 'normal'; // Normal / manually created recipes
|
||||
|
||||
public function AddNotFulfilledProductsToShoppingList($recipeId, $excludedProductIds = null)
|
||||
public function AddNotFulfilledProductsToShoppingList($recipeId, $excludedProductIds = null, $ignoreStock = false)
|
||||
{
|
||||
$recipe = $this->getDataBase()->recipes($recipeId);
|
||||
$recipePositions = $this->GetRecipesPosResolved();
|
||||
|
|
@ -26,13 +26,26 @@ class RecipesService extends BaseService
|
|||
if ($recipePosition->recipe_id == $recipeId && !in_array($recipePosition->product_id, $excludedProductIds))
|
||||
{
|
||||
$product = $this->getDataBase()->products($recipePosition->product_id);
|
||||
$toOrderAmount = round(($recipePosition->missing_amount - $recipePosition->amount_on_shopping_list), 2);
|
||||
$quId = $product->qu_id_purchase;
|
||||
|
||||
if ($recipe->not_check_shoppinglist == 1)
|
||||
{
|
||||
$toOrderAmount = round($recipePosition->missing_amount, 2);
|
||||
// Determine order amount
|
||||
// First, define the base amount, depending on if the full recipe amount or just the missing amount shall be used
|
||||
if ($ignoreStock) {
|
||||
$toOrderAmount = $recipePosition->recipe_amount;
|
||||
}
|
||||
else {
|
||||
$toOrderAmount = $recipePosition->missing_amount;
|
||||
}
|
||||
|
||||
// Then, decide if on top of the base amount, the shopping cart shall be considered as well
|
||||
if ($recipe->not_check_shoppinglist == 0)
|
||||
{
|
||||
$toOrderAmount = round($toOrderAmount - $recipePosition->amount_on_shopping_list, 2);
|
||||
}
|
||||
else {
|
||||
$toOrderAmount = round($toOrderAmount, 2);
|
||||
}
|
||||
|
||||
$quId = $product->qu_id_purchase;
|
||||
|
||||
// When the recipe ingredient option "Only check if any amount is in stock" is enabled,
|
||||
// any QU can be used and the amount is not based on qu_stock then
|
||||
|
|
|
|||
|
|
@ -28,6 +28,17 @@
|
|||
column-count: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.missing-recipe-pos-list-radio-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.missing-recipe-pos-list-selection-buttons {
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
|
|
@ -340,7 +351,7 @@
|
|||
data-recipe-name="{{ $recipe->name }}">
|
||||
<i class="fa-solid fa-utensils"></i>
|
||||
</a>
|
||||
<a class="btn @if(!GROCY_FEATURE_FLAG_SHOPPINGLIST) d-none @endif recipe-shopping-list @if(FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $recipe->id)->need_fulfilled_with_shopping_list == 1) disabled @endif"
|
||||
<a class="btn @if(!GROCY_FEATURE_FLAG_SHOPPINGLIST) d-none @endif recipe-shopping-list"
|
||||
href="#"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('Put missing products on shopping list') }}"
|
||||
|
|
@ -376,7 +387,7 @@
|
|||
data-recipe-name="{{ $recipe->name }}">
|
||||
<i class="fa-solid fa-utensils"></i>
|
||||
</a>
|
||||
<a class="btn recipe-shopping-list @if(FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $recipe->id)->need_fulfilled_with_shopping_list == 1) disabled @endif"
|
||||
<a class="btn recipe-shopping-list"
|
||||
href="#"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('Put missing products on shopping list') }}"
|
||||
|
|
@ -580,20 +591,56 @@
|
|||
|
||||
<div id="missing-recipe-pos-list"
|
||||
class="list-group d-none mt-3">
|
||||
<div class="missing-recipe-pos-list-selection-buttons">
|
||||
<button id="missing-recipe-pos-list-select-missing"
|
||||
class="btn btn-secondary">{{ $__t('Only Missing') }}</button>
|
||||
<button id="missing-recipe-pos-list-select-all"
|
||||
class="btn btn-secondary">{{ $__t('All') }}</button>
|
||||
</div>
|
||||
@foreach($recipePositionsResolved as $recipePos)
|
||||
@if(in_array($recipePos->recipe_id, $includedRecipeIdsAbsolute) && $recipePos->missing_amount > 0)
|
||||
@if(in_array($recipePos->recipe_id, $includedRecipeIdsAbsolute))
|
||||
<a href="#"
|
||||
class="list-group-item list-group-item-action list-group-item-primary missing-recipe-pos-select-button">
|
||||
class="list-group-item list-group-item-action list-group-item-primary missing-recipe-pos-select-button @if($recipePos->recipe_pos_data->not_check_stock_fulfillment == 1) d-none @endif">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input missing-recipe-pos-product-checkbox"
|
||||
type="checkbox"
|
||||
data-product-id="{{ $recipePos->product_id }}"
|
||||
checked>
|
||||
data-ignore="{{ $recipePos->recipe_pos_data->not_check_stock_fulfillment }}"
|
||||
data-need-fulfilled="{{ $recipePos->need_fulfilled }}"
|
||||
@if(!$recipePos->need_fulfilled) checked @endif>
|
||||
</div>
|
||||
{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePos->product_id)->name }}
|
||||
{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePos->product_id)->name }}@if($recipePos->missing_amount > 0) ({{ $recipePos->missing_amount }} {{ $__t('missing') }})@endif
|
||||
</a>
|
||||
@endif
|
||||
@endforeach
|
||||
<div class="missing-recipe-pos-list-radio-buttons">
|
||||
<script>$('[data-toggle="tooltip"]').tooltip();</script>
|
||||
<div>
|
||||
<input class="missing-recipe-pos-list-only-missing"
|
||||
type="radio"
|
||||
id="missing-recipe-pos-list-only-missing"
|
||||
name="missing-recipe-pos-list"
|
||||
val="1"
|
||||
checked>
|
||||
<label
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('Only the from stock missing amount will be added to the shopping list') }}"
|
||||
for="missing-recipe-pos-list-only-missing">{{ $__t('missing amount') }}
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input class="missing-recipe-pos-list-only-missing"
|
||||
type="radio"
|
||||
id="missing-recipe-pos-list-full-recipe"
|
||||
name="missing-recipe-pos-list"
|
||||
val="2">
|
||||
<label
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('The full recipe required amount will be added to the shopping list, regardless of your current stock') }}"
|
||||
for="missing-recipe-pos-list-full-recipe">{{ $__t('full recipe amount') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user