From 0bb0047fcd19582850b6300c7dfa63466285fa2e Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 4 Mar 2022 16:27:50 +1100 Subject: [PATCH] Add unit tests for the auto_allocate_stock method --- InvenTree/build/models.py | 7 +- InvenTree/build/serializers.py | 2 - InvenTree/build/test_build.py | 120 +++++++++++++++++++++++++++++++-- 3 files changed, 119 insertions(+), 10 deletions(-) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 15f1f252f2..e4a5013b7b 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -826,7 +826,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): self.save() @transaction.atomic - def auto_allocate_stock(self, user, **kwargs): + def auto_allocate_stock(self, **kwargs): """ Automatically allocate stock items against this build order, following a number of 'guidelines': @@ -847,9 +847,8 @@ class Build(MPTTModel, ReferenceIndexingMixin): # Get a list of all 'untracked' BOM items for bom_item in self.untracked_bom_items: - + variant_parts = bom_item.sub_part.get_descendants(include_self=False) - substitute_parts = [p for p in bom_item.substitutes.all()] unallocated_quantity = self.unallocated_quantity(bom_item) @@ -891,7 +890,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): return 2 else: return 3 - + available_stock = sorted(available_stock, key=stock_sort) if len(available_stock) == 0: diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 9085a25f30..ba43a079ff 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -746,11 +746,9 @@ class BuildAutoAllocationSerializer(serializers.Serializer): data = self.validated_data - request = self.context['request'] build = self.context['build'] build.auto_allocate_stock( - request.user, location=data.get('location', None), interchangeable=data['interchangeable'], substitutes=data['substitutes'], diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py index 116c705f61..4c48d27853 100644 --- a/InvenTree/build/test_build.py +++ b/InvenTree/build/test_build.py @@ -8,11 +8,11 @@ from django.db.utils import IntegrityError from InvenTree import status_codes as status from build.models import Build, BuildItem, get_next_build_number -from part.models import Part, BomItem +from part.models import Part, BomItem, BomItemSubstitute from stock.models import StockItem -class BuildTest(TestCase): +class BuildTestBase(TestCase): """ Run some tests to ensure that the Build model is working properly. """ @@ -107,13 +107,20 @@ class BuildTest(TestCase): ) # Create some stock items to assign to the build - self.stock_1_1 = StockItem.objects.create(part=self.sub_part_1, quantity=1000) + self.stock_1_1 = StockItem.objects.create(part=self.sub_part_1, quantity=3) self.stock_1_2 = StockItem.objects.create(part=self.sub_part_1, quantity=100) - self.stock_2_1 = StockItem.objects.create(part=self.sub_part_2, quantity=5000) + self.stock_2_1 = StockItem.objects.create(part=self.sub_part_2, quantity=5) + self.stock_2_2 = StockItem.objects.create(part=self.sub_part_2, quantity=5) + self.stock_2_2 = StockItem.objects.create(part=self.sub_part_2, quantity=5) + self.stock_2_2 = StockItem.objects.create(part=self.sub_part_2, quantity=5) + self.stock_2_2 = StockItem.objects.create(part=self.sub_part_2, quantity=5) self.stock_3_1 = StockItem.objects.create(part=self.sub_part_3, quantity=1000) + +class BuildTest(BuildTestBase): + def test_ref_int(self): """ Test the "integer reference" field used for natural sorting @@ -369,3 +376,108 @@ class BuildTest(TestCase): for output in outputs: self.assertFalse(output.is_building) + + +class AutoAllocationTests(BuildTestBase): + """ + Tests for auto allocating stock against a build order + """ + + def setUp(self): + + super().setUp() + + # Add a "substitute" part for bom_item_2 + alt_part = Part.objects.create( + name="alt part", + description="An alternative part!", + component=True, + ) + + BomItemSubstitute.objects.create( + bom_item=self.bom_item_2, + part=alt_part, + ) + + StockItem.objects.create( + part=alt_part, + quantity=500, + ) + + def test_auto_allocate(self): + """ + Run the 'auto-allocate' function. What do we expect to happen? + + There are two "untracked" parts: + - sub_part_1 (quantity 5 per BOM = 50 required total) / 103 in stock (2 items) + - sub_part_2 (quantity 3 per BOM = 30 required total) / 25 in stock (5 items) + + A "fully auto" allocation should allocate *all* of these stock items to the build + """ + + # No build item allocations have been made against the build + self.assertEqual(self.build.allocated_stock.count(), 0) + + self.assertFalse(self.build.are_untracked_parts_allocated()) + + # Stock is not interchangeable, nothing will happen + self.build.auto_allocate_stock( + interchangeable=False, + substitutes=False, + ) + + self.assertFalse(self.build.are_untracked_parts_allocated()) + + self.assertEqual(self.build.allocated_stock.count(), 0) + + self.assertFalse(self.build.is_bom_item_allocated(self.bom_item_1)) + self.assertFalse(self.build.is_bom_item_allocated(self.bom_item_2)) + + self.assertEqual(self.build.unallocated_quantity(self.bom_item_1), 50) + self.assertEqual(self.build.unallocated_quantity(self.bom_item_2), 30) + + # This time we expect stock to be allocated! + self.build.auto_allocate_stock( + interchangeable=True, + substitutes=False, + ) + + self.assertFalse(self.build.are_untracked_parts_allocated()) + + self.assertEqual(self.build.allocated_stock.count(), 7) + + self.assertTrue(self.build.is_bom_item_allocated(self.bom_item_1)) + self.assertFalse(self.build.is_bom_item_allocated(self.bom_item_2)) + + self.assertEqual(self.build.unallocated_quantity(self.bom_item_1), 0) + self.assertEqual(self.build.unallocated_quantity(self.bom_item_2), 5) + + # This time, allow substitue parts to be used! + self.build.auto_allocate_stock( + interchangeable=True, + substitutes=True, + ) + + # self.assertTrue(self.build.are_untracked_parts_allocated()) + + # self.assertEqual(self.build.allocated_stock.count(), 8) + self.assertEqual(self.build.unallocated_quantity(self.bom_item_1), 0) + self.assertEqual(self.build.unallocated_quantity(self.bom_item_2), 0) + + self.assertTrue(self.build.is_bom_item_allocated(self.bom_item_1)) + self.assertTrue(self.build.is_bom_item_allocated(self.bom_item_2)) + + def test_fully_auto(self): + """ + We should be able to auto-allocate against a build in a single go + """ + + self.build.auto_allocate_stock( + interchangeable=True, + substitutes=True + ) + + self.assertTrue(self.build.are_untracked_parts_allocated()) + + self.assertEqual(self.build.unallocated_quantity(self.bom_item_1), 0) + self.assertEqual(self.build.unallocated_quantity(self.bom_item_2), 0)