From 5e309a62f736a6b2b7977e042aad21b9ca3110b4 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 27 Apr 2020 10:31:38 +1000 Subject: [PATCH] Display "Fulfilled" items - Once a salesorder has been marked as "shipped" then the table is displayed differently - The sub rows show stock items which have been fulfilled against the sales order --- InvenTree/build/templates/build/allocate.html | 5 +- InvenTree/order/models.py | 2 +- .../templates/order/sales_order_base.html | 2 +- .../templates/order/sales_order_detail.html | 219 ++++++++++++------ InvenTree/stock/models.py | 4 + .../stock/templates/stock/item_base.html | 10 - 6 files changed, 153 insertions(+), 89 deletions(-) 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 %}