From b0891c921c09eca7481f00c510c42f09040be18b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 25 Apr 2020 21:13:38 +1000 Subject: [PATCH] Working towards better build allocation: - Improve data serialization in API - Javascript bug fixes - Load the build allocation table using jQuery --- InvenTree/InvenTree/api.py | 3 - .../InvenTree/static/script/inventree/api.js | 25 ---- .../static/script/inventree/build.js | 1 + .../static/script/inventree/inventree.js | 2 +- InvenTree/build/api.py | 19 ++- InvenTree/build/serializers.py | 4 + InvenTree/build/templates/build/allocate.html | 136 ++++++++++++++++++ .../build/templates/build/allocate_view.html | 6 +- InvenTree/build/templates/build/tabs.html | 8 +- InvenTree/part/serializers.py | 2 + 10 files changed, 164 insertions(+), 42 deletions(-) diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py index bfcc02794e..abb6102a6f 100644 --- a/InvenTree/InvenTree/api.py +++ b/InvenTree/InvenTree/api.py @@ -137,7 +137,4 @@ class BarcodePluginView(APIView): # Include the original barcode data response['barcode_data'] = barcode_data - print("Response:") - print(response) - return Response(response) diff --git a/InvenTree/InvenTree/static/script/inventree/api.js b/InvenTree/InvenTree/static/script/inventree/api.js index 8c67f92979..0a3b8d9374 100644 --- a/InvenTree/InvenTree/static/script/inventree/api.js +++ b/InvenTree/InvenTree/static/script/inventree/api.js @@ -25,7 +25,6 @@ function inventreeGet(url, filters={}, options={}) { dataType: 'json', contentType: 'application/json', success: function(response) { - console.log('Success GET data at ' + url); if (options.success) { options.success(response); } @@ -64,7 +63,6 @@ function inventreeFormDataUpload(url, data, options={}) { processData: false, contentType: false, success: function(data, status, xhr) { - console.log('Form data upload success'); if (options.success) { options.success(data, status, xhr); } @@ -97,7 +95,6 @@ function inventreePut(url, data={}, options={}) { dataType: 'json', contentType: 'application/json', success: function(response, status) { - console.log(method + ' - ' + url + ' : result = ' + status); if (options.success) { options.success(response, status); } @@ -114,25 +111,3 @@ function inventreePut(url, data={}, options={}) { } }); } - -// Return list of parts with optional filters -function getParts(filters={}, options={}) { - return inventreeGet('/api/part/', filters, options); -} - -// Return list of part categories with optional filters -function getPartCategories(filters={}, options={}) { - return inventreeGet('/api/part/category/', filters, options); -} - -function getCompanies(filters={}, options={}) { - return inventreeGet('/api/company/', filters, options); -} - -function updateStockItem(pk, data, final=false) { - return inventreePut('/api/stock/' + pk + '/', data, final); -} - -function updatePart(pk, data, final=false) { - return inventreePut('/api/part/' + pk + '/', data, final); -} \ No newline at end of file diff --git a/InvenTree/InvenTree/static/script/inventree/build.js b/InvenTree/InvenTree/static/script/inventree/build.js index e61fa397a6..28767c99b4 100644 --- a/InvenTree/InvenTree/static/script/inventree/build.js +++ b/InvenTree/InvenTree/static/script/inventree/build.js @@ -1,4 +1,5 @@ function loadBuildTable(table, options) { + // Display a table of Build objects var params = options.params || {}; diff --git a/InvenTree/InvenTree/static/script/inventree/inventree.js b/InvenTree/InvenTree/static/script/inventree/inventree.js index 650a115e9c..5995034241 100644 --- a/InvenTree/InvenTree/static/script/inventree/inventree.js +++ b/InvenTree/InvenTree/static/script/inventree/inventree.js @@ -121,7 +121,7 @@ function makeProgressBar(value, maximum, opts) { extraclass = 'progress-bar-under'; } - var id = opts.id || 'progress-bar'; + var id = options.id || 'progress-bar'; return `
diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index ceddb10014..de44f1185c 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -95,20 +95,25 @@ class BuildItemList(generics.ListCreateAPIView): to allow filtering by stock_item.part """ - # Does the user wish to filter by part? - part_pk = self.request.query_params.get('part', None) - query = BuildItem.objects.all() query = query.select_related('stock_item') query = query.prefetch_related('stock_item__part') query = query.prefetch_related('stock_item__part__category') - if part_pk: - query = query.filter(stock_item__part=part_pk) - return query + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + + # Does the user wish to filter by part? + part_pk = self.request.query_params.get('part', None) + + if part_pk: + queryset = queryset.filter(stock_item__part=part_pk) + + return queryset + permission_classes = [ permissions.IsAuthenticated, ] @@ -128,7 +133,7 @@ build_item_api_urls = [ ] build_api_urls = [ - url(r'^item/?', include(build_item_api_urls)), + url(r'^item/', include(build_item_api_urls)), url(r'^(?P\d+)/', BuildDetail.as_view(), name='api-build-detail'), diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index bc90b2b152..6480004699 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -21,6 +21,8 @@ class BuildSerializer(InvenTreeModelSerializer): part_detail = PartBriefSerializer(source='part', many=False, read_only=True) + quantity = serializers.FloatField() + def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) @@ -63,6 +65,8 @@ class BuildItemSerializer(InvenTreeModelSerializer): part_image = serializers.CharField(source='stock_item.part.image', read_only=True) stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True) + quantity = serializers.FloatField() + class Meta: model = BuildItem fields = [ diff --git a/InvenTree/build/templates/build/allocate.html b/InvenTree/build/templates/build/allocate.html index 3e67c65145..60637cf66f 100644 --- a/InvenTree/build/templates/build/allocate.html +++ b/InvenTree/build/templates/build/allocate.html @@ -1,5 +1,6 @@ {% extends "build/build_base.html" %} {% load static %} +{% load i18n %} {% load inventree_extras %} {% block page_title %} @@ -27,6 +28,141 @@ InvenTree | Allocate Parts {% block js_ready %} {{ block.super }} + var buildTable = $("#build-item-list"); + + buildTable.inventreeTable({ + uniqueId: 'sub_part', + url: "{% url 'api-bom-list' %}", + formatNoMatches: function() { return "{% trans 'No BOM items found' %}"; }, + onLoadSuccess: function(tableData) { + // Once the BOM data are loaded, request allocation data for the build + inventreeGet('/api/build/item/', { + build: {{ build.id }}, + }, + { + success: function(data) { + + // Iterate through the returned data, and group by "part" + var allocations = {}; + + data.forEach(function(item) { + + // Group allocations by referenced 'part' + var part = item.part; + var key = parseInt(part); + + if (!(key in allocations)) { + allocations[key] = new Array(); + } + + // Add the allocation to the list + allocations[key].push(item); + }); + + for (var key in allocations) { + + // Select the associated row in the table + var tableRow = buildTable.bootstrapTable('getRowByUniqueId', key); + + // Set the allocations for the row + tableRow.allocations = allocations[key]; + + // And push the updated row back into the main table + buildTable.bootstrapTable('updateByUniqueId', key, tableRow, true); + } + } + }, + ); + }, + queryParams: { + part: {{ build.part.id }}, + sub_part_detail: 1, + }, + columns: [ + { + field: 'id', + visible: false, + }, + { + sortable: true, + field: 'sub_part', + title: '{% trans "Part" %}', + formatter: function(value, row, index, field) { + return imageHoverIcon(row.sub_part_detail.thumbnail) + renderLink(row.sub_part_detail.full_name, `/part/${row.sub_part}/`); + }, + }, + { + sortable: true, + field: 'sub_part_detail.description', + title: '{% trans "Description" %}', + }, + { + sortable: true, + field: 'reference', + title: '{% trans "Reference" %}', + }, + { + sortable: true, + field: 'quantity', + title: '{% trans "Required" %}', + formatter: function(value, row) { + return value * {{ build.quantity }}; + }, + }, + { + sortable: true, + field: 'allocated', + title: '{% trans "Allocated" %}', + formatter: function(value, row) { + var progress = value || 0; + + return makeProgressBar(progress, row.quantity * {{ build.quantity }}); + }, + sorter: function(valA, valB, rowA, rowB) { + + var aA = rowA.allocated || 0; + var aB = rowB.allocated || 0; + + var qA = rowA.quantity * {{ build.quantity }}; + var qB = rowB.quantity * {{ build.quantity }}; + + if (aA == 0 && aB == 0) { + return (qA > qB) ? 1 : -1; + } + + var progressA = parseFloat(aA) / qA; + var progressB = parseFloat(aB) / qB; + + return (progressA < progressB) ? 1 : -1; + } + }, + { + field: 'buttons', + formatter: function(value, row, index, field) { + + var html = `
`; + var pk = row.sub_part; + + {% if build.status == BuildStatus.PENDING %} + if (row.sub_part_detail.purchaseable) { + html += makeIconButton('fa-shopping-cart', 'button-buy', pk, '{% trans "Buy parts" %}'); + } + + if (row.sub_part.assembly) { + html += makeIconButton('fa-tools', 'button-build', pk, '{% trans "Build parts" %}'); + } + + html += makeIconButton('fa-plus', 'button-add', pk, '{% trans "Allocate stock" %}'); + {% endif %} + + html += '
'; + + return html; + }, + } + ], + }); + {% if editing %} {% for bom_item in bom_items.all %} diff --git a/InvenTree/build/templates/build/allocate_view.html b/InvenTree/build/templates/build/allocate_view.html index 6100f7232e..ed404a8fc1 100644 --- a/InvenTree/build/templates/build/allocate_view.html +++ b/InvenTree/build/templates/build/allocate_view.html @@ -1,7 +1,7 @@ {% load i18n %} {% load inventree_extras %} -

{% trans "Required Parts" %}

+

{% trans "Allocated Parts" %}


@@ -11,7 +11,9 @@
- +
+ + diff --git a/InvenTree/build/templates/build/tabs.html b/InvenTree/build/templates/build/tabs.html index 552bac7408..24b1e53a15 100644 --- a/InvenTree/build/templates/build/tabs.html +++ b/InvenTree/build/templates/build/tabs.html @@ -4,13 +4,13 @@ {% trans "Details" %} + + {% trans "Allocated Parts" %} + - {% trans "Outputs" %}{% if build.output_count > 0%}{{ build.output_count }}{% endif %} + {% trans "Build Outputs" %}{% if build.output_count > 0%}{{ build.output_count }}{% endif %} {% trans "Notes" %}{% if build.notes %} {% endif %} - - {% trans "Assign Parts" %} - \ No newline at end of file diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 26017a4e4e..8cb3584664 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -234,6 +234,8 @@ class BomItemSerializer(InvenTreeModelSerializer): """ Serializer for BomItem object """ price_range = serializers.CharField(read_only=True) + + quantity = serializers.FloatField() part_detail = PartBriefSerializer(source='part', many=False, read_only=True) sub_part_detail = PartBriefSerializer(source='sub_part', many=False, read_only=True)
{% trans "Part" %}