diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 96b886e15c..b2e7145d52 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -846,6 +846,12 @@ class SOAllocationList(generics.ListAPIView): if order is not None: queryset = queryset.filter(line__order=order) + # Filter by "stock item" + item = params.get('item', params.get('stock_item', None)) + + if item is not None: + queryset = queryset.filter(item=item) + # Filter by "outstanding" order status outstanding = params.get('outstanding', None) @@ -865,7 +871,6 @@ class SOAllocationList(generics.ListAPIView): # Default filterable fields filter_fields = [ - 'item', ] diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index fb92293b3b..9082285bdb 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -37,6 +37,23 @@ +
+
+
+

{% trans "Part Stock Allocations" %}

+ {% include "spacer.html" %} +
+
+
+
+
+ {% include "filter_list.html" with id="allocations" %} +
+
+
+
+
+
@@ -631,6 +648,19 @@ {% endif %} }); + // Load the "allocations" tab + onPanelLoad('allocations', function() { + + loadStockAllocationTable( + $("#part-allocation-table"), + { + params: { + part: {{ part.pk }}, + }, + } + ); + }); + // Load the "related parts" tab onPanelLoad("related-parts", function() { diff --git a/InvenTree/part/templates/part/part_sidebar.html b/InvenTree/part/templates/part/part_sidebar.html index f4e59af865..a8f57aa7b0 100644 --- a/InvenTree/part/templates/part/part_sidebar.html +++ b/InvenTree/part/templates/part/part_sidebar.html @@ -27,6 +27,10 @@ {% endif %} {% trans "Pricing" as text %} {% include "sidebar_item.html" with label="pricing" text=text icon="fa-dollar-sign" %} +{% if part.salable or part.component %} +{% trans "Allocations" as text %} +{% include "sidebar_item.html" with label="allocations" text=text icon="fa-bookmark" %} +{% endif %} {% if part.purchaseable and roles.purchase_order.view %} {% trans "Suppliers" as text %} {% include "sidebar_item.html" with label="suppliers" text=text icon="fa-building" %} diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index 9cc6d85aeb..5c3f94f72f 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -43,9 +43,26 @@
+
+
+

{% trans "Stock Item Allocations" %}

+ {% include "spacer.html" %} +
+
+
+
+ {% include "filter_list.html" with id="allocations" %} +
+
+
+
+ +
+

{% trans "Child Stock Items" %}

+ {% include "spacer.html" %}
{% if item.child_count > 0 %} @@ -151,6 +168,19 @@ {% block js_ready %} {{ block.super }} + // Load the "allocations" tab + onPanelLoad('allocations', function() { + + loadStockAllocationTable( + $("#stock-allocation-table"), + { + params: { + stock_item: {{ item.pk }}, + }, + } + ); + }); + $('#stock-item-install').click(function() { launchModalForm( diff --git a/InvenTree/stock/templates/stock/stock_sidebar.html b/InvenTree/stock/templates/stock/stock_sidebar.html index 8161251ad7..3032bab5bf 100644 --- a/InvenTree/stock/templates/stock/stock_sidebar.html +++ b/InvenTree/stock/templates/stock/stock_sidebar.html @@ -4,6 +4,10 @@ {% trans "Stock Tracking" as text %} {% include "sidebar_item.html" with label='history' text=text icon="fa-history" %} +{% if item.part.salable or item.part.component %} +{% trans "Allocations" as text %} +{% include "sidebar_item.html" with label="allocations" text=text icon="fa-bookmark" %} +{% endif %} {% if item.part.trackable %} {% trans "Test Data" as text %} {% include "sidebar_item.html" with label='test-data' text=text icon="fa-vial" %} diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index ded4c48883..d9fee44297 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -47,6 +47,7 @@ exportStock, findStockItemBySerialNumber, loadInstalledInTable, + loadStockAllocationTable, loadStockLocationTable, loadStockTable, loadStockTestResultsTable, @@ -2203,6 +2204,125 @@ function loadStockTable(table, options) { } +/* + * Display a table of allocated stock, for either a part or stock item + * Allocations are displayed for: + * + * a) Sales Orders + * b) Build Orders + */ +function loadStockAllocationTable(table, options={}) { + + var params = options.params || {}; + + params.build_detail = true; + + var filterListElement = options.filterList || '#filter-list-allocations'; + + var filters = {}; + + var filterKey = options.filterKey || options.name || 'allocations'; + + var original = {}; + + for (var k in params) { + original[k] = params[k]; + filters[k] = params[k]; + } + + setupFilterList(filterKey, table, filterListElement); + + /* + * We have two separate API queries to make here: + * a) Build Order Allocations + * b) Sales Order Allocations + * + * We will let the call to inventreeTable take care of build orders, + * and then load sales orders after that. + */ + table.inventreeTable({ + url: '{% url "api-build-item-list" %}', + name: 'allocations', + original: original, + method: 'get', + queryParams: filters, + sidePagination: 'client', + showColumns: false, + onLoadSuccess: function(tableData) { + + var query_params = params; + query_params.order_detail = true; + + // Load sales order allocation data + inventreeGet('{% url "api-so-allocation-list" %}', query_params, { + success: function(data) { + // Update table to include sales order data + $(table).bootstrapTable('append', data); + } + }); + }, + columns: [ + { + field: 'order', + title: '{% trans "Order" %}', + formatter: function(value, row) { + + var html = ''; + + if (row.build) { + html = renderLink( + global_settings.BUILDORDER_REFERENCE_PREFIX + row.build_detail.reference, + `/build/${row.build}/` + ); + + html += makeIconBadge('fa-tools', '{% trans "Build Order" %}'); + } else if (row.order) { + html += renderLink( + global_settings.SALESORDER_REFERENCE_PREFIX + row.order, + `/order/so/${row.order}/` + ); + + html += makeIconBadge('fa-truck', '{% trans "Sales Order" %}'); + } else { + return '-'; + } + + return html; + } + }, + { + field: 'status', + title: '{% trans "Order Status" %}', + formatter: function(value, row) { + if (row.build) { + return buildStatusDisplay(row.build_detail.status); + } else if (row.order) { + return salesOrderStatusDisplay(row.order_detail.status); + } else { + return '-'; + } + } + }, + { + field: 'quantity', + title: '{% trans "Allocated Quantity" %}', + formatter: function(value, row) { + var text = value; + var pk = row.stock_item || row.item; + + if (pk) { + var url = `/stock/item/${pk}/`; + return renderLink(text, url); + } else { + return value; + } + } + }, + ] + }); +} + + /* * Display a table of stock locations */ @@ -2252,7 +2372,6 @@ function loadStockLocationTable(table, options) { method: 'get', url: options.url || '{% url "api-location-list" %}', queryParams: filters, - sidePagination: 'server', name: 'location', original: original, showColumns: true,