diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index f5dd359a0b..aaeb33676d 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -25,8 +25,8 @@ from InvenTree.status_codes import BuildStatus, StockStatus from InvenTree.fields import InvenTreeURLField from InvenTree.helpers import decimal2string -from stock.models import StockItem -from part.models import Part, BomItem +from stock import models as StockModels +from part import models as PartModels class Build(MPTTModel): @@ -465,7 +465,7 @@ class BuildItem(models.Model): if self.stock_item.serial and not self.quantity == 1: errors['quantity'] = _('Quantity must be 1 for serialized stock') - except (StockItem.DoesNotExist, Part.DoesNotExist): + except (StockModels.StockItem.DoesNotExist, PartModels.Part.DoesNotExist): pass if len(errors) > 0: diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index a03f11cbfa..fb0ee6e1f7 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -13,6 +13,7 @@ from django.urls import reverse from django.db import models, transaction from django.db.models import Sum +from django.db.models.functions import Coalesce from django.db.models import prefetch_related_objects from django.core.validators import MinValueValidator @@ -41,6 +42,8 @@ from InvenTree.helpers import decimal2string, normalize from InvenTree.status_codes import BuildStatus, StockStatus, PurchaseOrderStatus +from build import models as BuildModels +from order import models as OrderModels from company.models import SupplierPart from stock import models as StockModels @@ -502,8 +505,7 @@ class Part(models.Model): """ total = self.total_stock - - total -= self.allocation_count + total -= self.allocation_count() return max(total, 0) @@ -594,46 +596,48 @@ class Part(models.Model): return quantity - @property - def build_allocation(self): - """ Return list of builds to which this part is allocated + def build_order_allocations(self): + """ + Return all 'BuildItem' objects which allocate this part to Build objects """ - builds = [] + return BuildModels.BuildItem.objects.filter(stock_item__part__id=self.id) - for item in self.used_in.all().prefetch_related('part__builds'): - - active = item.part.active_builds - - for build in active: - b = {} - - b['build'] = build - b['quantity'] = item.quantity * build.quantity - - builds.append(b) - - prefetch_related_objects(builds, 'build_items') - - return builds - - @property - def allocated_build_count(self): - """ Return the total number of this part that are allocated for builds + def build_order_allocation_count(self): + """ + Return the total amount of this part allocated to build orders """ - return sum([a['quantity'] for a in self.build_allocation]) + query = self.build_order_allocations().aggregate(total=Coalesce(Sum('quantity'), 0)) + + return query['total'] + + def sales_order_allocations(self): + """ + Return all sales-order-allocation objects which allocate this part to a SalesOrder + """ + + return OrderModels.SalesOrderAllocation.objects.filter(item__part__id=self.id) + + def sales_order_allocation_count(self): + """ + Return the tutal quantity of this part allocated to sales orders + """ + + query = self.sales_order_allocations().aggregate(total=Coalesce(Sum('quantity'), 0)) + + return query['total'] + - @property def allocation_count(self): - """ Return true if any of this part is allocated: - - - To another build - - To a customer order + """ + Return the total quantity of stock allocated for this part, + against both build orders and sales orders. """ return sum([ - self.allocated_build_count, + self.build_order_allocation_count(), + self.sales_order_allocation_count(), ]) @property @@ -645,7 +649,7 @@ class Part(models.Model): - belongs_to is None """ - return self.stock_items.filter(StockModels.StockItem.IN_STOCK_FILTER).exclude(status__in=StockStatus.UNAVAILABLE_CODES) + return self.stock_items.filter(StockModels.StockItem.IN_STOCK_FILTER) @property def total_stock(self): diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 2261d8ad23..c9e4c75336 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -31,7 +31,7 @@ from InvenTree.models import InvenTreeTree from InvenTree.fields import InvenTreeURLField from part import models as PartModels -from order.models import PurchaseOrder, SalesOrder +from order import models as OrderModels class StockLocation(InvenTreeTree): @@ -134,7 +134,12 @@ class StockItem(MPTTModel): """ # A Query filter which will be re-used in multiple places to determine if a StockItem is actually "in stock" - IN_STOCK_FILTER = Q(sales_order=None, build_order=None, belongs_to=None) + IN_STOCK_FILTER = Q( + sales_order=None, + build_order=None, + belongs_to=None, + status__in=StockStatus.AVAILABLE_CODES + ) def save(self, *args, **kwargs): if not self.pk: @@ -393,7 +398,7 @@ class StockItem(MPTTModel): ) purchase_order = models.ForeignKey( - PurchaseOrder, + 'order.PurchaseOrder', on_delete=models.SET_NULL, verbose_name=_('Source Purchase Order'), related_name='stock_items', @@ -402,7 +407,7 @@ class StockItem(MPTTModel): ) sales_order = models.ForeignKey( - SalesOrder, + 'order.SalesOrder', on_delete=models.SET_NULL, verbose_name=_("Destination Sales Order"), related_name='stock_items',