mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 21:25:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			227 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| from django.test import TestCase
 | |
| 
 | |
| from django.core.exceptions import ValidationError
 | |
| from django.db.utils import IntegrityError
 | |
| 
 | |
| from build.models import Build, BuildItem
 | |
| from stock.models import StockItem
 | |
| from part.models import Part, BomItem
 | |
| from InvenTree import status_codes as status
 | |
| 
 | |
| from InvenTree.helpers import extract_serial_numbers
 | |
| 
 | |
| 
 | |
| class BuildTest(TestCase):
 | |
|     """
 | |
|     Run some tests to ensure that the Build model is working properly.
 | |
|     """
 | |
| 
 | |
|     def setUp(self):
 | |
|         """
 | |
|         Initialize data to use for these tests.
 | |
|         """
 | |
| 
 | |
|         # Create a base "Part"
 | |
|         self.assembly = Part.objects.create(
 | |
|             name="An assembled part",
 | |
|             description="Why does it matter what my description is?",
 | |
|             assembly=True,
 | |
|             trackable=True,
 | |
|         )
 | |
| 
 | |
|         self.sub_part_1 = Part.objects.create(
 | |
|             name="Widget A",
 | |
|             description="A widget",
 | |
|             component=True
 | |
|         )
 | |
| 
 | |
|         self.sub_part_2 = Part.objects.create(
 | |
|             name="Widget B",
 | |
|             description="A widget",
 | |
|             component=True
 | |
|         )
 | |
| 
 | |
|         # Create BOM item links for the parts
 | |
|         BomItem.objects.create(
 | |
|             part=self.assembly,
 | |
|             sub_part=self.sub_part_1,
 | |
|             quantity=10
 | |
|         )
 | |
| 
 | |
|         BomItem.objects.create(
 | |
|             part=self.assembly,
 | |
|             sub_part=self.sub_part_2,
 | |
|             quantity=25
 | |
|         )
 | |
| 
 | |
|         # Create a "Build" object to make 10x objects
 | |
|         self.build = Build.objects.create(
 | |
|             title="This is a build",
 | |
|             part=self.assembly,
 | |
|             quantity=10
 | |
|         )
 | |
| 
 | |
|         # 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_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)
 | |
| 
 | |
|     def test_init(self):
 | |
|         # Perform some basic tests before we start the ball rolling
 | |
| 
 | |
|         self.assertEqual(StockItem.objects.count(), 3)
 | |
|         self.assertEqual(self.build.status, status.BuildStatus.PENDING)
 | |
|         self.assertFalse(self.build.isFullyAllocated())
 | |
| 
 | |
|         self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_1))
 | |
|         self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_2))
 | |
| 
 | |
|         self.assertEqual(self.build.getRequiredQuantity(self.sub_part_1), 100)
 | |
|         self.assertEqual(self.build.getRequiredQuantity(self.sub_part_2), 250)
 | |
| 
 | |
|         self.assertFalse(self.build.is_complete)
 | |
| 
 | |
|         # Delete some stock and see if the build can still be completed
 | |
|         self.stock_2_1.delete()
 | |
| 
 | |
|     def test_build_item_clean(self):
 | |
|         # Ensure that dodgy BuildItem objects cannot be created
 | |
| 
 | |
|         stock = StockItem.objects.create(part=self.assembly, quantity=99)
 | |
| 
 | |
|         # Create a BuiltItem which points to an invalid StockItem
 | |
|         b = BuildItem(stock_item=stock, build=self.build, quantity=10)
 | |
|         
 | |
|         with self.assertRaises(ValidationError):
 | |
|             b.clean()
 | |
| 
 | |
|         # Create a BuildItem which has too much stock assigned
 | |
|         b = BuildItem(stock_item=self.stock_1_1, build=self.build, quantity=9999999)
 | |
| 
 | |
|         with self.assertRaises(ValidationError):
 | |
|             b.clean()
 | |
| 
 | |
|         # Negative stock? Not on my watch!
 | |
|         b = BuildItem(stock_item=self.stock_1_1, build=self.build, quantity=-99)
 | |
| 
 | |
|         with self.assertRaises(ValidationError):
 | |
|             b.clean()
 | |
| 
 | |
|     def test_duplicate_bom_line(self):
 | |
|         # Try to add a duplicate BOM item - it should fail!
 | |
| 
 | |
|         with self.assertRaises(IntegrityError):
 | |
|             BomItem.objects.create(
 | |
|                 part=self.assembly,
 | |
|                 sub_part=self.sub_part_1,
 | |
|                 quantity=99
 | |
|             )
 | |
