From 28c9c80f540d0c05bd49f383798e58fd81f16cd8 Mon Sep 17 00:00:00 2001
From: Oliver Walters <oliver.henry.walters@gmail.com>
Date: Wed, 17 Feb 2021 10:57:17 +1100
Subject: [PATCH] Calculate quantity required for sales orders

- Cache data going to part detail view
---
 InvenTree/part/models.py                     | 38 ++++++++++++++++++-
 InvenTree/part/templates/part/part_base.html | 40 ++++++++------------
 InvenTree/part/views.py                      | 10 +++++
 3 files changed, 63 insertions(+), 25 deletions(-)

diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py
index 85e7c051bf..6041c2827e 100644
--- a/InvenTree/part/models.py
+++ b/InvenTree/part/models.py
@@ -41,7 +41,7 @@ from InvenTree.models import InvenTreeTree, InvenTreeAttachment
 from InvenTree.fields import InvenTreeURLField
 from InvenTree.helpers import decimal2string, normalize
 
-from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus
+from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus, SalesOrderStatus
 
 from build import models as BuildModels
 from order import models as OrderModels
@@ -940,6 +940,42 @@ class Part(MPTTModel):
         
         return quantity
 
+    def requiring_sales_orders(self):
+        """
+        Return a list of sales orders which require this part
+        """
+
+        orders = set()
+
+        # Get a list of line items for open orders which match this part
+        open_lines = OrderModels.SalesOrderLineItem.objects.filter(
+            order__status__in=SalesOrderStatus.OPEN,
+            part=self
+        )
+
+        for line in open_lines:
+            orders.add(line.order)
+
+        return orders
+
+    def required_sales_order_quantity(self):
+        """
+        Return the quantity of this part required for active sales orders
+        """
+
+        # Get a list of line items for open orders which match this part
+        open_lines = OrderModels.SalesOrderLineItem.objects.filter(
+            order__status__in=SalesOrderStatus.OPEN,
+            part=self
+        )
+
+        quantity = 0
+
+        for line in open_lines:
+            quantity += line.quantity
+
+        return quantity
+
     @property
     def quantity_to_order(self):
         """ Return the quantity needing to be ordered for this part. """
diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html
index 0ddb5d2180..24c0c0b234 100644
--- a/InvenTree/part/templates/part/part_base.html
+++ b/InvenTree/part/templates/part/part_base.html
@@ -126,28 +126,6 @@
             <td>{% trans "In Stock" %}</td>
             <td>{% include "part/stock_count.html" %}</td>
         </tr>
-        {% if not part.is_template %}
-        {% if part.required_build_order_quantity > 0 %}
-        <tr>
-            <td><span class='fas fa-hand-holding'></span></td>
-            <td>{% trans "Required for Build Orders" %}</td>
-            <td>{% decimal part.required_build_order_quantity %}</td>
-        </tr>
-        {% endif %}
-        {% if part.build_order_allocation_count > 0 %}
-        <tr>
-            <td><span class='fas fa-dolly'></span></td>
-            <td>{% trans "Allocated to Build Orders" %}</td>
-            <td>{% decimal part.build_order_allocation_count %}</td>
-        </tr>
-        {% endif %}
-        {% if part.sales_order_allocation_count > 0 %}
-        <tr>
-            <td><span class='fas fa-dolly'></span></td>
-            <td>{% trans "Allocated to Sales Orders" %}</td>
-            <td>{% decimal part.sales_order_allocation_count %}</td>
-        </tr>
-        {% endif %}
         {% if part.on_order > 0 %}
         <tr>
             <td><span class='fas fa-shopping-cart'></span></td>
@@ -155,7 +133,21 @@
             <td>{% decimal part.on_order %}</td>
         </tr>
         {% endif %}
+        {% if required > 0 %}
+        <tr>
+            <td><span class='fas fa-clipboard-list'></span></td>
+            <td>{% trans "Required for Orders" %}</td>
+            <td>{% decimal required %}
+        </tr>
         {% endif %}
+        {% if allocated > 0 %}
+        <tr>
+            <td><span class='fas fa-dolly'></span></td>
+            <td>{% trans "Allocated to Orders" %}</td>
+            <td>{% decimal allocated %}</td>
+        </tr>
+        {% endif %}
+        
         {% if not part.is_template %}
         {% if part.assembly %}
         <tr>
@@ -169,11 +161,11 @@
             <td>{% trans "Can Build" %}</td>
             <td>{% decimal part.can_build %}</td>
         </tr>
-        {% if part.quantity_being_built > 0 %}
+        {% if quantity_being_built > 0 %}
         <tr>
             <td></td>
             <td>{% trans "Underway" %}</td>
-            <td>{% decimal part.quantity_being_built %}</td>
+            <td>{% decimal quantity_being_built %}</td>
         </tr>
         {% endif %}
         {% endif %}
diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index 8f1c86fdfc..42c48c8cab 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -792,6 +792,16 @@ class PartDetail(InvenTreeRoleMixin, DetailView):
         context['starred'] = part.isStarredBy(self.request.user)
         context['disabled'] = not part.active
 
+        # Pre-calculate complex queries so they only need to be performed once
+        context['required_build_order_quantity'] = part.required_build_order_quantity()
+        context['allocated_build_order_quantity'] = part.build_order_allocation_count()
+
+        context['required_sales_order_quantity'] = part.required_sales_order_quantity()
+        context['allocated_sales_order_quantity'] = part.sales_order_allocation_count()
+
+        context['required'] = context['required_build_order_quantity'] + context['required_sales_order_quantity']
+        context['allocated'] = context['allocated_build_order_quantity'] + context['allocated_sales_order_quantity']
+
         return context