Bug fixes for BuildItemCreate view:

- Add option to calculate required quantity against a particular build output, not just the build
This commit is contained in:
Oliver Walters 2020-10-23 23:09:22 +11:00
parent 076d5c4f7f
commit 0752df26dc
3 changed files with 65 additions and 29 deletions

View File

@ -473,8 +473,13 @@ class Build(MPTTModel):
return self.getAllocatedQuantity(part) >= self.getRequiredQuantity(part)
def getRequiredQuantity(self, part):
""" Calculate the quantity of <part> required to make this build.
def getRequiredQuantity(self, part, output=None):
"""
Calculate the quantity of <part> 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
if output:
return q * output.quantity
else:
return q * self.quantity
def getAllocatedQuantity(self, part):
""" Calculate the total number of <part> currently allocated to this build
def getAllocatedQuantity(self, part, output=None):
"""
Calculate the total number of <part> 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 <part> which still needs to be allocated to this build.
def getUnallocatedQuantity(self, part, output=None):
"""
Calculate the quantity of <part> 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):

View File

@ -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

View File

@ -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)