diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php
index 8e54aae2..24d6a84d 100644
--- a/controllers/StockApiController.php
+++ b/controllers/StockApiController.php
@@ -263,7 +263,15 @@ class StockApiController extends BaseApiController
$recipeId = $requestBody['recipe_id'];
}
- $bookingId = $this->getStockService()->ConsumeProduct($args['productId'], $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId, $locationId);
+ $consumeExact = false;
+
+ if (array_key_exists('exact_amount', $requestBody))
+ {
+ $consumeExact = $requestBody['exact_amount'];
+ }
+ $transactionId = null;
+
+ $bookingId = $this->getStockService()->ConsumeProduct($args['productId'], $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId, $locationId, $transactionId, false, $consumeExact);
return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId));
}
catch (\Exception $ex)
diff --git a/public/js/grocy_dbchangedhandling.js b/public/js/grocy_dbchangedhandling.js
index 77ee6c0c..b92e7b3e 100644
--- a/public/js/grocy_dbchangedhandling.js
+++ b/public/js/grocy_dbchangedhandling.js
@@ -15,31 +15,34 @@
// Check if the database has changed once a minute
// If a change is detected, reload the current page, but only if already idling for at least 50 seconds,
// when there is no unsaved form data and when the user enabled auto reloading
-setInterval(function()
+if (Grocy.DbChangedHandlingEnabledForPage)
{
- Grocy.Api.Get('system/db-changed-time',
- function(result)
- {
- var newDbChangedTime = moment(result.changed_time);
- if (newDbChangedTime.isAfter(Grocy.DatabaseChangedTime))
+ setInterval(function()
+ {
+ Grocy.Api.Get('system/db-changed-time',
+ function(result)
{
- if (Grocy.IdleTime >= 50)
+ var newDbChangedTime = moment(result.changed_time);
+ if (newDbChangedTime.isAfter(Grocy.DatabaseChangedTime))
{
- if (BoolVal(Grocy.UserSettings.auto_reload_on_db_change) && $("form.is-dirty").length === 0 && !$("body").hasClass("fullscreen-card"))
+ if (Grocy.IdleTime >= 50)
{
- window.location.reload();
+ if (BoolVal(Grocy.UserSettings.auto_reload_on_db_change) && $("form.is-dirty").length === 0 && !$("body").hasClass("fullscreen-card"))
+ {
+ window.location.reload();
+ }
}
- }
- Grocy.DatabaseChangedTime = newDbChangedTime;
+ Grocy.DatabaseChangedTime = newDbChangedTime;
+ }
+ },
+ function(xhr)
+ {
+ console.error(xhr);
}
- },
- function(xhr)
- {
- console.error(xhr);
- }
- );
-}, 60000);
+ );
+ }, 60000);
+}
Grocy.IdleTime = 0;
Grocy.ResetIdleTime = function()
diff --git a/public/viewjs/consume.js b/public/viewjs/consume.js
index e007dc55..69e772be 100644
--- a/public/viewjs/consume.js
+++ b/public/viewjs/consume.js
@@ -9,6 +9,7 @@
var jsonData = {};
jsonData.amount = jsonForm.amount;
+ jsonData.exact_amount = (jsonForm.exact_amount == "on");
jsonData.spoiled = $('#spoiled').is(':checked');
if ($("#use_specific_stock_entry").is(":checked"))
@@ -70,7 +71,7 @@
$("#use_specific_stock_entry").click();
}
- if (productDetails.product.enable_tare_weight_handling == 1)
+ if (productDetails.product.enable_tare_weight_handling == 1 && !jsonData.exact_amount)
{
var successMessage = __t('Removed %1$s of %2$s from stock', Math.abs(jsonForm.amount - (parseFloat(productDetails.product.tare_weight) + parseFloat(productDetails.stock_amount))) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + __t("Undo") + '';
}
@@ -177,11 +178,11 @@ $('#save-mark-as-open-button').on('click', function(e)
}
);
});
-
+var sumValue = 0;
$("#location_id").on('change', function(e)
{
var locationId = $(e.target).val();
- var sumValue = 0;
+ sumValue = 0;
var stockId = null;
$("#specific_stock_entry").find("option").remove().end().append("");
@@ -228,37 +229,8 @@ $("#location_id").on('change', function(e)
Grocy.Api.Get('stock/products/' + Grocy.Components.ProductPicker.GetValue(),
function(productDetails)
{
- if (productDetails.product.enable_tare_weight_handling == 1)
- {
- $("#amount").attr("min", productDetails.product.tare_weight);
- $('#amount').attr('max', sumValue + parseFloat(productDetails.product.tare_weight));
- $("#amount").parent().find(".invalid-feedback").text(__t('The amount must be between %1$s and %2$s', parseFloat(productDetails.product.tare_weight).toLocaleString(), (parseFloat(productDetails.stock_amount) + parseFloat(productDetails.product.tare_weight)).toLocaleString()));
- $("#tare-weight-handling-info").removeClass("d-none");
- }
- else
- {
- $("#tare-weight-handling-info").addClass("d-none");
-
- if (productDetails.product.allow_partial_units_in_stock == 1)
- {
- $("#amount").attr("min", "0.01");
- $("#amount").attr("step", "0.01");
- $("#amount").parent().find(".invalid-feedback").text(__t('The amount must be between %1$s and %2$s', 0.01.toLocaleString(), parseFloat(productDetails.stock_amount).toLocaleString()));
- }
- else
- {
- $("#amount").attr("min", "1");
- $("#amount").attr("step", "1");
- $("#amount").parent().find(".invalid-feedback").text(__t('The amount must be between %1$s and %2$s', "1", parseFloat(productDetails.stock_amount).toLocaleString()));
- }
-
- $('#amount').attr('max', sumValue);
-
- if (sumValue == 0)
- {
- $("#amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location'));
- }
- }
+ current_productDetails = productDetails;
+ RefreshForm();
},
function(xhr)
{
@@ -447,7 +419,7 @@ $("#specific_stock_entry").on("change", function(e)
{
if ($(e.target).val() == "")
{
- var sumValue = 0;
+ sumValue = 0;
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries',
function(stockEntries)
{
@@ -572,3 +544,49 @@ $("#scan-mode-button").on("click", function(e)
$("#scan-mode-status").text(__t("off"));
}
});
+
+$('#consume-exact-amount').on('change', RefreshForm);
+var current_productDetails;
+function RefreshForm()
+{
+ var productDetails = current_productDetails;
+ if (productDetails.product.enable_tare_weight_handling == 1)
+ {
+ $("#consume-exact-amount").parent().removeClass("d-none");
+ }
+ else
+ {
+ $("#consume-exact-amount").parent().addClass("d-none");
+ }
+ if (productDetails.product.enable_tare_weight_handling == 1 && !$('#consume-exact-amount').is(':checked'))
+ {
+ $("#amount").attr("min", productDetails.product.tare_weight);
+ $('#amount').attr('max', sumValue + parseFloat(productDetails.product.tare_weight));
+ $("#amount").parent().find(".invalid-feedback").text(__t('The amount must be between %1$s and %2$s', parseFloat(productDetails.product.tare_weight).toLocaleString(), (parseFloat(productDetails.stock_amount) + parseFloat(productDetails.product.tare_weight)).toLocaleString()));
+ $("#tare-weight-handling-info").removeClass("d-none");
+ }
+ else
+ {
+ $("#tare-weight-handling-info").addClass("d-none");
+
+ if (productDetails.product.allow_partial_units_in_stock == 1)
+ {
+ $("#amount").attr("min", "0.01");
+ $("#amount").attr("step", "0.01");
+ $("#amount").parent().find(".invalid-feedback").text(__t('The amount must be between %1$s and %2$s', 0.01.toLocaleString(), parseFloat(productDetails.stock_amount).toLocaleString()));
+ }
+ else
+ {
+ $("#amount").attr("min", "1");
+ $("#amount").attr("step", "1");
+ $("#amount").parent().find(".invalid-feedback").text(__t('The amount must be between %1$s and %2$s', "1", parseFloat(productDetails.stock_amount).toLocaleString()));
+ }
+
+ $('#amount').attr('max', sumValue);
+
+ if (sumValue == 0)
+ {
+ $("#amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location'));
+ }
+ }
+}
diff --git a/public/viewjs/recipeform.js b/public/viewjs/recipeform.js
index 09501c47..a736b9f3 100644
--- a/public/viewjs/recipeform.js
+++ b/public/viewjs/recipeform.js
@@ -1,4 +1,4 @@
-function saveRecipePicture(result, location)
+function saveRecipePicture(result, location, jsonData)
{
$recipeId = Grocy.EditObjectId || result.created_object_id;
Grocy.Components.UserfieldsForm.Save(() =>
@@ -43,7 +43,7 @@ $('.save-recipe').on('click', function(e)
{
console.log(jsonData);
Grocy.Api.Post('objects/recipes', jsonData,
- (result) => saveRecipePicture(result, location));
+ (result) => saveRecipePicture(result, location, jsonData));
return;
}
@@ -65,7 +65,7 @@ $('.save-recipe').on('click', function(e)
}
Grocy.Api.Put('objects/recipes/' + Grocy.EditObjectId, jsonData,
- (result) => saveRecipePicture(result, location),
+ (result) => saveRecipePicture(result, location, jsonData),
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy("recipe-form");
diff --git a/public/viewjs/shoppinglistitemform.js b/public/viewjs/shoppinglistitemform.js
index db706114..f08dae77 100644
--- a/public/viewjs/shoppinglistitemform.js
+++ b/public/viewjs/shoppinglistitemform.js
@@ -139,7 +139,7 @@ if (Grocy.EditMode === "edit")
$('#amount').on('focus', function(e)
{
- if (Grocy.Components.ProductPicker.GetValue().length === 0)
+ if (Grocy.Components.ProductPicker.GetValue().length === 0 && Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK)
{
Grocy.Components.ProductPicker.GetInputElement().focus();
}
diff --git a/services/RecipesService.php b/services/RecipesService.php
index 697ecde9..5e5e53bb 100644
--- a/services/RecipesService.php
+++ b/services/RecipesService.php
@@ -58,7 +58,7 @@ class RecipesService extends BaseService
{
if ($recipePosition->only_check_single_unit_in_stock == 0)
{
- $this->getStockService()->ConsumeProduct($recipePosition->product_id, $recipePosition->recipe_amount, false, StockService::TRANSACTION_TYPE_CONSUME, 'default', $recipeId, null, $transactionId, true);
+ $this->getStockService()->ConsumeProduct($recipePosition->product_id, $recipePosition->recipe_amount, false, StockService::TRANSACTION_TYPE_CONSUME, 'default', $recipeId, null, $transactionId, true, true);
}
}
diff --git a/services/StockService.php b/services/StockService.php
index 959f8554..b5a0ea49 100644
--- a/services/StockService.php
+++ b/services/StockService.php
@@ -210,7 +210,7 @@ class StockService extends BaseService
$this->getDatabase()->shopping_list()->where('shopping_list_id = :1', $listId)->delete();
}
- public function ConsumeProduct(int $productId, float $amount, bool $spoiled, $transactionType, $specificStockEntryId = 'default', $recipeId = null, $locationId = null, &$transactionId = null, $allowSubproductSubstitution = false)
+ public function ConsumeProduct(int $productId, float $amount, bool $spoiled, $transactionType, $specificStockEntryId = 'default', $recipeId = null, $locationId = null, &$transactionId = null, $allowSubproductSubstitution = false, $consumeExactAmount = false)
{
if (!$this->ProductExists($productId))
{
@@ -230,6 +230,10 @@ class StockService extends BaseService
if ($productDetails->product->enable_tare_weight_handling == 1)
{
+ if($consumeExactAmount)
+ {
+ $amount = floatval($productDetails->stock_amount) + floatval($productDetails->product->tare_weight) - $amount;
+ }
if ($amount < floatval($productDetails->product->tare_weight))
{
throw new \Exception('The amount cannot be lower than the defined tare weight');
@@ -249,6 +253,11 @@ class StockService extends BaseService
$potentialStockEntries = $this->GetProductStockEntriesForLocation($productId, $locationId, false, $allowSubproductSubstitution);
}
+ if ($specificStockEntryId !== 'default')
+ {
+ $potentialStockEntries = FindAllObjectsInArrayByPropertyValue($potentialStockEntries, 'stock_id', $specificStockEntryId);
+ }
+
$productStockAmount = SumArrayValue($potentialStockEntries, 'amount');
if ($amount > $productStockAmount)
@@ -256,11 +265,6 @@ class StockService extends BaseService
throw new \Exception('Amount to be consumed cannot be > current stock amount (if supplied, at the desired location)');
}
- if ($specificStockEntryId !== 'default')
- {
- $potentialStockEntries = FindAllObjectsInArrayByPropertyValue($potentialStockEntries, 'stock_id', $specificStockEntryId);
- }
-
if ($transactionId === null)
{
$transactionId = uniqid();
diff --git a/views/components/locationpicker.blade.php b/views/components/locationpicker.blade.php
index 36e5b678..bbe54cd7 100644
--- a/views/components/locationpicker.blade.php
+++ b/views/components/locationpicker.blade.php
@@ -6,9 +6,10 @@
@php if(empty($prefillById)) { $prefillById = ''; } @endphp
@php if(!isset($isRequired)) { $isRequired = true; } @endphp
@php if(empty($hint)) { $hint = ''; } @endphp
+@php if(empty($nextInputSelector)) { $nextInputSelector = ''; } @endphp