Location barcode actions (#3887)

* Reorder action buttons for stock location

* Tweak position of "New Location" button

* Tweak loaction of "New Category" button for part category page

* Working on skeleton for new barcode dialog

* Scan location into location

* Add configurable input delay for processing barcode scan data
This commit is contained in:
Oliver 2022-11-01 22:18:10 +11:00 committed by GitHub
parent 9beefd09f7
commit db45b6f9dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 161 additions and 34 deletions

View File

@ -933,6 +933,17 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'validator': bool,
},
'BARCODE_INPUT_DELAY': {
'name': _('Barcode Input Delay'),
'description': _('Barcode input processing delay time'),
'default': 50,
'validator': [
int,
MinValueValidator(1),
],
'units': 'ms',
},
'BARCODE_WEBCAM_SUPPORT': {
'name': _('Barcode Webcam Support'),
'description': _('Allow barcode scanning via webcam in browser'),

View File

@ -63,11 +63,6 @@
</div>
{% endif %}
{% endif %}
{% if roles.part_category.add %}
<button class='btn btn-success' id='cat-create' title='{% trans "Create new part category" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Category" %}
</button>
{% endif %}
{% endblock %}
{% block details_left %}
@ -225,7 +220,17 @@
<div class='panel panel-hidden' id='panel-subcategories'>
<div class='panel-heading'>
<h4>{% trans "Subcategories" %}</h4>
<div class='d-flex flex-wrap'>
<h4>{% trans "Subcategories" %}</h4>
{% include "spacer.html" %}
<div class='btn-group' role='group'>
{% if roles.part_category.add %}
<button class='btn btn-success' id='cat-create' title='{% trans "Create new part category" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Category" %}
</button>
{% endif %}
</div>
</div>
</div>
<div class='panel-content'>
<div id='subcategory-button-toolbar'>

View File

@ -53,12 +53,21 @@
{% else %}
<li><a class='dropdown-item' href='#' id='barcode-link'><span class='fas fa-link'></span> {% trans "Link Barcode" %}</a></li>
{% endif %}
{% if labels_enabled %}
<li><a class='dropdown-item' href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li>
{% endif %}
<li><a class='dropdown-item' href='#' id='barcode-check-in'><span class='fas fa-arrow-right'></span> {% trans "Check-in Items" %}</a></li>
<li><a class='dropdown-item' href='#' id='barcode-scan-in-items' title='{% trans "Scan stock items into this location" %}'><span class='fas fa-boxes'></span> {% trans "Scan In Stock Items" %}</a></li>
<li><a class='dropdown-item' href='#' id='barcode-scan-in-containers' title='{% trans "Scan stock container into this location" %}'><span class='fas fa-sitemap'></span> {% trans "Scan In Container" %}</a></li>
</ul>
</div>
<!-- Printing action -->
{% if labels_enabled %}
<div class='btn-group' role='group'>
<button id='printing-options' title='{% trans "Printing actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'>
<span class='fas fa-print'></span> <span class='caret'></span>
</button>
<ul class='dropdown-menu'>
<li><a class='dropdown-item' href='#' id='print-label'><span class='fas fa-print'></span> {% trans "Print Label" %}</a>
</ul>
</div>
{% endif %}
<!-- Check permissions and owner -->
{% if user_owns_location %}
{% if roles.stock.change %}
@ -96,11 +105,6 @@
{% endif %}
{% endif %}
{% endif %}
{% if user_owns_location and roles.stock_location.add %}
<button class='btn btn-success' id='location-create' type='button' title='{% trans "Create new stock location" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Location" %}
</button>
{% endif %}
{% endblock %}
{% block details_left %}
@ -203,7 +207,17 @@
<div class='panel panel-hidden' id='panel-sublocations'>
<div class='panel-heading'>
<h4>{% trans "Sublocations" %}</h4>
<div class='d-flex flex-wrap'>
<h4>{% trans "Sublocations" %}</h4>
{% include "spacer.html" %}
<div class='btn-group' role='group'>
{% if user_owns_location and roles.stock_location.add %}
<button class='btn btn-success' id='location-create' type='button' title='{% trans "Create new stock location" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Location" %}
</button>
{% endif %}
</div>
</div>
</div>
<div class='panel-content'>
<div id='sublocation-button-toolbar'>
@ -284,13 +298,29 @@
{% endif %}
{% if location %}
$("#barcode-check-in").click(function() {
barcodeCheckIn({{ location.id }});
$("#barcode-scan-in-items").click(function() {
barcodeCheckInStockItems({{ location.id }});
});
$('#barcode-scan-in-containers').click(function() {
barcodeCheckInStockLocations({{ location.id }},
{
onSuccess: function() {
showMessage(
'{% trans "Scanned stock container into this location" %}',
{
style: 'success',
}
);
$('#sublocation-table').bootstrapTable('refresh');
}
}
);
});
{% endif %}
$('#location-create').click(function () {
createStockLocation({
{% if location %}
parent: {{ location.pk }},

View File

@ -13,6 +13,7 @@
<table class='table table-striped table-condensed'>
<tbody>
{% include "InvenTree/settings/setting.html" with key="BARCODE_ENABLE" icon="fa-qrcode" %}
{% include "InvenTree/settings/setting.html" with key="BARCODE_INPUT_DELAY" icon="fa-hourglass-half" %}
{% include "InvenTree/settings/setting.html" with key="BARCODE_WEBCAM_SUPPORT" icon="fa-video" %}
</tbody>
</table>

View File

@ -14,7 +14,8 @@
*/
/* exported
barcodeCheckIn,
barcodeCheckInStockItems,
barcodeCheckInStockLocations,
barcodeScanDialog,
linkBarcodeDialog,
scanItemsIntoLocation,
@ -22,12 +23,14 @@
onBarcodeScanClicked,
*/
function makeBarcodeInput(placeholderText='', hintText='') {
/*
* Generate HTML for a barcode input
*/
var barcodeInputTimer = null;
placeholderText = placeholderText || '{% trans "Scan barcode data here using wedge scanner" %}';
/*
* Generate HTML for a barcode scan input
*/
function makeBarcodeInput(placeholderText='', hintText='') {
placeholderText = placeholderText || '{% trans "Scan barcode data here using barcode scanner" %}';
hintText = hintText || '{% trans "Enter barcode data" %}';
@ -43,7 +46,7 @@ function makeBarcodeInput(placeholderText='', hintText='') {
<span class='fas fa-qrcode'></span>
</span>
<input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'>
<button id='barcode_scan_btn' type='button' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'>
<button title='{% trans "Scan barcode using connected webcam" %}' id='barcode_scan_btn' type='button' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'>
<span class='fas fa-camera'></span>
</button>
</div>
@ -92,6 +95,9 @@ function onBarcodeScanCompleted(result, options) {
postBarcodeData(result.data, options);
}
/*
* Construct a generic "notes" field for barcode scanning operations
*/
function makeNotesField(options={}) {
var tooltip = options.tooltip || '{% trans "Enter optional notes for stock transfer" %}';
@ -199,6 +205,9 @@ function showBarcodeMessage(modal, message, style='danger') {
}
/*
* Display an error message when the server indicates an error
*/
function showInvalidResponseError(modal, response, status) {
showBarcodeMessage(
modal,
@ -207,6 +216,9 @@ function showInvalidResponseError(modal, response, status) {
}
/*
* Enable (or disable) the barcode scanning input
*/
function enableBarcodeInput(modal, enabled=true) {
var barcode = $(modal + ' #barcode');
@ -218,6 +230,10 @@ function enableBarcodeInput(modal, enabled=true) {
barcode.focus();
}
/*
* Extract scanned data from the barcode input
*/
function getBarcodeData(modal) {
modal = modal || '#modal-form';
@ -233,10 +249,10 @@ function getBarcodeData(modal) {
}
/*
* Handle a barcode display dialog.
*/
function barcodeDialog(title, options={}) {
/*
* Handle a barcode display dialog.
*/
var modal = '#modal-form';
@ -244,7 +260,6 @@ function barcodeDialog(title, options={}) {
var barcode = getBarcodeData(modal);
if (barcode && barcode.length > 0) {
postBarcodeData(barcode, options);
}
}
@ -264,7 +279,15 @@ function barcodeDialog(title, options={}) {
event.preventDefault();
if (event.which == 10 || event.which == 13) {
clearTimeout(barcodeInputTimer);
sendBarcode();
} else {
// Start a timer to automatically send barcode after input is complete
clearTimeout(barcodeInputTimer);
barcodeInputTimer = setTimeout(function() {
sendBarcode();
}, global_settings.BARCODE_INPUT_DELAY);
}
});
@ -305,9 +328,11 @@ function barcodeDialog(title, options={}) {
modalShowSubmitButton(modal, false);
}
var details = options.details || '{% trans "Scan barcode data" %}';
var content = '';
content += `<div class='alert alert-info alert-block'>{% trans "Scan barcode data below" %}</div>`;
content += `<div class='alert alert-info alert-block'>${details}</div>`;
content += `<div id='barcode-error-message'></div>`;
content += `<form class='js-modal-form' method='post'>`;
@ -431,7 +456,7 @@ function unlinkBarcode(data, options={}) {
/*
* Display dialog to check multiple stock items in to a stock location.
*/
function barcodeCheckIn(location_id, options={}) {
function barcodeCheckInStockItems(location_id, options={}) {
var modal = '#modal-form';
@ -486,6 +511,7 @@ function barcodeCheckIn(location_id, options={}) {
$(modal + ' #barcode').focus();
// Callback to remove the scanned item from the table
$(modal + ' .button-item-remove').unbind('click').on('mouseup', function() {
var pk = $(this).attr('pk');
@ -514,8 +540,9 @@ function barcodeCheckIn(location_id, options={}) {
var extra = makeNotesField();
barcodeDialog(
'{% trans "Check Stock Items into Location" %}',
'{% trans "Scan Stock Items Into Location" %}',
{
details: '{% trans "Scan stock item barcode to check in to this location" %}',
headerContent: table,
preShow: function() {
modalSetSubmitText(modal, '{% trans "Check In" %}');
@ -609,7 +636,7 @@ function barcodeCheckIn(location_id, options={}) {
);
} else {
// Barcode does not match a stock item
showBarcodeMessage(modal, '{% trans "Barcode does not match Stock Item" %}', 'warning');
showBarcodeMessage(modal, '{% trans "Barcode does not match valid stock item" %}', 'warning');
}
},
}
@ -617,6 +644,59 @@ function barcodeCheckIn(location_id, options={}) {
}
/*
* Display dialog to scan stock locations into the current location
*/
function barcodeCheckInStockLocations(location_id, options={}) {
var modal = '#modal-form';
var header = '';
barcodeDialog(
'{% trans "Scan Stock Container Into Location" %}',
{
details: '{% trans "Scan stock container barcode to check in to this location" %}',
headerContent: header,
preShow: function() {
modalEnable(modal, false);
},
onShow: function() {
// TODO
},
onScan: function(response) {
if ('stocklocation' in response) {
var pk = response.stocklocation.pk;
var url = `/api/stock/location/${pk}/`;
// Move the scanned location into *this* location
inventreePut(
url,
{
parent: location_id,
},
{
method: 'PATCH',
success: function(response) {
$(modal).modal('hide');
handleFormSuccess(response, options);
},
error: function(xhr) {
$(modal).modal('hide');
showApiError(xhr, url);
},
}
);
} else {
// Barcode does not match a valid stock location
showBarcodeMessage(modal, '{% trans "Barcode does not match valid stock location" %}', 'warning');
}
}
}
);
}
/*
* Display dialog to check a single stock item into a stock location
*/