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