mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Working towards better build allocation:
- Improve data serialization in API - Javascript bug fixes - Load the build allocation table using jQuery
This commit is contained in:
parent
c11b433d94
commit
b0891c921c
@ -137,7 +137,4 @@ class BarcodePluginView(APIView):
|
|||||||
# Include the original barcode data
|
# Include the original barcode data
|
||||||
response['barcode_data'] = barcode_data
|
response['barcode_data'] = barcode_data
|
||||||
|
|
||||||
print("Response:")
|
|
||||||
print(response)
|
|
||||||
|
|
||||||
return Response(response)
|
return Response(response)
|
||||||
|
@ -25,7 +25,6 @@ function inventreeGet(url, filters={}, options={}) {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
console.log('Success GET data at ' + url);
|
|
||||||
if (options.success) {
|
if (options.success) {
|
||||||
options.success(response);
|
options.success(response);
|
||||||
}
|
}
|
||||||
@ -64,7 +63,6 @@ function inventreeFormDataUpload(url, data, options={}) {
|
|||||||
processData: false,
|
processData: false,
|
||||||
contentType: false,
|
contentType: false,
|
||||||
success: function(data, status, xhr) {
|
success: function(data, status, xhr) {
|
||||||
console.log('Form data upload success');
|
|
||||||
if (options.success) {
|
if (options.success) {
|
||||||
options.success(data, status, xhr);
|
options.success(data, status, xhr);
|
||||||
}
|
}
|
||||||
@ -97,7 +95,6 @@ function inventreePut(url, data={}, options={}) {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(response, status) {
|
success: function(response, status) {
|
||||||
console.log(method + ' - ' + url + ' : result = ' + status);
|
|
||||||
if (options.success) {
|
if (options.success) {
|
||||||
options.success(response, status);
|
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);
|
|
||||||
}
|
|
@ -1,4 +1,5 @@
|
|||||||
function loadBuildTable(table, options) {
|
function loadBuildTable(table, options) {
|
||||||
|
// Display a table of Build objects
|
||||||
|
|
||||||
var params = options.params || {};
|
var params = options.params || {};
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ function makeProgressBar(value, maximum, opts) {
|
|||||||
extraclass = 'progress-bar-under';
|
extraclass = 'progress-bar-under';
|
||||||
}
|
}
|
||||||
|
|
||||||
var id = opts.id || 'progress-bar';
|
var id = options.id || 'progress-bar';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div id='${id}' class='progress'>
|
<div id='${id}' class='progress'>
|
||||||
|
@ -95,20 +95,25 @@ class BuildItemList(generics.ListCreateAPIView):
|
|||||||
to allow filtering by stock_item.part
|
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 = BuildItem.objects.all()
|
||||||
|
|
||||||
query = query.select_related('stock_item')
|
query = query.select_related('stock_item')
|
||||||
query = query.prefetch_related('stock_item__part')
|
query = query.prefetch_related('stock_item__part')
|
||||||
query = query.prefetch_related('stock_item__part__category')
|
query = query.prefetch_related('stock_item__part__category')
|
||||||
|
|
||||||
if part_pk:
|
|
||||||
query = query.filter(stock_item__part=part_pk)
|
|
||||||
|
|
||||||
return query
|
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 = [
|
permission_classes = [
|
||||||
permissions.IsAuthenticated,
|
permissions.IsAuthenticated,
|
||||||
]
|
]
|
||||||
@ -128,7 +133,7 @@ build_item_api_urls = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
build_api_urls = [
|
build_api_urls = [
|
||||||
url(r'^item/?', include(build_item_api_urls)),
|
url(r'^item/', include(build_item_api_urls)),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/', BuildDetail.as_view(), name='api-build-detail'),
|
url(r'^(?P<pk>\d+)/', BuildDetail.as_view(), name='api-build-detail'),
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ class BuildSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
|
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
|
||||||
|
|
||||||
|
quantity = serializers.FloatField()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
part_detail = kwargs.pop('part_detail', False)
|
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)
|
part_image = serializers.CharField(source='stock_item.part.image', read_only=True)
|
||||||
stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True)
|
stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True)
|
||||||
|
|
||||||
|
quantity = serializers.FloatField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BuildItem
|
model = BuildItem
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% extends "build/build_base.html" %}
|
{% extends "build/build_base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
{% load inventree_extras %}
|
{% load inventree_extras %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
@ -27,6 +28,141 @@ InvenTree | Allocate Parts
|
|||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ 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 = `<div class='btn-group float-right' role='group'>`;
|
||||||
|
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 += '</div>';
|
||||||
|
|
||||||
|
return html;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
{% if editing %}
|
{% if editing %}
|
||||||
|
|
||||||
{% for bom_item in bom_items.all %}
|
{% for bom_item in bom_items.all %}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load inventree_extras %}
|
{% load inventree_extras %}
|
||||||
|
|
||||||
<h4>{% trans "Required Parts" %}</h4>
|
<h4>{% trans "Allocated Parts" %}</h4>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div id='build-item-toolbar'>
|
<div id='build-item-toolbar'>
|
||||||
@ -11,7 +11,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class='table table-striped table-condensed' id='build-list' data-sorting='true' data-toolbar='#build-item-toolbar'>
|
<table class='table table-striped table-condensed' id='build-item-list' data-toolbar='#build-item-toolbar'></table>
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed' id='build-list' data-sorting='true'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th data-sortable='true'>{% trans "Part" %}</th>
|
<th data-sortable='true'>{% trans "Part" %}</th>
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
<li{% if tab == 'details' %} class='active'{% endif %}>
|
<li{% if tab == 'details' %} class='active'{% endif %}>
|
||||||
<a href="{% url 'build-detail' build.id %}">{% trans "Details" %}</a>
|
<a href="{% url 'build-detail' build.id %}">{% trans "Details" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li{% if tab == 'allocate' %} class='active'{% endif %}>
|
||||||
|
<a href="{% url 'build-allocate' build.id %}">{% trans "Allocated Parts" %}</a>
|
||||||
|
</li>
|
||||||
<li{% if tab == 'output' %} class='active'{% endif %}>
|
<li{% if tab == 'output' %} class='active'{% endif %}>
|
||||||
<a href="{% url 'build-output' build.id %}">{% trans "Outputs" %}{% if build.output_count > 0%}<span class='badge'>{{ build.output_count }}</span>{% endif %}</a>
|
<a href="{% url 'build-output' build.id %}">{% trans "Build Outputs" %}{% if build.output_count > 0%}<span class='badge'>{{ build.output_count }}</span>{% endif %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li{% if tab == 'notes' %} class='active'{% endif %}>
|
<li{% if tab == 'notes' %} class='active'{% endif %}>
|
||||||
<a href="{% url 'build-notes' build.id %}">{% trans "Notes" %}{% if build.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
<a href="{% url 'build-notes' build.id %}">{% trans "Notes" %}{% if build.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li{% if tab == 'allocate' %} class='active'{% endif %}>
|
|
||||||
<a href="{% url 'build-allocate' build.id %}">{% trans "Assign Parts" %}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
@ -235,6 +235,8 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
price_range = serializers.CharField(read_only=True)
|
price_range = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
|
quantity = serializers.FloatField()
|
||||||
|
|
||||||
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
|
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
|
||||||
sub_part_detail = PartBriefSerializer(source='sub_part', many=False, read_only=True)
|
sub_part_detail = PartBriefSerializer(source='sub_part', many=False, read_only=True)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user