From ab4e6548dc97115d13c3c767cc87faf96356d3f9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 12 Jun 2020 16:29:08 +1000 Subject: [PATCH] Transfer stock items into a stock location using barcode scanning --- .../static/script/inventree/modals.js | 18 +- InvenTree/stock/templates/stock/location.html | 6 + InvenTree/templates/js/barcode.html | 331 +++++++++++++++--- 3 files changed, 297 insertions(+), 58 deletions(-) diff --git a/InvenTree/InvenTree/static/script/inventree/modals.js b/InvenTree/InvenTree/static/script/inventree/modals.js index 158cf67a77..5b76203927 100644 --- a/InvenTree/InvenTree/static/script/inventree/modals.js +++ b/InvenTree/InvenTree/static/script/inventree/modals.js @@ -166,14 +166,28 @@ function modalSetContent(modal, content='') { } +function modalSetSubmitText(modal, text) { + if (text) { + $(modal).find('#modal-form-submit').html(text); + } +} + + +function modalSetCloseText(modal, text) { + if (text) { + $(modal).find('#modal-form-close').html(text); + } +} + + function modalSetButtonText(modal, submit_text, close_text) { /* Set the button text for a modal form * * submit_text - text for the form submit button * close_text - text for the form dismiss button */ - $(modal).find("#modal-form-submit").html(submit_text); - $(modal).find("#modal-form-close").html(close_text); + modalSetSubmitText(modal, submit_text); + modalSetCloseText(modal, close_text); } diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index 8b5e1d6e28..20032e557c 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -120,6 +120,12 @@ inventreeDel('show-part-locs'); }); + {% if location %} + $("#barcode-check-in").click(function() { + barcodeCheckIn({{ location.id }}); + }); + {% endif %} + $("#stock-export").click(function() { launchModalForm("{% url 'stock-export-options' %}", { submit_text: "Export", diff --git a/InvenTree/templates/js/barcode.html b/InvenTree/templates/js/barcode.html index ced4ebbf7e..7f4273d7eb 100644 --- a/InvenTree/templates/js/barcode.html +++ b/InvenTree/templates/js/barcode.html @@ -1,25 +1,5 @@ {% load i18n %} -/* - * Pass barcode data to the server. - */ -function scanBarcode(barcode, options={}) { - - inventreePut( - '/api/barcode/', - { - 'barcode': barcode, - }, - { - method: 'POST', - success: function(response, status) { - console.log(response); - }, - } - ); -} - - function makeBarcodeInput(placeholderText='') { /* * Generate HTML for a barcode input @@ -28,8 +8,6 @@ function makeBarcodeInput(placeholderText='') { placeholderText = placeholderText || '{% trans "Scan barcode data here using wedge scanner" %}'; var html = ` -
-
@@ -42,14 +20,13 @@ function makeBarcodeInput(placeholderText='') {
{% trans "Enter barcode data" %}
-
`; return html; } -function showBarcodeError(modal, message, style='danger') { +function showBarcodeMessage(modal, message, style='danger') { var html = `
`; @@ -60,6 +37,12 @@ function showBarcodeError(modal, message, style='danger') { $(modal + ' #barcode-error-message').html(html); } + +function showInvalidResponseError(modal, response, status) { + showBarcodeMessage(modal, `{% trans "Invalid server response" %}
{% trans "Status" %}: '${status}'`); +} + + function clearBarcodeError(modal, message) { $(modal + ' #barcode-error-message').html(''); @@ -73,6 +56,8 @@ function enableBarcodeInput(modal, enabled=true) { barcode.prop('disabled', !enabled); modalEnable(modal, enabled); + + barcode.focus(); } function getBarcodeData(modal) { @@ -86,7 +71,7 @@ function getBarcodeData(modal) { el.val(''); el.focus(); - return barcode; + return barcode.trim(); } @@ -97,54 +82,87 @@ function barcodeDialog(title, options={}) { var modal = '#modal-form'; + function sendBarcode() { + var barcode = getBarcodeData(modal); + + if (barcode && barcode.length > 0) { + + if (options.onScan) { + options.onScan(barcode); + } + } + } + $(modal).on('shown.bs.modal', function() { $(modal + ' .modal-form-content').scrollTop(0); + var barcode = $(modal + ' #barcode'); + + // Handle 'enter' key on barcode + barcode.keyup(function(event) { + event.preventDefault(); + + if (event.which == 10 || event.which == 13) { + sendBarcode(); + } + }); + // Ensure the barcode field has focus - $(modal + ' #barcode').focus(); + barcode.focus(); var form = $(modal).find('.js-modal-form'); // Override form submission form.submit(function() { - - var barcode = getBarcodeData(modal); - - if (options.submit) { - options.submit(barcode); - } - return false; }); + // Callback for when the "submit" button is pressed on the modal modalSubmit(modal, function() { - - var barcode = getBarcodeData(modal); - - if (options.submit) { - options.submit(barcode); + if (options.onSubmit) { + options.onSubmit(); } }); + if (options.onShow) { + options.onShow(); + } + }); modalSetTitle(modal, title); - modalShowSubmitButton(modal, true); + + if (options.onSubmit) { + modalShowSubmitButton(modal, true); + } else { + modalShowSubmitButton(modal, false); + } var content = ''; - if (options.headerContent) { - content += options.headerContent; - } - content += `
{% trans "Scan barcode data below" %}
`; + + content += `
`; + content += `
`; + + // Optional content before barcode input + content += `
`; + content += options.headerContent || ''; + content += `
`; content += makeBarcodeInput(); - if (options.footerContent) { - content += options.footerContent; + if (options.extraFields) { + content += options.extraFields; } + content += `
`; + + // Optional content after barcode input + content += `'; + modalSetContent(modal, content); $(modal).modal({ @@ -152,6 +170,10 @@ function barcodeDialog(title, options={}) { keyboard: false, }); + if (options.preShow) { + options.preShow(); + } + $(modal).modal('show'); } @@ -167,7 +189,7 @@ function barcodeScanDialog() { barcodeDialog( "Scan Barcode", { - submit: function(barcode) { + onScan: function(barcode) { enableBarcodeInput(modal, false); inventreePut( '/api/barcode/', @@ -190,12 +212,12 @@ function barcodeScanDialog() { } } else if ('error' in response) { - showBarcodeError(modal, response.error, 'warning'); + showBarcodeMessage(modal, response.error, 'warning'); } else { - showBarcodeError(modal, "{% trans 'Unknown response from server' %}", 'warning'); + showBarcodeMessage(modal, "{% trans 'Unknown response from server' %}", 'warning'); } } else { - showBarcodeError(modal, `{% trans "Invalid server response" %}.
Status code: '${status}'`); + showInvalidResponseError(modal, response, status); } }, }, @@ -214,9 +236,9 @@ function linkBarcodeDialog(stockitem, options={}) { var modal = '#modal-form'; barcodeDialog( - "Link Barcode", + "{% trans 'Link Barcode to Stock Item' %}", { - submit: function(barcode) { + onScan: function(barcode) { enableBarcodeInput(modal, false); inventreePut( '/api/barcode/link/', @@ -228,8 +250,6 @@ function linkBarcodeDialog(stockitem, options={}) { method: 'POST', success: function(response, status) { - console.log(response); - enableBarcodeInput(modal, true); if (status == 'success') { @@ -238,13 +258,13 @@ function linkBarcodeDialog(stockitem, options={}) { $(modal).modal('hide'); location.reload(); } else if ('error' in response) { - showBarcodeError(modal, response.error, 'warning'); + showBarcodeMessage(modal, response.error, 'warning'); } else { - showBarcodeError(modal, "{% trans 'Unknown response from server' %}", warning); + showBarcodeMessage(modal, "{% trans 'Unknown response from server' %}", warning); } } else { - showBarcodeError(modal, `{% trans "Invalid server response" %}.
Status code: '${status}'`); + showInvalidResponseError(modal, response, status); } }, }, @@ -287,3 +307,202 @@ function unlinkBarcode(stockitem) { } ); } + + +/* + * Display dialog to check multiple stock items in to a stock location. + */ +function barcodeCheckIn(location_id, options={}) { + + var modal = '#modal-form'; + + // List of items we are going to checkin + var items = []; + + + function reloadTable() { + + modalEnable(modal, false); + + // Remove click listeners + $(modal + ' .button-item-remove').off('click'); + + var table = $(modal + ' #items-table-div'); + + var html = ` + + + + + + + + + + `; + + items.forEach(function(item) { + html += ` + + + + + + `; + }); + + html += ` + +
{% trans "Part" %}{% trans "Location" %}{% trans "Quantity" %}
${imageHoverIcon(item.part_detail.thumbnail)} ${item.part_detail.name}${item.location_detail.name}${item.quantity}${makeIconButton('fa-times-circle icon-red', 'button-item-remove', item.pk, '{% trans "Remove stock item" %}')}
`; + + table.html(html); + + modalEnable(modal, items.length > 0); + + $(modal + ' #barcode').focus(); + + + $(modal + ' .button-item-remove').on('click', function() { + var pk = $(this).attr('pk'); + + for (var ii = 0; ii < items.length; ii++) { + if (pk.toString() == items[ii].pk.toString()) { + //items.splice(ii, 1); + break; + } + } + + reloadTable(); + + }); + } + + var table = `
`; + + // Extra form fields + var extra = ` +
+ +
+
+ + + + +
+
{% trans "Enter optional notes for stock transfer" %}
+
+
`; + + barcodeDialog( + "{% trans "Check Stock Items into Location" %}", + { + headerContent: table, + preShow: function() { + modalSetSubmitText(modal, '{% trans "Check In" %}'); + modalEnable(modal, false); + reloadTable(); + }, + onShow: function() { + }, + extraFields: extra, + onSubmit: function() { + + + // Called when the 'check-in' button is pressed + + var data = {location: location_id}; + + // Extract 'notes' field + data.notes = $(modal + ' #notes').val(); + + var entries = []; + + items.forEach(function(item) { + entries.push({ + pk: item.pk, + quantity: item.quantity, + }); + }); + + data.items = entries; + + inventreePut( + '{% url 'api-stock-transfer' %}', + data, + { + method: 'POST', + success: function(response, status) { + // Hide the modal + $(modal).modal('hide'); + if (status == 'success' && 'success' in response) { + + showAlertOrCache('alert-success', response.success, true); + location.reload(); + } else { + showAlertOrCache('alert-success', 'Error transferring stock', false); + } + } + } + ); + }, + onScan: function(barcode) { + enableBarcodeInput(modal, false); + inventreePut( + '/api/barcode/', + { + barcode: barcode, + }, + { + method: 'POST', + error: function() { + enableBarcodeInput(modal, true); + showBarcodeMessage(modal, '{% trans "Server error" %}'); + }, + success: function(response, status) { + + enableBarcodeInput(modal, true); + + if (status == 'success') { + if ('stockitem' in response) { + stockitem = response.stockitem; + + var duplicate = false; + + items.forEach(function(item) { + if (item.pk == stockitem.pk) { + duplicate = true; + } + }); + + if (duplicate) { + showBarcodeMessage(modal, "{% trans "Stock Item already scanned" %}", "warning"); + } else { + + if (stockitem.location == location_id) { + showBarcodeMessage(modal, "{% trans "Stock Item already in this location" %}"); + return; + } + + // Add this stock item to the list + items.push(stockitem); + + showBarcodeMessage(modal, "{% trans "Added stock item" %}", "success"); + + reloadTable(); + } + + } else { + // Barcode does not match a stock item + showBarcodeMessage(modal, "{% trans "Barcode does not match Stock Item" %}", "warning"); + } + } else { + showInvalidResponseError(modal, response, status); + } + }, + }, + ); + }, + } + ); +}