diff --git a/changelog/55_UNRELEASED_2019-xx-xx.md b/changelog/55_UNRELEASED_2019-xx-xx.md
index 7c759c30..28574d5f 100644
--- a/changelog/55_UNRELEASED_2019-xx-xx.md
+++ b/changelog/55_UNRELEASED_2019-xx-xx.md
@@ -4,6 +4,14 @@
- From there you can also edit the stock entries
- A huge THANK YOU goes to @kriddles for the work on this feature
+### New feature: Scan mode
+- New switch-button on the purchase and consume page
+- When enabled
+ - The amount will always be filled with `1` after changing/scanning a product
+ - If all fields could be automatically populated (means for purchase the product has a default best before date set), the transaction is automatically submitted
+ - If not, a warning is displayed and you can fill in the missing information
+ - Audio feedback is provided after scanning and on success/error of the transaction
+
### New feature: Self produced products
- To a recipe a product can be attached
- This products needs a "Default best before date"
diff --git a/config-dist.php b/config-dist.php
index 4884ff7c..35f6d83f 100644
--- a/config-dist.php
+++ b/config-dist.php
@@ -91,6 +91,8 @@ DefaultUserSetting('product_presets_qu_id', -1); // Default quantity unit id for
DefaultUserSetting('stock_expring_soon_days', 5);
DefaultUserSetting('stock_default_purchase_amount', 0);
DefaultUserSetting('stock_default_consume_amount', 1);
+DefaultUserSetting('scan_mode_consume_enabled', false);
+DefaultUserSetting('scan_mode_purchase_enabled', false);
# Chores settings
DefaultUserSetting('chores_due_soon_days', 5);
diff --git a/localization/strings.pot b/localization/strings.pot
index 8a3768e6..96ed6add 100644
--- a/localization/strings.pot
+++ b/localization/strings.pot
@@ -1669,3 +1669,15 @@ msgstr ""
msgid "Meal plan product"
msgstr ""
+
+msgid "Scan mode"
+msgstr ""
+
+msgid "on"
+msgstr ""
+
+msgid "off"
+msgstr ""
+
+msgid "Scan mode is on but not all required fields could be populated automatically"
+msgstr ""
diff --git a/package.json b/package.json
index eaadff35..7709a79b 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"bootbox": "^5.3.2",
"bootstrap": "^4.3.1",
"bootstrap-select": "^1.13.10",
+ "bootstrap-switch-button": "https://github.com/walidbagh/bootstrap-switch-button#Fix-module-export",
"chart.js": "^2.8.0",
"datatables.net": "^1.10.19",
"datatables.net-bs4": "^1.10.19",
diff --git a/public/js/grocy.js b/public/js/grocy.js
index cb080592..08fbeab9 100644
--- a/public/js/grocy.js
+++ b/public/js/grocy.js
@@ -425,7 +425,7 @@ $(document).on("click", "select", function()
});
// Auto saving user setting controls
-$(".user-setting-control").on("change", function()
+$(document).on("change", ".user-setting-control", function()
{
var element = $(this);
var settingKey = element.attr("data-setting-key");
diff --git a/public/js/grocy_uisound.js b/public/js/grocy_uisound.js
new file mode 100644
index 00000000..b7150084
--- /dev/null
+++ b/public/js/grocy_uisound.js
@@ -0,0 +1,26 @@
+Grocy.UISound = { };
+
+Grocy.UISound.Play = function(url)
+{
+ new Audio(url).play();
+}
+
+Grocy.UISound.AskForPermission = function()
+{
+ Grocy.UISound.Play(U("/uisounds/silence.mp3"));
+}
+
+Grocy.UISound.Success = function()
+{
+ Grocy.UISound.Play(U("/uisounds/success.mp3"));
+}
+
+Grocy.UISound.Error = function()
+{
+ Grocy.UISound.Play(U("/uisounds/error.mp3"));
+}
+
+Grocy.UISound.BarcodeScannerBeep = function()
+{
+ Grocy.UISound.Play(U("/uisounds/barcodescannerbeep.mp3"));
+}
diff --git a/public/uisounds/barcodescannerbeep.mp3 b/public/uisounds/barcodescannerbeep.mp3
new file mode 100644
index 00000000..e488a47e
Binary files /dev/null and b/public/uisounds/barcodescannerbeep.mp3 differ
diff --git a/public/uisounds/error.mp3 b/public/uisounds/error.mp3
new file mode 100644
index 00000000..f68bc892
Binary files /dev/null and b/public/uisounds/error.mp3 differ
diff --git a/public/uisounds/silence.mp3 b/public/uisounds/silence.mp3
new file mode 100644
index 00000000..057d3ffc
Binary files /dev/null and b/public/uisounds/silence.mp3 differ
diff --git a/public/uisounds/success.mp3 b/public/uisounds/success.mp3
new file mode 100644
index 00000000..6c752d65
Binary files /dev/null and b/public/uisounds/success.mp3 differ
diff --git a/public/viewjs/consume.js b/public/viewjs/consume.js
index 9faf4cca..601b2f00 100644
--- a/public/viewjs/consume.js
+++ b/public/viewjs/consume.js
@@ -38,6 +38,11 @@
Grocy.Api.Post(apiUrl, jsonData,
function(result)
{
+ if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled))
+ {
+ Grocy.UISound.Success();
+ }
+
bookingResponse = result;
var addBarcode = GetUriParam('addbarcodetoselection');
@@ -246,6 +251,11 @@ $("#location_id").on('change', function(e)
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
+ if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled))
+ {
+ Grocy.UISound.BarcodeScannerBeep();
+ }
+
$("#specific_stock_entry").find("option").remove().end().append("");
if ($("#use_specific_stock_entry").is(":checked"))
{
@@ -265,13 +275,14 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$('#amount_qu_unit').text(productDetails.quantity_unit_stock.name);
$("#location_id").find("option").remove().end().append("");
- Grocy.Api.Get("stock/products/" + productId + '/locations',
+ Grocy.Api.Get("stock/products/" + productId + '/locations',
function(stockLocations)
{
var setDefault = 0;
stockLocations.forEach(stockLocation =>
{
- if (productDetails.location.id == stockLocation.location_id) {
+ if (productDetails.location.id == stockLocation.location_id)
+ {
$("#location_id").append($("