| 
 | |
|     def allocate_stock(self, q11, q12, q21):
 | |
|         # Assign stock to this build
 | |
| 
 | |
|         BuildItem.objects.create(
 | |
|             build=self.build,
 | |
|             stock_item=self.stock_1_1,
 | |
|             quantity=q11
 | |
|         )
 | |
| 
 | |
|         BuildItem.objects.create(
 | |
|             build=self.build,
 | |
|             stock_item=self.stock_1_2,
 | |
|             quantity=q12
 | |
|         )
 | |
| 
 | |
|         BuildItem.objects.create(
 | |
|             build=self.build,
 | |
|             stock_item=self.stock_2_1,
 | |
|             quantity=q21
 | |
|         )
 | |
| 
 | |
|         # Attempt to create another identical BuildItem
 | |
|         b = BuildItem(
 | |
|             build=self.build,
 | |
|             stock_item=self.stock_2_1,
 | |
|             quantity=q21
 | |
|         )
 | |
| 
 | |
|         with self.assertRaises(ValidationError):
 | |
|             b.clean()
 | |
| 
 | |
|         self.assertEqual(BuildItem.objects.count(), 3)
 | |
| 
 | |
|     def test_partial_allocation(self):
 | |
| 
 | |
|         self.allocate_stock(50, 50, 200)
 | |
| 
 | |
|         self.assertFalse(self.build.isFullyAllocated())
 | |
|         self.assertTrue(self.build.isPartFullyAllocated(self.sub_part_1))
 | |
|         self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_2))
 | |
| 
 | |
|         self.build.unallocateStock()
 | |
|         self.assertEqual(BuildItem.objects.count(), 0)
 | |
| 
 | |
|     def test_auto_allocate(self):
 | |
| 
 | |
|         allocations = self.build.getAutoAllocations()
 | |
| 
 | |
|         self.assertEqual(len(allocations), 1)
 | |
| 
 | |
|         self.build.auto_allocate()
 | |
|         self.assertEqual(BuildItem.objects.count(), 1)
 | |
|         self.assertTrue(self.build.isPartFullyAllocated(self.sub_part_2))
 | |
| 
 | |
|     def test_cancel(self):
 | |
| 
 | |
|         self.allocate_stock(50, 50, 200)
 | |
|         self.build.cancelBuild(None)
 | |
| 
 | |
|         self.assertEqual(BuildItem.objects.count(), 0)
 | |
| 
 | |
|     def test_complete(self):
 | |
| 
 | |
|         self.allocate_stock(50, 50, 250)
 | |
| 
 | |
|         self.assertTrue(self.build.isFullyAllocated())
 | |
| 
 | |
|         # Generate some serial numbers!
 | |
|         serials = extract_serial_numbers("1-10", 10)
 | |
| 
 | |
|         self.build.completeBuild(None, serials, None)
 | |
| 
 | |
|         self.assertEqual(self.build.status, status.BuildStatus.COMPLETE)
 | |
| 
 | |
|         # the original BuildItem objects should have been deleted!
 | |
|         self.assertEqual(BuildItem.objects.count(), 0)
 | |
| 
 | |
|         # New stock items should have been created!
 | |
|         # - Ten for the build output (as the part was serialized)
 | |
|         # - Three for the split items assigned to the build
 | |
|         self.assertEqual(StockItem.objects.count(), 16)
 | |
| 
 | |
|         A = StockItem.objects.get(pk=self.stock_1_1.pk)
 | |
|         B = StockItem.objects.get(pk=self.stock_1_2.pk)
 | |
|         C = StockItem.objects.get(pk=self.stock_2_1.pk)
 | |
| 
 | |
|         # Stock should have been subtracted from the original items
 | |
|         self.assertEqual(A.quantity, 950)
 | |
|         self.assertEqual(B.quantity, 50)
 | |
|         self.assertEqual(C.quantity, 4750)
 | |
| 
 | |
|         # New stock items should have also been allocated to the build
 | |
|         allocated = StockItem.objects.filter(build_order=self.build)
 | |
| 
 | |
|         self.assertEqual(allocated.count(), 3)
 | |
| 
 | |
|         q = sum([item.quantity for item in allocated.all()])
 | |
| 
 | |
|         self.assertEqual(q, 350)
 | |
|         
 | |
|         # And 10 new stock items created for the build output
 | |
|         outputs = StockItem.objects.filter(build=self.build)
 | |
| 
 | |
|         self.assertEqual(outputs.count(), 10)
 |