diff --git a/InvenTree/templates/js/dynamic/inventree.js b/InvenTree/templates/js/dynamic/inventree.js
index 0324d72e3c..d06399c61e 100644
--- a/InvenTree/templates/js/dynamic/inventree.js
+++ b/InvenTree/templates/js/dynamic/inventree.js
@@ -290,3 +290,8 @@ function loadBrandIcon(element, name) {
element.addClass('fab fa-' + name);
}
}
+
+// Convenience function to determine if an element exists
+$.fn.exists = function() {
+ return this.length !== 0;
+}
diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js
index 96594ed8dd..2515cbc346 100644
--- a/InvenTree/templates/js/translated/build.js
+++ b/InvenTree/templates/js/translated/build.js
@@ -324,6 +324,143 @@ function unallocateStock(build_id, options={}) {
}
}
});
+}
+
+
+/**
+ * Launch a modal form to complete selected build outputs
+ */
+function completeBuildOutputs(build_id, outputs, options={}) {
+
+ if (outputs.length == 0) {
+ showAlertDialog(
+ '{% trans "Select Build Outputs" %}',
+ '{% trans "At least one build output must be selected" %}',
+ );
+ return;
+ }
+
+ // Render a single build output (StockItem)
+ function renderBuildOutput(output, opts={}) {
+ var pk = output.pk;
+
+ var quantity = '';
+
+ if (output.quantity == 1 && output.serial) {
+ quantity = `{% trans "Serial Number" %}: ${output.serial}`;
+ } else {
+ quantity = `{% trans "Quantity" %}: ${output.quantity}`;
+ }
+
+ var buttons = `
`;
+
+ buttons += makeIconButton('fa-times icon-red', 'button-row-remove', pk, '{% trans "Remove row" %}');
+
+ buttons += '
';
+
+ var html = `
+
+ ${quantity} |
+ ${buttons} |
+
`;
+
+ return html;
+ }
+
+ // Construct table entries
+ var table_entries = '';
+
+ outputs.forEach(function(output) {
+ table_entries += renderBuildOutput(output);
+ });
+
+ var html = `
+
+
+ {% trans "Output" %} |
+ |
+
+
+ ${table_entries}
+
+
`;
+
+ constructForm(`/api/build/${build_id}/complete/`, {
+ method: 'POST',
+ preFormContent: html,
+ fields: {
+ status: {},
+ location: {},
+ },
+ confirm: true,
+ title: '{% trans "Complete Build Outputs" %}',
+ afterRender: function(fields, opts) {
+ // Setup callbacks to remove outputs
+ $(opts.modal).find('.button-row-remove').click(function() {
+ var pk = $(this).attr('pk');
+
+ $(opts.modal).find(`#output_row_${pk}`).remove();
+ });
+ },
+ onSubmit: function(fields, opts) {
+
+ // Extract data elements from the form
+ var data = {
+ outputs: [],
+ status: getFormFieldValue('status', {}, opts),
+ location: getFormFieldValue('location', {}, opts),
+ };
+
+ var output_pk_values = [];
+
+ outputs.forEach(function(output) {
+ var pk = output.pk;
+
+ var row = $(opts.modal).find(`#output_row_${pk}`);
+
+ if (row.exists()) {
+ data.outputs.push({
+ output: pk,
+ });
+ output_pk_values.push(pk);
+ } else {
+ console.log(`Could not find row for ${pk}`);
+ }
+ });
+
+ // Provide list of nested values
+ opts.nested = {
+ 'outputs': output_pk_values,
+ };
+
+ inventreePut(
+ opts.url,
+ data,
+ {
+ method: 'POST',
+ success: function(response) {
+ // Hide the modal
+ $(opts.modal).modal('hide');
+
+ if (options.success) {
+ options.success(response);
+ }
+ },
+ error: function(xhr) {
+ switch (xhr.status) {
+ case 400:
+ handleFormErrors(xhr.responseJSON, fields, opts);
+ break;
+ default:
+ $(opts.modal).modal('hide');
+ showApiError(xhr);
+ break;
+ }
+ }
+ }
+ )
+ }
+ });
}
@@ -453,6 +590,7 @@ function loadBuildOutputTable(build_info, options={}) {
// TODO
var todo = "Work out which stock items we need to allocate and launch the form";
+
/*
allocateStockToBuild(
build_info.pk,
@@ -475,7 +613,19 @@ function loadBuildOutputTable(build_info, options={}) {
$(table).find('.button-output-complete').click(function() {
var pk = $(this).attr('pk');
- var todo = "write function to complete build output(s)";
+ var output = $(table).bootstrapTable('getRowByUniqueId', pk);
+
+ completeBuildOutputs(
+ build_info.pk,
+ [
+ output,
+ ],
+ {
+ success: function() {
+ $(table).bootstrapTable('refresh');
+ }
+ }
+ );
});
// Callback for the "delete" button
@@ -554,6 +704,8 @@ function loadBuildOutputTable(build_info, options={}) {
field: 'allocated',
title: '{% trans "Allocated" %}',
formatter: function(value, row) {
+
+ // Render a "progress" row
return "todo";
}
},
@@ -579,6 +731,7 @@ function loadBuildOutputTable(build_info, options={}) {
queryParams: filters,
original: params,
showColumns: true,
+ uniqueId: 'pk',
name: 'build-outputs',
sortable: true,
search: true,