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 @@
-