diff --git a/InvenTree/build/templates/build/allocate.html b/InvenTree/build/templates/build/allocate.html
index 55ce042087..0e4a39cc29 100644
--- a/InvenTree/build/templates/build/allocate.html
+++ b/InvenTree/build/templates/build/allocate.html
@@ -201,8 +201,9 @@ InvenTree | Allocate Parts
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 }},
+ inventreeGet('/api/build/item/',
+ {
+ build: {{ build.id }},
},
{
success: function(data) {
diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py
index a7a176a7bf..35661d60fe 100644
--- a/InvenTree/order/models.py
+++ b/InvenTree/order/models.py
@@ -325,7 +325,7 @@ class SalesOrder(Order):
allocation.complete_allocation(user)
# Remove the allocation from the database once it has been 'fulfilled'
- if allocation.item.sales_order == self.order:
+ if allocation.item.sales_order == self:
allocation.delete()
else:
raise ValidationError("Could not complete order - allocation item not fulfilled")
diff --git a/InvenTree/order/templates/order/sales_order_base.html b/InvenTree/order/templates/order/sales_order_base.html
index 298b7441b1..6028157a22 100644
--- a/InvenTree/order/templates/order/sales_order_base.html
+++ b/InvenTree/order/templates/order/sales_order_base.html
@@ -10,7 +10,7 @@ InvenTree | {% trans "Sales Order" %}
{% endblock %}
{% block pre_content %}
-{% if not order.is_fully_allocated %}
+{% if order.status == SalesOrderStatus.PENDING and not order.is_fully_allocated %}
{% trans "This SalesOrder has not been fully allocated" %}
diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html
index f4a6425a97..9f21d8fc35 100644
--- a/InvenTree/order/templates/order/sales_order_detail.html
+++ b/InvenTree/order/templates/order/sales_order_detail.html
@@ -41,6 +41,116 @@ $("#new-so-line").click(function() {
});
});
+{% if order.status == SalesOrderStatus.PENDING %}
+function showAllocationSubTable(index, row, element) {
+ // Construct a table showing stock items which have been allocated against this line item
+
+ var html = ``;
+
+ element.html(html);
+
+ var lineItem = row;
+
+ var table = $(`#allocation-table-${row.pk}`);
+
+ table.bootstrapTable({
+ data: row.allocations,
+ showHeader: false,
+ columns: [
+ {
+ width: '50%',
+ field: 'allocated',
+ title: 'Quantity',
+ formatter: function(value, row, index, field) {
+ return renderLink(value, `/stock/item/${row.item}/`);
+ },
+ },
+ {
+ field: 'location_id',
+ title: 'Location',
+ formatter: function(value, row, index, field) {
+ return renderLink(row.location_path, `/stock/location/${row.location_id}/`);
+ },
+ },
+ {
+ field: 'buttons',
+ title: 'Actions',
+ formatter: function(value, row, index, field) {
+
+ var html = "";
+ var pk = row.pk;
+
+ {% if order.status == SalesOrderStatus.PENDING %}
+ html += makeIconButton('fa-edit', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
+ html += makeIconButton('fa-trash-alt', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
+ {% endif %}
+
+ html += "
";
+
+ return html;
+ },
+ },
+ ],
+ });
+
+ table.find(".button-allocation-edit").click(function() {
+
+ var pk = $(this).attr('pk');
+
+ launchModalForm(`/order/sales-order/allocation/${pk}/edit/`, {
+ success: reloadTable,
+ });
+ });
+
+ table.find(".button-allocation-delete").click(function() {
+ var pk = $(this).attr('pk');
+
+ launchModalForm(`/order/sales-order/allocation/${pk}/delete/`, {
+ success: reloadTable,
+ });
+ });
+}
+{% endif %}
+
+function showFulfilledSubTable(index, row, element) {
+ // Construct a table showing stock items which have been fulfilled against this line item
+
+ var id = `fulfilled-table-${row.pk}`;
+ var html = ``;
+
+ element.html(html);
+
+ var lineItem = row;
+
+ $(`#${id}`).bootstrapTable({
+ url: "{% url 'api-stock-list' %}",
+ queryParams: {
+ part: row.part,
+ sales_order: {{ order.id }},
+ },
+ showHeader: false,
+ columns: [
+ {
+ field: 'pk',
+ visible: false,
+ },
+ {
+ field: 'stock',
+ formatter: function(value, row) {
+ var text = '';
+ if (row.serial) {
+ text = `{% trans "Serial Number" %}: ${row.serial}`;
+ } else {
+ text = `{% trans "Quantity" %}: ${row.quantity}`;
+ }
+
+ return renderLink(text, `/stock/item/${row.pk}/`);
+ },
+ }
+ ],
+ });
+}
+
$("#so-lines-table").inventreeTable({
formatNoMatches: function() { return "No matching line items"; },
queryParams: {
@@ -51,78 +161,22 @@ $("#so-lines-table").inventreeTable({
uniqueId: 'pk',
url: "{% url 'api-so-line-list' %}",
onPostBody: setupCallbacks,
+ {% if order.status == SalesOrderStatus.PENDING or order.status == SalesOrderStatus.SHIPPED %}
detailViewByClick: true,
detailView: true,
detailFilter: function(index, row) {
+ {% if order.status == SalesOrderStatus.PENDING %}
return row.allocated > 0;
+ {% else %}
+ return row.fulfilled > 0;
+ {% endif %}
},
- detailFormatter: function(index, row, element) {
-
- var html = ``;
-
- element.html(html);
-
- var lineItem = row;
-
- var table = $(`#allocation-table-${row.pk}`);
-
- table.bootstrapTable({
- data: row.allocations,
- showHeader: false,
- columns: [
- {
- width: '50%',
- field: 'allocated',
- title: 'Quantity',
- formatter: function(value, row, index, field) {
- return renderLink(value, `/stock/item/${row.item}/`);
- },
- },
- {
- field: 'location_id',
- title: 'Location',
- formatter: function(value, row, index, field) {
- return renderLink(row.location_path, `/stock/location/${row.location_id}/`);
- },
- },
- {
- field: 'buttons',
- title: 'Actions',
- formatter: function(value, row, index, field) {
-
- var html = "";
- var pk = row.pk;
-
- {% if order.status == SalesOrderStatus.PENDING %}
- html += makeIconButton('fa-edit', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
- html += makeIconButton('fa-trash-alt', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
- {% endif %}
-
- html += "
";
-
- return html;
- },
- },
- ],
- });
-
- table.find(".button-allocation-edit").click(function() {
-
- var pk = $(this).attr('pk');
-
- launchModalForm(`/order/sales-order/allocation/${pk}/edit/`, {
- success: reloadTable,
- });
- });
-
- table.find(".button-allocation-delete").click(function() {
- var pk = $(this).attr('pk');
-
- launchModalForm(`/order/sales-order/allocation/${pk}/delete/`, {
- success: reloadTable,
- });
- });
- },
+ {% if order.status == SalesOrderStatus.PENDING %}
+ detailFormatter: showAllocationSubTable,
+ {% else %}
+ detailFormatter: showFulfilledSubTable,
+ {% endif %}
+ {% endif %}
columns: [
{
field: 'pk',
@@ -154,20 +208,36 @@ $("#so-lines-table").inventreeTable({
{
sortable: true,
field: 'allocated',
- title: 'Allocated',
+ {% if order.status == SalesOrderStatus.PENDING %}
+ title: '{% trans "Allocated" %}',
+ {% else %}
+ title: '{% trans "Fulfilled" %}',
+ {% endif %}
formatter: function(value, row, index, field) {
- return makeProgressBar(row.allocated, row.quantity, {
+ {% if order.status == SalesOrderStatus.PENDING %}
+ var quantity = row.allocated;
+ {% else %}
+ var quantity = row.fulfilled;
+ {% endif %}
+ return makeProgressBar(quantity, row.quantity, {
id: `order-line-progress-${row.pk}`,
});
},
sorter: function(valA, valB, rowA, rowB) {
+ {% if order.status == SalesOrderStatus.PENDING %}
+ var A = rowA.allocated;
+ var B = rowB.allocated;
+ {% else %}
+ var A = rowA.fulfilled;
+ var B = rowB.fulfilled;
+ {% endif %}
- if (rowA.allocated == 0 && rowB.allocated == 0) {
+ if (A == 0 && B == 0) {
return (rowA.quantity > rowB.quantity) ? 1 : -1;
}
- var progressA = parseFloat(rowA.allocated) / rowA.quantity;
- var progressB = parseFloat(rowB.allocated) / rowB.quantity;
+ var progressA = parseFloat(A) / rowA.quantity;
+ var progressB = parseFloat(B) / rowB.quantity;
return (progressA < progressB) ? 1 : -1;
}
@@ -176,6 +246,7 @@ $("#so-lines-table").inventreeTable({
field: 'notes',
title: 'Notes',
},
+ {% if order.status == SalesOrderStatus.PENDING %}
{
field: 'buttons',
formatter: function(value, row, index, field) {
@@ -184,7 +255,6 @@ $("#so-lines-table").inventreeTable({
var pk = row.pk;
- {% if order.status == SalesOrderStatus.PENDING %}
if (row.part) {
var part = row.part_detail;
@@ -202,13 +272,12 @@ $("#so-lines-table").inventreeTable({
html += makeIconButton('fa-edit', 'button-edit', pk, '{% trans "Edit line item" %}');
html += makeIconButton('fa-trash-alt', 'button-delete', pk, '{% trans "Delete line item " %}');
- {% endif %}
-
html += ``;
return html;
}
},
+ {% endif %}
],
});
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index 25c00ca65a..2261d8ad23 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -541,6 +541,10 @@ class StockItem(MPTTModel):
if self.build_order is not None:
return False
+ # Not 'in stock' if the status code makes it unavailable
+ if self.status in StockStatus.UNAVAILABLE_CODES:
+ return False
+
return True
@property
diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html
index 8428418392..bfbc737181 100644
--- a/InvenTree/stock/templates/stock/item_base.html
+++ b/InvenTree/stock/templates/stock/item_base.html
@@ -50,17 +50,7 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
{% block page_data %}
{% trans "Stock Item" %}
- {% if item.sales_order %}
-
- {% trans "Sold" $}
-
- {% elif item.build_order %}
-
- {% trans "Used in Build" %}
-
- {% else %}
{% stock_status_label item.status large=True %}
- {% endif %}