diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index e843422f63..dd62a3e6da 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -473,8 +473,13 @@ class Build(MPTTModel): return self.getAllocatedQuantity(part) >= self.getRequiredQuantity(part) - def getRequiredQuantity(self, part): - """ Calculate the quantity of required to make this build. + def getRequiredQuantity(self, part, output=None): + """ + Calculate the quantity of required to make this build. + + Args: + part: The 'Part' archetype reference + output: A particular build output (StockItem) (or None to specify the entire build) """ try: @@ -483,27 +488,51 @@ class Build(MPTTModel): except PartModels.BomItem.DoesNotExist: q = 0 - return q * self.quantity + if output: + return q * output.quantity + else: + return q * self.quantity - def getAllocatedQuantity(self, part): - """ Calculate the total number of currently allocated to this build + def getAllocatedQuantity(self, part, output=None): + """ + Calculate the total number of currently allocated to this build. + + Args: + part: The 'Part' archetype reference + output: A particular build output (StockItem) (or None to specify the entire build) """ - allocated = BuildItem.objects.filter(build=self.id, stock_item__part=part.id).aggregate(q=Coalesce(Sum('quantity'), 0)) + allocations = BuildItem.objects.filter( + build=self.id, + stock_item__part=part.id + ) + + # Optionally, filter by the specified build output StockItem + if output is not None: + allocations = allocations.filter( + install_into=output + ) + + allocated = allocations.aggregate(q=Coalesce(Sum('quantity'), 0)) return allocated['q'] - def getUnallocatedQuantity(self, part): - """ Calculate the quantity of which still needs to be allocated to this build. + def getUnallocatedQuantity(self, part, output=None): + """ + Calculate the quantity of which still needs to be allocated to this build. Args: - Part - the part to be tested + part - the part to be tested + output - A particular build output (StockItem) (or None to specify the entire build) Returns: The remaining allocated quantity """ - return max(self.getRequiredQuantity(part) - self.getAllocatedQuantity(part), 0) + required = self.getRequiredQuantity(part, output=output) + allocated = self.getAllocatedQuantity(part, output=output) + + return max(required-allocated, 0) @property def required_parts(self): diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index ff1befcfc3..fdea5534fe 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -552,6 +552,8 @@ class BuildItemCreate(AjaxCreateView): """ pass + self.output = None + # If the output stock item is specified, hide the input field output_id = form['install_into'].value() @@ -591,11 +593,11 @@ class BuildItemCreate(AjaxCreateView): pass # Exclude StockItem objects which are already allocated to this build and part - stock_filter = stock_filter.exclude( - id__in=[ - item.stock_item.id for item in BuildItem.objects.filter(build=build_id, stock_item__part=part_id) - ] - ) + to_exclude = BuildItem.objects.filter(build=build_id, stock_item__part=part_id) + if self.output: + to_exclude = to_exclude.filter(install_into=self.output) + + stock_filter = stock_filter.exclude(id__in=[item.stock_item.id for item in to_exclude.all()]) except Part.DoesNotExist: self.part = None @@ -654,15 +656,26 @@ class BuildItemCreate(AjaxCreateView): except Build.DoesNotExist: pass + # If the output has been specified + if output_id: + try: + output = StockItem.objects.get(pk=output_id) + initials['install_into'] = output + except (ValueError, StockItem.DoesNotExist): + pass + + # Work out how much stock is required + if build and part: + required_quantity = build.getUnallocatedQuantity(part, output=output) + else: + required_quantity = None + quantity = self.request.GET.get('quantity', None) if quantity is not None: quantity = float(quantity) - - if quantity is None: - # Work out how many parts remain to be alloacted for the build - if part: - quantity = build.getUnallocatedQuantity(part) + elif required_quantity is not None: + quantity = required_quantity item_id = self.get_param('item') @@ -686,14 +699,6 @@ class BuildItemCreate(AjaxCreateView): else: quantity = min(quantity, item.unallocated_quantity()) - # If the output has been specified - if output_id: - try: - output = StockItem.objects.get(pk=output_id) - initials['install_into'] = output - except (ValueError, StockItem.DoesNotExist): - pass - if quantity is not None: initials['quantity'] = quantity diff --git a/InvenTree/part/test_bom_item.py b/InvenTree/part/test_bom_item.py index 91aa95c17c..a518ca1ddc 100644 --- a/InvenTree/part/test_bom_item.py +++ b/InvenTree/part/test_bom_item.py @@ -28,10 +28,12 @@ class BomItemTest(TestCase): self.assertEqual(self.bob.bom_count, 4) def test_in_bom(self): - parts = self.bob.required_parts() + parts = self.bob.getRequiredParts() self.assertIn(self.orphan, parts) + # TODO: Tests for multi-level BOMs + def test_used_in(self): self.assertEqual(self.bob.used_in_count, 0) self.assertEqual(self.orphan.used_in_count, 1)