Updates for stock allocation form

- Data submission
- Nested error handling
- Button callbacks to remove rows
This commit is contained in:
Oliver 2021-10-04 23:20:03 +11:00
parent a9d5b776d3
commit 074466f087
2 changed files with 142 additions and 75 deletions

View File

@ -154,6 +154,13 @@ class BuildAllocationItemSerializer(serializers.Serializer):
required=True required=True
) )
def validate_quantity(self, quantity):
if quantity <= 0:
raise ValidationError(_("Quantity must be greater than zero"))
return quantity
output = serializers.PrimaryKeyRelatedField( output = serializers.PrimaryKeyRelatedField(
queryset=StockItem.objects.filter(is_building=True), queryset=StockItem.objects.filter(is_building=True),
many=False, many=False,

View File

@ -378,52 +378,18 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
// Primary key of the 'sub_part' // Primary key of the 'sub_part'
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
// Launch form to allocate new stock against this output allocateStockToBuild(
launchModalForm('{% url "build-item-create" %}', { buildId,
success: reloadTable, partId,
data: {
part: pk,
build: buildId,
install_into: outputId,
},
secondary: [
{ {
field: 'stock_item', success: function(data) {
label: '{% trans "New Stock Item" %}', console.log("here we go I guess");
title: '{% trans "Create new Stock Item" %}',
url: '{% url "stock-item-create" %}',
data: {
part: pk,
}, },
}, parts: [
], parseInt(pk),
callback: [ ]
{
field: 'stock_item',
action: function(value) {
inventreeGet(
`/api/stock/${value}/`, {},
{
success: function(response) {
// How many items are actually available for the given stock item?
var available = response.quantity - response.allocated;
var field = getFieldByName('#modal-form', 'quantity');
// Allocation quantity initial value
var initial = field.attr('value');
if (available < initial) {
field.val(available);
}
}
} }
); );
}
}
]
});
}); });
// Callback for 'buy' button // Callback for 'buy' button
@ -831,18 +797,18 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
* - outputId: ID / PK of the associated build output (or null for untracked items) * - outputId: ID / PK of the associated build output (or null for untracked items)
* - parts: List of ID values for filtering against specific sub parts * - parts: List of ID values for filtering against specific sub parts
*/ */
function allocateStockToBuild(buildId, partId, options={}) { function allocateStockToBuild(build_id, part_id, options={}) {
// ID of the associated "build output" (or null) // ID of the associated "build output" (or null)
var outputId = options.output || null; var output_id = options.output || null;
// Extract list of BOM items (or empty list) // Extract list of BOM items (or empty list)
var subPartIds = options.parts || []; var sub_part_ids = options.parts || [];
var bomItemQueryParams = { var query_params = {
part: partId, part: part_id,
sub_part_detail: true, sub_part_detail: true,
sub_part_trackable: outputId != null sub_part_trackable: output_id != null
}; };
function renderBomItemRow(bom_item, quantity) { function renderBomItemRow(bom_item, quantity) {
@ -856,29 +822,50 @@ function allocateStockToBuild(buildId, partId, options={}) {
delete_button += makeIconButton( delete_button += makeIconButton(
'fa-times icon-red', 'fa-times icon-red',
'button-part-remove', 'button-row-remove',
pk, pk,
'{% trans "Remove row" %}', '{% trans "Remove row" %}',
); );
delete_button += `</div>`; delete_button += `</div>`;
var quantity_input = constructNumberInput(pk, { var quantity_input = constructField(
value: quantity || 0, `items_quantity_${pk}`,
{
type: 'decimal',
min_value: 0, min_value: 0,
value: quantity || 0,
title: '{% trans "Specify stock allocation quantity" %}', title: '{% trans "Specify stock allocation quantity" %}',
}); required: true,
},
{
hideLabels: true,
}
);
var stock_input = constructRelatedFieldInput(`stock_query_${pk}`); var stock_input = constructField(
`items_stock_item_${pk}`,
{
type: 'related field',
required: 'true',
},
{
hideLabels: true,
}
);
// var stock_input = constructRelatedFieldInput(`items_stock_item_${pk}`);
var html = ` var html = `
<tr id='part_row_${pk}' class='part-allocation-row'> <tr id='allocation_row_${pk}' class='part-allocation-row'>
<td id='part_${pk}'> <td id='part_${pk}'>
${thumb} ${sub_part.full_name} ${thumb} ${sub_part.full_name}
</td> </td>
<td id='stock_item_${pk}'> <td id='stock_item_${pk}'>
${stock_input} ${stock_input}
</td> </td>
<td id='allocated_${pk}'>
</td>
<td id='quantity_${pk}'> <td id='quantity_${pk}'>
${quantity_input} ${quantity_input}
</td> </td>
@ -893,7 +880,7 @@ function allocateStockToBuild(buildId, partId, options={}) {
inventreeGet( inventreeGet(
'{% url "api-bom-list" %}', '{% url "api-bom-list" %}',
bomItemQueryParams, query_params,
{ {
success: function(response) { success: function(response) {
@ -905,10 +892,10 @@ function allocateStockToBuild(buildId, partId, options={}) {
for (var idx = 0; idx < response.length; idx++) { for (var idx = 0; idx < response.length; idx++) {
var item = response[idx]; var item = response[idx];
var subPartId = item.sub_part; var sub_part_id = item.sub_part;
// Check if we are interested in this item // Check if we are interested in this item
if (subPartIds.length > 0 && !subPartIds.includes(subPartId)) { if (sub_part_ids.length > 0 && !sub_part_ids.includes(sub_part_id)) {
continue; continue;
} }
@ -930,16 +917,14 @@ function allocateStockToBuild(buildId, partId, options={}) {
return; return;
} }
var modal = createNewModal({ // Create table of parts
title: '{% trans "Allocate Stock to Build" %}',
});
var html = ` var html = `
<table class='table table-striped table-condensed' id='stock-allocation-table'> <table class='table table-striped table-condensed' id='stock-allocation-table'>
<thead> <thead>
<tr> <tr>
<th>{% trans "Part" %}</th> <th>{% trans "Part" %}</th>
<th style='min-width: 250px;'>{% trans "Stock Item" %}</th> <th style='min-width: 250px;'>{% trans "Stock Item" %}</th>
<th>{% trans "Allocated" %}</th>
<th>{% trans "Quantity" %}</th> <th>{% trans "Quantity" %}</th>
<th></th> <th></th>
</tr> </tr>
@ -950,18 +935,20 @@ function allocateStockToBuild(buildId, partId, options={}) {
</table> </table>
`; `;
constructFormBody({}, { constructForm(`/api/build/${build_id}/allocate/`, {
preFormContent: html, method: 'POST',
fields: {}, fields: {},
preFormContent: html,
confirm: true, confirm: true,
confirmMessage: '{% trans "Confirm Stock Allocation" %}', confirmMessage: '{% trans "Confirm stock allocation" %}',
modal: modal, title: '{% trans "Allocate Stock Items to Build Order" %}',
afterRender: function(fields, options) { afterRender: function(fields, options) {
// Initialize select2 fields
bom_items.forEach(function(bom_item) { bom_items.forEach(function(bom_item) {
initializeRelatedField( initializeRelatedField(
{ {
name: `stock_query_${bom_item.pk}`, name: `items_stock_item_${bom_item.pk}`,
api_url: '{% url "api-stock-list" %}', api_url: '{% url "api-stock-list" %}',
filters: { filters: {
part: bom_item.sub_part, part: bom_item.sub_part,
@ -979,10 +966,83 @@ function allocateStockToBuild(buildId, partId, options={}) {
); );
}); });
// Add button callbacks
$(options.modal).find('.button-row-remove').click(function() {
var pk = $(this).attr('pk');
$(options.modal).find(`#allocation_row_${pk}`).remove();
});
}, },
onSubmit: function(fields) { onSubmit: function(fields, options) {
// TODO
// Extract elements from the form
var data = {
items: []
};
var item_pk_values = [];
bom_items.forEach(function(item) {
var quantity = getFormFieldValue(
`items_quantity_${item.pk}`,
{},
{
modal: options.modal,
},
);
var stock_item = getFormFieldValue(
`items_stock_item_${item.pk}`,
{},
{
modal: options.modal,
} }
);
if (quantity != null) {
data.items.push({
bom_item: item.pk,
stock_item: stock_item,
quantity: quantity
});
item_pk_values.push(item.pk);
}
});
// Provide nested values
options.nested = {
"items": item_pk_values
};
inventreePut(
options.url,
data,
{
method: 'POST',
success: function(response) {
// Hide the modal
$(options.modal).modal('hide');
if (options.success) {
options.success(response);
}
},
error: function(xhr) {
switch (xhr.status) {
case 400:
handleFormErrors(xhr.responseJSON, fields, options);
break;
default:
$(options.modal).modal('hide');
showApiError(xhr);
break;
}
}
}
);
},
}); });
} }
} }