Transfer stock items into a stock location using barcode scanning

This commit is contained in:
Oliver Walters 2020-06-12 16:29:08 +10:00
parent 351a55c9ea
commit ab4e6548dc
3 changed files with 297 additions and 58 deletions

View File

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

View File

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

View File

@ -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 = `
<div id='barcode-error-message'></div>
<form class='js-modal-form' method='post'>
<div class='form-group'>
<label class='control-label' for='barcode'>{% trans "Barcode" %}</label>
<div class='controls'>
@ -42,14 +20,13 @@ function makeBarcodeInput(placeholderText='') {
<div id='hint_barcode_data' class='help-block'>{% trans "Enter barcode data" %}</div>
</div>
</div>
</form>
`;
return html;
}
function showBarcodeError(modal, message, style='danger') {
function showBarcodeMessage(modal, message, style='danger') {
var html = `<div class='alert alert-block alert-${style}'>`;
@ -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" %}<br>{% 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 += `<div class='alert alert-info alert-block'>{% trans "Scan barcode data below" %}</div>`;
content += `<div id='barcode-error-message'></div>`;
content += `<form class='js-modal-form' method='post'>`;
// Optional content before barcode input
content += `<div class='container' id='barcode-header'>`;
content += options.headerContent || '';
content += `</div>`;
content += makeBarcodeInput();
if (options.footerContent) {
content += options.footerContent;
if (options.extraFields) {
content += options.extraFields;
}
content += `</form>`;
// Optional content after barcode input
content += `<div class='container' id='barcode-footer'>`;
content += options.footerContent || '';
content += '</div>';
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" %}.<br>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" %}.<br>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 = `
<table class='table table-condensed table-striped' id='items-table'>
<thead>
<tr>
<th>{% trans "Part" %}</th>
<th>{% trans "Location" %}</th>
<th>{% trans "Quantity" %}</th>
<th></th>
</tr>
</thead>
<tbody>`;
items.forEach(function(item) {
html += `
<tr pk='${item.pk}'>
<td>${imageHoverIcon(item.part_detail.thumbnail)} ${item.part_detail.name}</td>
<td>${item.location_detail.name}</td>
<td>${item.quantity}</td>
<td>${makeIconButton('fa-times-circle icon-red', 'button-item-remove', item.pk, '{% trans "Remove stock item" %}')}</td>
</tr>`;
});
html += `
</tbody>
</table>`;
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 = `<div class='container' id='items-table-div' style='width: 80%; float: left;'></div>`;
// Extra form fields
var extra = `
<div class='form-group'>
<label class='control-label' for='notes'>{% trans "Notes" %}</label>
<div class='controls'>
<div class='input-group'>
<span class='input-group-addon'>
<span class='fas fa-sticky-note'></span>
</span>
<input id='notes' class='textinput textInput form-control' type='text' name='notes' placeholder='{% trans "Enter notes" %}'>
</div>
<div id='hint_notes' class='help_block'>{% trans "Enter optional notes for stock transfer" %}</div>
</div>
</div>`;
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);
}
},
},
);
},
}
);
}