Implemented "Scan mode"

This commit is contained in:
Bernd Bestel 2020-01-26 15:35:01 +01:00
parent 7a048136c6
commit c7bcb9984a
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
16 changed files with 190 additions and 22 deletions

View File

@ -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"

View File

@ -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);

View File

@ -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 ""

View File

@ -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",

View File

@ -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");

View File

@ -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"));
}

Binary file not shown.

BIN
public/uisounds/error.mp3 Normal file

Binary file not shown.

BIN
public/uisounds/silence.mp3 Normal file

Binary file not shown.

BIN
public/uisounds/success.mp3 Normal file

Binary file not shown.

View File

@ -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("<option></option>");
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("<option></option>");
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($("<option>", {
value: stockLocation.location_id,
text: stockLocation.location_name + " (" + __t("Default location") + ")"
@ -294,6 +305,21 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$("#location_id").trigger('change');
}
});
if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled))
{
$("#amount").val(1);
Grocy.FrontendHelpers.ValidateForm("consume-form");
if (document.getElementById("consume-form").checkValidity() === true)
{
$('#save-consume-button').click();
}
else
{
toastr.warning(__t("Scan mode is on but not all required fields could be populated automatically"));
Grocy.UISound.Error();
}
}
},
function(xhr)
{
@ -495,3 +521,18 @@ if (GetUriParam("embedded") !== undefined)
$("#use_specific_stock_entry").trigger('change');
}
}
// Default input field
Grocy.Components.ProductPicker.GetInputElement().focus();
// Can only be set via JS however...
$("#scan-mode").addClass("user-setting-control");
$("#scan-mode").attr("data-setting-key", "scan_mode_consume_enabled");
$(document).on("change", "#scan-mode", function(e)
{
if ($(this).prop("checked"))
{
Grocy.UISound.AskForPermission();
}
});

View File

@ -42,6 +42,11 @@
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData,
function(result)
{
if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled))
{
Grocy.UISound.Success();
}
var addBarcode = GetUriParam('addbarcodetoselection');
if (addBarcode !== undefined)
{
@ -122,6 +127,11 @@ if (Grocy.Components.ProductPicker !== undefined)
{
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled))
{
Grocy.UISound.BarcodeScannerBeep();
}
var productId = $(e.target).val();
if (productId)
@ -129,7 +139,7 @@ if (Grocy.Components.ProductPicker !== undefined)
Grocy.Components.ProductCard.Refresh(productId);
Grocy.Api.Get('stock/products/' + productId,
function (productDetails)
function(productDetails)
{
$('#price').val(productDetails.last_price);
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
@ -205,6 +215,21 @@ if (Grocy.Components.ProductPicker !== undefined)
$('#amount').focus();
}
}
if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled))
{
$("#amount").val(1);
Grocy.FrontendHelpers.ValidateForm("purchase-form");
if (document.getElementById("purchase-form").checkValidity() === true)
{
$('#save-purchase-button').click();
}
else
{
toastr.warning(__t("Scan mode is on but not all required fields could be populated automatically"));
Grocy.UISound.Error();
}
}
},
function(xhr)
{
@ -336,3 +361,15 @@ function UndoStockTransaction(transactionId)
}
);
};
// Can only be set via JS however...
$("#scan-mode").addClass("user-setting-control");
$("#scan-mode").attr("data-setting-key", "scan_mode_purchase_enabled");
$(document).on("change", "#scan-mode", function(e)
{
if ($(this).prop("checked"))
{
Grocy.UISound.AskForPermission();
}
});

View File

@ -440,3 +440,6 @@ if (GetUriParam("embedded") !== undefined)
$("#use_specific_stock_entry").trigger('change');
}
}
// Default input field
Grocy.Components.ProductPicker.GetInputElement().focus();

View File

