diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index f29cc1de2c..536aced1a2 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -154,6 +154,13 @@ class BuildAllocationItemSerializer(serializers.Serializer): required=True ) + def validate_quantity(self, quantity): + + if quantity <= 0: + raise ValidationError(_("Quantity must be greater than zero")) + + return quantity + output = serializers.PrimaryKeyRelatedField( queryset=StockItem.objects.filter(is_building=True), many=False, diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index 5715af9f3f..14523e445a 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -378,52 +378,18 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { // Primary key of the 'sub_part' var pk = $(this).attr('pk'); - // Launch form to allocate new stock against this output - launchModalForm('{% url "build-item-create" %}', { - success: reloadTable, - data: { - part: pk, - build: buildId, - install_into: outputId, - }, - secondary: [ - { - field: 'stock_item', - label: '{% trans "New Stock Item" %}', - title: '{% trans "Create new Stock Item" %}', - url: '{% url "stock-item-create" %}', - data: { - part: pk, - }, + allocateStockToBuild( + buildId, + partId, + { + success: function(data) { + console.log("here we go I guess"); }, - ], - 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); - } - } - } - ); - } - } - ] - }); + parts: [ + parseInt(pk), + ] + } + ); }); // 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) * - 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) - var outputId = options.output || null; + var output_id = options.output || null; // Extract list of BOM items (or empty list) - var subPartIds = options.parts || []; + var sub_part_ids = options.parts || []; - var bomItemQueryParams = { - part: partId, + var query_params = { + part: part_id, sub_part_detail: true, - sub_part_trackable: outputId != null + sub_part_trackable: output_id != null }; function renderBomItemRow(bom_item, quantity) { @@ -856,29 +822,50 @@ function allocateStockToBuild(buildId, partId, options={}) { delete_button += makeIconButton( 'fa-times icon-red', - 'button-part-remove', + 'button-row-remove', pk, '{% trans "Remove row" %}', ); delete_button += ``; - var quantity_input = constructNumberInput(pk, { - value: quantity || 0, - min_value: 0, - title: '{% trans "Specify stock allocation quantity" %}', - }); + var quantity_input = constructField( + `items_quantity_${pk}`, + { + type: 'decimal', + min_value: 0, + value: quantity || 0, + 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 = ` - + ${thumb} ${sub_part.full_name} ${stock_input} + + ${quantity_input} @@ -893,7 +880,7 @@ function allocateStockToBuild(buildId, partId, options={}) { inventreeGet( '{% url "api-bom-list" %}', - bomItemQueryParams, + query_params, { success: function(response) { @@ -905,10 +892,10 @@ function allocateStockToBuild(buildId, partId, options={}) { for (var idx = 0; idx < response.length; 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 - if (subPartIds.length > 0 && !subPartIds.includes(subPartId)) { + if (sub_part_ids.length > 0 && !sub_part_ids.includes(sub_part_id)) { continue; } @@ -930,16 +917,14 @@ function allocateStockToBuild(buildId, partId, options={}) { return; } - var modal = createNewModal({ - title: '{% trans "Allocate Stock to Build" %}', - }); - + // Create table of parts var html = ` + @@ -950,18 +935,20 @@ function allocateStockToBuild(buildId, partId, options={}) {
{% trans "Part" %} {% trans "Stock Item" %}{% trans "Allocated" %} {% trans "Quantity" %}
`; - constructFormBody({}, { - preFormContent: html, + constructForm(`/api/build/${build_id}/allocate/`, { + method: 'POST', fields: {}, + preFormContent: html, confirm: true, - confirmMessage: '{% trans "Confirm Stock Allocation" %}', - modal: modal, + confirmMessage: '{% trans "Confirm stock allocation" %}', + title: '{% trans "Allocate Stock Items to Build Order" %}', afterRender: function(fields, options) { - + + // Initialize select2 fields bom_items.forEach(function(bom_item) { initializeRelatedField( { - name: `stock_query_${bom_item.pk}`, + name: `items_stock_item_${bom_item.pk}`, api_url: '{% url "api-stock-list" %}', filters: { 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, options) { + + // 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; + } + } + } + ); }, - onSubmit: function(fields) { - // TODO - } }); } }