2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 12:06:44 +00:00

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, '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': { 'BARCODE_WEBCAM_SUPPORT': {
'name': _('Barcode Webcam Support'), 'name': _('Barcode Webcam Support'),
'description': _('Allow barcode scanning via webcam in browser'), 'description': _('Allow barcode scanning via webcam in browser'),

View File

@ -63,11 +63,6 @@
</div> </div>
{% endif %} {% endif %}
{% 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 %} {% endblock %}
{% block details_left %} {% block details_left %}
@ -225,7 +220,17 @@
<div class='panel panel-hidden' id='panel-subcategories'> <div class='panel panel-hidden' id='panel-subcategories'>
<div class='panel-heading'> <div class='panel-heading'>
<div class='d-flex flex-wrap'>
<h4>{% trans "Subcategories" %}</h4> <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>
<div class='panel-content'> <div class='panel-content'>
<div id='subcategory-button-toolbar'> <div id='subcategory-button-toolbar'>

View File

@ -53,12 +53,21 @@
{% else %} {% else %}
<li><a class='dropdown-item' href='#' id='barcode-link'><span class='fas fa-link'></span> {% trans "Link Barcode" %}</a></li> <li><a class='dropdown-item' href='#' id='barcode-link'><span class='fas fa-link'></span> {% trans "Link Barcode" %}</a></li>
{% endif %} {% endif %}
{% if labels_enabled %} <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='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</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>
{% endif %}
<li><a class='dropdown-item' href='#' id='barcode-check-in'><span class='fas fa-arrow-right'></span> {% trans "Check-in Items" %}</a></li>
</ul> </ul>
</div> </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 --> <!-- Check permissions and owner -->
{% if user_owns_location %} {% if user_owns_location %}
{% if roles.stock.change %} {% if roles.stock.change %}
@ -96,11 +105,6 @@
{% endif %} {% endif %}
{% endif %} {% 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 %} {% endblock %}
{% block details_left %} {% block details_left %}
@ -203,7 +207,17 @@
<div class='panel panel-hidden' id='panel-sublocations'> <div class='panel panel-hidden' id='panel-sublocations'>
<div class='panel-heading'> <div class='panel-heading'>
<div class='d-flex flex-wrap'>
<h4>{% trans "Sublocations" %}</h4> <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>
<div class='panel-content'> <div class='panel-content'>
<div id='sublocation-button-toolbar'> <div id='sublocation-button-toolbar'>
@ -284,13 +298,29 @@
{% endif %} {% endif %}
{% if location %} {% if location %}
$("#barcode-check-in").click(function() { $("#barcode-scan-in-items").click(function() {
barcodeCheckIn({{ location.id }}); 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 %} {% endif %}
$('#location-create').click(function () { $('#location-create').click(function () {
createStockLocation({ createStockLocation({
{% if location %} {% if location %}
parent: {{ location.pk }}, parent: {{ location.pk }},

View File

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

View File

@ -14,7 +14,8 @@
*/ */
/* exported /* exported
barcodeCheckIn, barcodeCheckInStockItems,
barcodeCheckInStockLocations,
barcodeScanDialog, barcodeScanDialog,
linkBarcodeDialog, linkBarcodeDialog,
scanItemsIntoLocation, scanItemsIntoLocation,
@ -22,12 +23,14 @@
onBarcodeScanClicked, onBarcodeScanClicked,
*/ */
function makeBarcodeInput(placeholderText='', hintText='') { var barcodeInputTimer = null;
/*
* Generate HTML for a barcode input
*/
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" %}'; hintText = hintText || '{% trans "Enter barcode data" %}';
@ -43,7 +46,7 @@ function makeBarcodeInput(placeholderText='', hintText='') {
<span class='fas fa-qrcode'></span> <span class='fas fa-qrcode'></span>
</span> </span>
<input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'> <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> <span class='fas fa-camera'></span>
</button> </button>
</div> </div>
@ -92,6 +95,9 @@ function onBarcodeScanCompleted(result, options) {
postBarcodeData(result.data, options); postBarcodeData(result.data, options);
} }
/*
* Construct a generic "notes" field for barcode scanning operations
*/
function makeNotesField(options={}) { function makeNotesField(options={}) {
var tooltip = options.tooltip || '{% trans "Enter optional notes for stock transfer" %}'; 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) { function showInvalidResponseError(modal, response, status) {
showBarcodeMessage( showBarcodeMessage(
modal, modal,
@ -207,6 +216,9 @@ function showInvalidResponseError(modal, response, status) {
} }
/*
* Enable (or disable) the barcode scanning input
*/
function enableBarcodeInput(modal, enabled=true) { function enableBarcodeInput(modal, enabled=true) {
var barcode = $(modal + ' #barcode'); var barcode = $(modal + ' #barcode');
@ -218,6 +230,10 @@ function enableBarcodeInput(modal, enabled=true) {
barcode.focus(); barcode.focus();
} }
/*
* Extract scanned data from the barcode input
*/
function getBarcodeData(modal) { function getBarcodeData(modal) {
modal = modal || '#modal-form'; modal = modal || '#modal-form';
@ -233,10 +249,10 @@ function getBarcodeData(modal) {
} }
function barcodeDialog(title, options={}) {
/* /*
* Handle a barcode display dialog. * Handle a barcode display dialog.
*/ */
function barcodeDialog(title, options={}) {
var modal = '#modal-form'; var modal = '#modal-form';
@ -244,7 +260,6 @@ function barcodeDialog(title, options={}) {
var barcode = getBarcodeData(modal); var barcode = getBarcodeData(modal);
if (barcode && barcode.length > 0) { if (barcode && barcode.length > 0) {
postBarcodeData(barcode, options); postBarcodeData(barcode, options);
} }
} }
@ -264,7 +279,15 @@ function barcodeDialog(title, options={}) {
event.preventDefault(); event.preventDefault();
if (event.which == 10 || event.which == 13) { if (event.which == 10 || event.which == 13) {
clearTimeout(barcodeInputTimer);
sendBarcode(); 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); modalShowSubmitButton(modal, false);
} }
var details = options.details || '{% trans "Scan barcode data" %}';
var content = ''; 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 += `<div id='barcode-error-message'></div>`;
content += `<form class='js-modal-form' method='post'>`; 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. * 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'; var modal = '#modal-form';
@ -486,6 +511,7 @@ function barcodeCheckIn(location_id, options={}) {
$(modal + ' #barcode').focus(); $(modal + ' #barcode').focus();
// Callback to remove the scanned item from the table
$(modal + ' .button-item-remove').unbind('click').on('mouseup', function() { $(modal + ' .button-item-remove').unbind('click').on('mouseup', function() {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
@ -514,8 +540,9 @@ function barcodeCheckIn(location_id, options={}) {
var extra = makeNotesField(); var extra = makeNotesField();
barcodeDialog( 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, headerContent: table,
preShow: function() { preShow: function() {
modalSetSubmitText(modal, '{% trans "Check In" %}'); modalSetSubmitText(modal, '{% trans "Check In" %}');
@ -609,7 +636,7 @@ function barcodeCheckIn(location_id, options={}) {
); );
} else { } else {
// Barcode does not match a stock item // 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 * Display dialog to check a single stock item into a stock location
*/ */