@ -4,10 +4,22 @@
@section('activeNav', 'consume')
@section('viewJsName', 'consume')
@push('pageStyles')
<link href="{{ $U('/node_modules/bootstrap-switch-button/css/bootstrap-switch-button.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@push('pageScripts')
<script src="{{ $U('/node_modules/bootstrap-switch-button/js/bootstrap-switch-button.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/js/grocy_uisound.js?v=', true) }}{{ $version }}"></script>
@endpush
@section('content')
<div class="row">
<div class="col-xs-12 col-md-6 col-xl-4 pb-3">
<h1>@yield('title')</h1>
<h1>
@yield('title')
<input @if(boolval($userSettings['scan_mode_consume_enabled'])) checked @endif id="scan-mode" type="checkbox" data-setting-key="scan_mode_consume_enabled" data-toggle="switchbutton" data-onlabel="{{ $__t('Scan mode') }} {{ $__t('on') }}" data-offlabel="{{ $__t('Scan mode') }} {{ $__t('off') }}" data-onstyle="success" data-offstyle="primary" data-style="ml-2" data-width="160">
</h1>
<form id="consume-form" novalidate>
@ -28,23 +40,23 @@
))
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
@php /*@include('components.locationpicker', array(
'id' => 'location_id',
'locations' => $locations,
'isRequired' => true,
'label' => 'Location'
))*/ @endphp
@php /*@include('components.locationpicker', array(
'id' => 'location_id',
'locations' => $locations,
'isRequired' => true,
'label' => 'Location'
))*/ @endphp
<div class="form-group">
<label for="location_id">{{ $__t('Location') }}</label>
<select required class="form-control location-combobox" id="location_id" name="location_id">
<option></option>
@foreach($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
<div class="form-group">
<label for="location_id">{{ $__t('Location') }}</label>
<select required class="form-control location-combobox" id="location_id" name="location_id">
<option></option>
@foreach($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
@else
<input type="hidden" name="location_id" id="location_id" value="1">
@endif

View File

@ -4,10 +4,22 @@
@section('activeNav', 'purchase')
@section('viewJsName', 'purchase')
@push('pageStyles')
<link href="{{ $U('/node_modules/bootstrap-switch-button/css/bootstrap-switch-button.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@push('pageScripts')
<script src="{{ $U('/node_modules/bootstrap-switch-button/js/bootstrap-switch-button.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/js/grocy_uisound.js?v=', true) }}{{ $version }}"></script>
@endpush
@section('content')
<div class="row">
<div class="col-xs-12 col-md-6 col-xl-4 pb-3">
<h1>@yield('title')</h1>
<h1>
@yield('title')
<input @if(boolval($userSettings['scan_mode_purchase_enabled'])) checked @endif id="scan-mode" type="checkbox" data-setting-key="scan_mode_purchase_enabled" data-toggle="switchbutton" data-onlabel="{{ $__t('Scan mode') }} {{ $__t('on') }}" data-offlabel="{{ $__t('Scan mode') }} {{ $__t('off') }}" data-onstyle="success" data-offstyle="primary" data-style="ml-2" data-width="160">
</h1>
<form id="purchase-form" novalidate>

View File

@ -17,6 +17,11 @@
dependencies:
jquery "1"
add@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/add/-/add-2.0.6.tgz#248f0a9f6e5a528ef2295dbeec30532130ae2235"
integrity sha1-JI8Kn25aUo7yKV2+7DBTITCuIjU=
ajv@^6.5.5:
version "6.10.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
@ -75,6 +80,10 @@ bootstrap-select@^1.13.10:
resolved "https://registry.yarnpkg.com/bootstrap-select/-/bootstrap-select-1.13.11.tgz#d364ccc67cd6c1f9158689087f1d2d029df82c29"
integrity sha512-WPpx2DYL9jVilNoqy4Pjcfa/Q0LOq8V0+xws/pmnRDn/deS7OYjo1njvD1Cv0s9/1ZUXt77UypxuloimEnkYsA==
"bootstrap-switch-button@https://github.com/walidbagh/bootstrap-switch-button#Fix-module-export":
version "1.0.0"
resolved "https://github.com/walidbagh/bootstrap-switch-button#551ccd361cc9e291cd04c2278357b1db76318f1e"
bootstrap@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.0.0.tgz#ceb03842c145fcc1b9b4e15da2a05656ba68469a"
@ -779,3 +788,8 @@ verror@1.10.0:
assert-plus "^1.0.0"
core-util-is "1.0.2"
extsprintf "^1.2.0"
yarn@^1.21.1:
version "1.21.1"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.21.1.tgz#1d5da01a9a03492dc4a5957befc1fd12da83d89c"
integrity sha512-dQgmJv676X/NQczpbiDtc2hsE/pppGDJAzwlRiADMTvFzYbdxPj2WO4PcNyriSt2c4jsCMpt8UFRKHUozt21GQ==