diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index 40dc772c4f..5e56990c3e 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -1,9 +1,10 @@ {% extends "build/build_base.html" %} {% load static %} {% load i18n %} -{% block details %} {% load status_codes %} +{% block details %} + {% include "build/tabs.html" with tab='details' %}

{% trans "Build Details" %}

diff --git a/InvenTree/build/templates/build/parts.html b/InvenTree/build/templates/build/parts.html new file mode 100644 index 0000000000..3b80cf551b --- /dev/null +++ b/InvenTree/build/templates/build/parts.html @@ -0,0 +1,28 @@ +{% extends "build/build_base.html" %} +{% load static %} +{% load i18n %} +{% load status_codes %} + +{% block details %} + +{% include "build/tabs.html" with tab='parts' %} + +

{% trans "Build Parts" %}

+
+ +
+ +{% endblock %} + +{% block js_ready %} + +{{ block.super }} + +loadBuildPartsTable($('#parts-table'), { + part: {{ build.part.pk }}, + build: {{ build.pk }}, + build_quantity: {{ build.quantity }}, + build_remaining: {{ build.remaining }}, +}); + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/tabs.html b/InvenTree/build/templates/build/tabs.html index 8bdb2a3013..c6d2893620 100644 --- a/InvenTree/build/templates/build/tabs.html +++ b/InvenTree/build/templates/build/tabs.html @@ -5,16 +5,22 @@ {% trans "Details" %} {% if build.active %} +
  • + + {% trans "Required Parts" %} + {{ build.part.bom_count }} + +
  • - {% trans "Incomplete" %} + {% trans "In Progress" %} {{ build.incomplete_outputs.count }} {% endif %} - {% trans "Build Outputs" %} + {% trans "Completed Outputs" %} {{ build.output_count }} diff --git a/InvenTree/build/urls.py b/InvenTree/build/urls.py index 08142e6939..6f681f5488 100644 --- a/InvenTree/build/urls.py +++ b/InvenTree/build/urls.py @@ -20,6 +20,7 @@ build_detail_urls = [ url(r'^notes/', views.BuildNotes.as_view(), name='build-notes'), + url(r'^parts/', views.BuildDetail.as_view(template_name='build/parts.html'), name='build-parts'), url(r'^attachments/', views.BuildDetail.as_view(template_name='build/attachments.html'), name='build-attachments'), url(r'^output/', views.BuildDetail.as_view(template_name='build/build_output.html'), name='build-output'), diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index ca0446378c..322d637089 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -77,19 +77,6 @@ {% endblock %} -{% block js_load %} -{{ block.super }} - - - - - - - - -{% endblock %} - - {% block js_ready %} {{ block.super }} diff --git a/InvenTree/templates/base.html b/InvenTree/templates/base.html index c1640892de..bb78e4a45d 100644 --- a/InvenTree/templates/base.html +++ b/InvenTree/templates/base.html @@ -107,6 +107,13 @@ InvenTree + + + + + + + diff --git a/InvenTree/templates/js/build.js b/InvenTree/templates/js/build.js index 965207de66..da5a54819d 100644 --- a/InvenTree/templates/js/build.js +++ b/InvenTree/templates/js/build.js @@ -833,4 +833,147 @@ function loadAllocationTable(table, part_id, part, url, required, button) { }); }); +} + + +function loadBuildPartsTable(table, options={}) { + /** + * Display a "required parts" table for build view. + * + * This is a simplified BOM view: + * - Does not display sub-bom items + * - Does not allow editing of BOM items + * + * Options: + * + * part: Part ID + * build: Build ID + * build_quantity: Total build quantity + * build_remaining: Number of items remaining + */ + + // Query params + var params = { + sub_part_detail: true, + part: options.part, + }; + + var filters = {}; + + if (!options.disableFilters) { + filters = loadTableFilters('bom'); + } + + setupFilterList('bom', $(table)); + + for (var key in params) { + filters[key] = params[key]; + } + + var columns = [ + { + field: 'sub_part', + title: '{% trans "Part" %}', + switchable: false, + sortable: true, + formatter: function(value, row, index, field) { + var url = `/part/${row.sub_part}/`; + var html = imageHoverIcon(row.sub_part_detail.thumbnail) + renderLink(row.sub_part_detail.full_name, url); + + var sub_part = row.sub_part_detail; + + html += makePartIcons(row.sub_part_detail); + + // Display an extra icon if this part is an assembly + if (sub_part.assembly) { + var text = ``; + + html += renderLink(text, `/part/${row.sub_part}/bom/`); + } + + return html; + } + }, + { + field: 'sub_part_detail.description', + title: '{% trans "Description" %}', + }, + { + field: 'reference', + title: '{% trans "Reference" %}', + searchable: true, + sortable: true, + }, + { + field: 'quantity', + title: '{% trans "Quantity" %}', + sortable: true + }, + { + sortable: true, + switchable: false, + field: 'sub_part_detail.stock', + title: '{% trans "Available" %}', + formatter: function(value, row, index, field) { + return makeProgressBar( + value, + row.quantity * options.build_remaining, + { + id: `part-progress-${row.part}` + } + ); + }, + sorter: function(valA, valB, rowA, rowB) { + if (rowA.received == 0 && rowB.received == 0) { + return (rowA.quantity > rowB.quantity) ? 1 : -1; + } + + var progressA = parseFloat(rowA.sub_part_detail.stock) / (rowA.quantity * options.build_remaining); + var progressB = parseFloat(rowB.sub_part_detail.stock) / (rowB.quantity * options.build_remaining); + + return (progressA < progressB) ? 1 : -1; + } + }, + { + field: 'actions', + title: '{% trans "Actions" %}', + switchable: false, + formatter: function(value, row, index, field) { + // TODO - Add actions to build / order stock + } + } + ]; + + table.inventreeTable({ + url: '{% url "api-bom-list" %}', + showColumns: true, + name: 'build-parts', + sortable: true, + search: true, + rowStyle: function(row, index) { + var classes = []; + + // Shade rows differently if they are for different parent parts + if (row.part != options.part) { + classes.push('rowinherited'); + } + + if (row.validated) { + classes.push('rowvalid'); + } else { + classes.push('rowinvalid'); + } + + return { + classes: classes.join(' '), + }; + }, + formatNoMatches: function() { + return '{% trans "No BOM items found" %}'; + }, + clickToSelect: true, + queryParams: filters, + original: params, + columns: columns, + }); } \ No newline at end of file