From db45b6f9dc8c4462922ad73f56fc313ed3a418b6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 1 Nov 2022 22:18:10 +1100 Subject: [PATCH] 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 --- InvenTree/common/models.py | 11 ++ InvenTree/part/templates/part/category.html | 17 ++- InvenTree/stock/templates/stock/location.html | 56 ++++++--- .../templates/InvenTree/settings/barcode.html | 1 + InvenTree/templates/js/translated/barcode.js | 110 +++++++++++++++--- 5 files changed, 161 insertions(+), 34 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 3d7ddaab37..51b786558e 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -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'), diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html index 6d55397007..530e68d47f 100644 --- a/InvenTree/part/templates/part/category.html +++ b/InvenTree/part/templates/part/category.html @@ -63,11 +63,6 @@ {% endif %} {% endif %} -{% if roles.part_category.add %} - -{% endif %} {% endblock %} {% block details_left %} @@ -225,7 +220,17 @@
-

{% trans "Subcategories" %}

+
+

{% trans "Subcategories" %}

+ {% include "spacer.html" %} +
+ {% if roles.part_category.add %} + + {% endif %} +
+
diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index f56913b761..4e04906a18 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -53,12 +53,21 @@ {% else %}
  • {% trans "Link Barcode" %}
  • {% endif %} - {% if labels_enabled %} -
  • {% trans "Print Label" %}
  • - {% endif %} -
  • {% trans "Check-in Items" %}
  • +
  • {% trans "Scan In Stock Items" %}
  • +
  • {% trans "Scan In Container" %}
  • + +{% if labels_enabled %} + +{% endif %} {% if user_owns_location %} {% if roles.stock.change %} @@ -96,11 +105,6 @@ {% endif %} {% endif %} {% endif %} -{% if user_owns_location and roles.stock_location.add %} - -{% endif %} {% endblock %} {% block details_left %} @@ -203,7 +207,17 @@
    -

    {% trans "Sublocations" %}

    +
    +

    {% trans "Sublocations" %}

    + {% include "spacer.html" %} +
    + {% if user_owns_location and roles.stock_location.add %} + + {% endif %} +
    +
    @@ -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 }}, diff --git a/InvenTree/templates/InvenTree/settings/barcode.html b/InvenTree/templates/InvenTree/settings/barcode.html index bdfcffbd5d..6a9b74e3cf 100644 --- a/InvenTree/templates/InvenTree/settings/barcode.html +++ b/InvenTree/templates/InvenTree/settings/barcode.html @@ -13,6 +13,7 @@ {% 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" %}
    diff --git a/InvenTree/templates/js/translated/barcode.js b/InvenTree/templates/js/translated/barcode.js index 41117a5a3b..40c0420ca7 100644 --- a/InvenTree/templates/js/translated/barcode.js +++ b/InvenTree/templates/js/translated/barcode.js @@ -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='') { -
    @@ -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 += `
    {% trans "Scan barcode data below" %}
    `; + content += `
    ${details}
    `; content += `
    `; content += `
    `; @@ -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 */