mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-30 18:50:53 +00:00
[Feature] Scrap Build Outputs (#4800)
* Update docs for status codes * Adds API endpoint for scrapping individual build outputs * Support 'buildorder' reference in stock tracking history * Add page for build output documentation * Build docs * Add example build order process to docs * remove debug statement * JS lint cleanup * Add migration file for stock status * Add unit tests for build output scrapping * Increment API version * bug fix
This commit is contained in:
@ -397,9 +397,17 @@ function makeBuildOutputButtons(output_id, build_info, options={}) {
|
||||
'{% trans "Complete build output" %}',
|
||||
);
|
||||
|
||||
// Add a button to "delete" this build output
|
||||
// Add a button to "scrap" the build output
|
||||
html += makeIconButton(
|
||||
'fa-times-circle icon-red',
|
||||
'button-output-scrap',
|
||||
output_id,
|
||||
'{% trans "Scrap build output" %}',
|
||||
);
|
||||
|
||||
// Add a button to "remove" this build output
|
||||
html += makeDeleteButton(
|
||||
'button-output-delete',
|
||||
'button-output-remove',
|
||||
output_id,
|
||||
'{% trans "Delete build output" %}',
|
||||
);
|
||||
@ -452,6 +460,51 @@ function unallocateStock(build_id, options={}) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Helper function to render a single build output in a modal form
|
||||
*/
|
||||
function renderBuildOutput(output, opts={}) {
|
||||
let pk = output.pk;
|
||||
|
||||
let output_html = imageHoverIcon(output.part_detail.thumbnail);
|
||||
|
||||
if (output.quantity == 1 && output.serial) {
|
||||
output_html += `{% trans "Serial Number" %}: ${output.serial}`;
|
||||
} else {
|
||||
output_html += `{% trans "Quantity" %}: ${output.quantity}`;
|
||||
if (output.part_detail && output.part_detail.units) {
|
||||
output_html += ` ${output.part_detail.units} `;
|
||||
}
|
||||
}
|
||||
|
||||
let buttons = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
buttons += makeRemoveButton('button-row-remove', pk, '{% trans "Remove row" %}');
|
||||
|
||||
buttons += '</div>';
|
||||
|
||||
let field = constructField(
|
||||
`outputs_output_${pk}`,
|
||||
{
|
||||
type: 'raw',
|
||||
html: output_html,
|
||||
},
|
||||
{
|
||||
hideLabels: true,
|
||||
}
|
||||
);
|
||||
|
||||
let html = `
|
||||
<tr id='output_row_${pk}'>
|
||||
<td>${field}</td>
|
||||
<td>${output.part_detail.full_name}</td>
|
||||
<td>${buttons}</td>
|
||||
</tr>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Launch a modal form to complete selected build outputs
|
||||
*/
|
||||
@ -465,48 +518,6 @@ function completeBuildOutputs(build_id, outputs, options={}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Render a single build output (StockItem)
|
||||
function renderBuildOutput(output, opts={}) {
|
||||
var pk = output.pk;
|
||||
|
||||
var output_html = imageHoverIcon(output.part_detail.thumbnail);
|
||||
|
||||
if (output.quantity == 1 && output.serial) {
|
||||
output_html += `{% trans "Serial Number" %}: ${output.serial}`;
|
||||
} else {
|
||||
output_html += `{% trans "Quantity" %}: ${output.quantity}`;
|
||||
if (output.part_detail && output.part_detail.units) {
|
||||
output_html += ` ${output.part_detail.units} `;
|
||||
}
|
||||
}
|
||||
|
||||
var buttons = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
buttons += makeRemoveButton('button-row-remove', pk, '{% trans "Remove row" %}');
|
||||
|
||||
buttons += '</div>';
|
||||
|
||||
var field = constructField(
|
||||
`outputs_output_${pk}`,
|
||||
{
|
||||
type: 'raw',
|
||||
html: output_html,
|
||||
},
|
||||
{
|
||||
hideLabels: true,
|
||||
}
|
||||
);
|
||||
|
||||
var html = `
|
||||
<tr id='output_row_${pk}'>
|
||||
<td>${field}</td>
|
||||
<td>${output.part_detail.full_name}</td>
|
||||
<td>${buttons}</td>
|
||||
</tr>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Construct table entries
|
||||
var table_entries = '';
|
||||
|
||||
@ -515,6 +526,9 @@ function completeBuildOutputs(build_id, outputs, options={}) {
|
||||
});
|
||||
|
||||
var html = `
|
||||
<div class='alert alert-block alert-success'>
|
||||
{% trans "Selected build outputs will be marked as complete" %}
|
||||
</div>
|
||||
<table class='table table-striped table-condensed' id='build-complete-table'>
|
||||
<thead>
|
||||
<th colspan='2'>{% trans "Output" %}</th>
|
||||
@ -613,8 +627,122 @@ function completeBuildOutputs(build_id, outputs, options={}) {
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Launch a modal form to scrap selected build outputs.
|
||||
* Scrapped outputs are marked as "complete", but with the "rejected" code
|
||||
* These outputs are not included in build completion calculations.
|
||||
*/
|
||||
function scrapBuildOutputs(build_id, outputs, options={}) {
|
||||
|
||||
if (outputs.length == 0) {
|
||||
showAlertDialog(
|
||||
'{% trans "Select Build Outputs" %}',
|
||||
'{% trans "At least one build output must be selected" %}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let table_entries = '';
|
||||
|
||||
outputs.forEach(function(output) {
|
||||
table_entries += renderBuildOutput(output);
|
||||
});
|
||||
|
||||
var html = `
|
||||
<div class='alert alert-block alert-danger'>
|
||||
{% trans "Selected build outputs will be marked as scrapped" %}
|
||||
<ul>
|
||||
<li>{% trans "Scrapped output are given the 'rejected' status" %}</li>
|
||||
<li>{% trans "Allocated stock items will no longer be available" %}</li>
|
||||
<li>{% trans "The completion status of the build order will not be adjusted" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<table class='table table-striped table-condensed' id='build-scrap-table'>
|
||||
<thead>
|
||||
<th colspan='2'>{% trans "Output" %}</th>
|
||||
<th><!-- Actions --></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
${table_entries}
|
||||
</tbody>
|
||||
</table>`;
|
||||
|
||||
constructForm(`{% url "api-build-list" %}${build_id}/scrap-outputs/`, {
|
||||
method: 'POST',
|
||||
preFormContent: html,
|
||||
fields: {
|
||||
location: {},
|
||||
notes: {},
|
||||
discard_allocations: {},
|
||||
},
|
||||
confirm: true,
|
||||
title: '{% trans "Scrap Build Outputs" %}',
|
||||
afterRender: function(fields, opts) {
|
||||
// Setup callbacks to remove outputs
|
||||
$(opts.modal).find('.button-row-remove').click(function() {
|
||||
let pk = $(this).attr('pk');
|
||||
$(opts.modal).find(`#output_row_${pk}`).remove();
|
||||
});
|
||||
},
|
||||
onSubmit: function(fields, opts) {
|
||||
let data = {
|
||||
outputs: [],
|
||||
location: getFormFieldValue('location', {}, opts),
|
||||
notes: getFormFieldValue('notes', {}, opts),
|
||||
discard_allocations: getFormFieldValue('discard_allocations', {type: 'boolean'}, opts),
|
||||
};
|
||||
|
||||
let output_pk_values = [];
|
||||
|
||||
outputs.forEach(function(output) {
|
||||
let pk = output.pk;
|
||||
let row = $(opts.modal).find(`#output_row_${pk}`);
|
||||
|
||||
if (row.exists()) {
|
||||
data.outputs.push({
|
||||
output: pk,
|
||||
});
|
||||
output_pk_values.push(pk);
|
||||
}
|
||||
});
|
||||
|
||||
opts.nested = {
|
||||
'outputs': output_pk_values,
|
||||
};
|
||||
|
||||
inventreePut(
|
||||
opts.url,
|
||||
data,
|
||||
{
|
||||
method: 'POST',
|
||||
success: function(response) {
|
||||
$(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, opts.url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Launch a modal form to delete selected build outputs
|
||||
* Launch a modal form to delete selected build outputs.
|
||||
* Deleted outputs are expunged from the database.
|
||||
*/
|
||||
function deleteBuildOutputs(build_id, outputs, options={}) {
|
||||
|
||||
@ -626,48 +754,6 @@ function deleteBuildOutputs(build_id, outputs, options={}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Render a single build output (StockItem)
|
||||
function renderBuildOutput(output, opts={}) {
|
||||
var pk = output.pk;
|
||||
|
||||
var output_html = imageHoverIcon(output.part_detail.thumbnail);
|
||||
|
||||
if (output.quantity == 1 && output.serial) {
|
||||
output_html += `{% trans "Serial Number" %}: ${output.serial}`;
|
||||
} else {
|
||||
output_html += `{% trans "Quantity" %}: ${output.quantity}`;
|
||||
if (output.part_detail && output.part_detail.units) {
|
||||
output_html += ` ${output.part_detail.units} `;
|
||||
}
|
||||
}
|
||||
|
||||
var buttons = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
buttons += makeRemoveButton('button-row-remove', pk, '{% trans "Remove row" %}');
|
||||
|
||||
buttons += '</div>';
|
||||
|
||||
var field = constructField(
|
||||
`outputs_output_${pk}`,
|
||||
{
|
||||
type: 'raw',
|
||||
html: output_html,
|
||||
},
|
||||
{
|
||||
hideLabels: true,
|
||||
}
|
||||
);
|
||||
|
||||
var html = `
|
||||
<tr id='output_row_${pk}'>
|
||||
<td>${field}</td>
|
||||
<td>${output.part_detail.full_name}</td>
|
||||
<td>${buttons}</td>
|
||||
</tr>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Construct table entries
|
||||
var table_entries = '';
|
||||
|
||||
@ -676,6 +762,13 @@ function deleteBuildOutputs(build_id, outputs, options={}) {
|
||||
});
|
||||
|
||||
var html = `
|
||||
<div class='alert alert-block alert-danger'>
|
||||
{% trans "Selected build outputs will be deleted" %}
|
||||
<ul>
|
||||
<li>{% trans "Build output data will be permanently deleted" %}</li>
|
||||
<li>{% trans "Allocated stock items will be returned to stock" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<table class='table table-striped table-condensed' id='build-complete-table'>
|
||||
<thead>
|
||||
<th colspan='2'>{% trans "Output" %}</th>
|
||||
@ -952,8 +1045,25 @@ function loadBuildOutputTable(build_info, options={}) {
|
||||
);
|
||||
});
|
||||
|
||||
// Callback for the "delete" button
|
||||
$(table).find('.button-output-delete').click(function() {
|
||||
// Callback for the "scrap" button
|
||||
$(table).find('.button-output-scrap').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
var output = $(table).bootstrapTable('getRowByUniqueId', pk);
|
||||
|
||||
scrapBuildOutputs(
|
||||
build_info.pk,
|
||||
[output],
|
||||
{
|
||||
success: function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
$('#build-stock-table').bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Callback for the "remove" button
|
||||
$(table).find('.button-output-remove').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
var output = $(table).bootstrapTable('getRowByUniqueId', pk);
|
||||
@ -1368,6 +1478,25 @@ function loadBuildOutputTable(build_info, options={}) {
|
||||
|
||||
// Add callbacks for the various table menubar buttons
|
||||
|
||||
// Scrap multiple outputs
|
||||
$('#multi-output-scrap').click(function() {
|
||||
var outputs = getTableData(table);
|
||||
|
||||
scrapBuildOutputs(
|
||||
build_info.pk,
|
||||
outputs,
|
||||
{
|
||||
success: function() {
|
||||
// Reload the "in progress" table
|
||||
$('#build-output-table').bootstrapTable('refresh');
|
||||
|
||||
// Reload the "completed" table
|
||||
$('#build-stock-table').bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Complete multiple outputs
|
||||
$('#multi-output-complete').click(function() {
|
||||
var outputs = getTableData(table);
|
||||
|
Reference in New Issue
Block a user