mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 21:25:42 +00:00 
			
		
		
		
	Fix build item over-allocation checks (#8235)
This commit is contained in:
		| @@ -1606,12 +1606,19 @@ class BuildItem(InvenTree.models.InvenTreeMetadataModel): | ||||
|                     'quantity': _(f'Allocated quantity ({q}) must not exceed available stock quantity ({a})') | ||||
|                 }) | ||||
|  | ||||
|             # Allocated quantity cannot cause the stock item to be over-allocated | ||||
|             # Ensure that we do not 'over allocate' a stock item | ||||
|             available = decimal.Decimal(self.stock_item.quantity) | ||||
|             allocated = decimal.Decimal(self.stock_item.allocation_count()) | ||||
|             quantity = decimal.Decimal(self.quantity) | ||||
|             build_allocation_count = decimal.Decimal(self.stock_item.build_allocation_count( | ||||
|                 exclude_allocations={'pk': self.pk} | ||||
|             )) | ||||
|             sales_allocation_count = decimal.Decimal(self.stock_item.sales_order_allocation_count()) | ||||
|  | ||||
|             if available - allocated + quantity < quantity: | ||||
|             total_allocation = ( | ||||
|                 build_allocation_count + sales_allocation_count + quantity | ||||
|             ) | ||||
|  | ||||
|             if total_allocation > available: | ||||
|                 raise ValidationError({ | ||||
|                     'quantity': _('Stock item is over-allocated') | ||||
|                 }) | ||||
|   | ||||
| @@ -993,6 +993,65 @@ class BuildAllocationTest(BuildAPITest): | ||||
|             expected_code=201, | ||||
|         ) | ||||
|  | ||||
| class BuildItemTest(BuildAPITest): | ||||
|     """Unit tests for build items. | ||||
|  | ||||
|     For this test, we will be using Build ID=1; | ||||
|  | ||||
|     - This points to Part 100 (see fixture data in part.yaml) | ||||
|     - This Part already has a BOM with 4 items (see fixture data in bom.yaml) | ||||
|     - There are no BomItem objects yet created for this build | ||||
|     """ | ||||
|  | ||||
|     def setUp(self): | ||||
|         """Basic operation as part of test suite setup""" | ||||
|         super().setUp() | ||||
|  | ||||
|         self.assignRole('build.add') | ||||
|         self.assignRole('build.change') | ||||
|  | ||||
|         self.build = Build.objects.get(pk=1) | ||||
|  | ||||
|         # Regenerate BuildLine objects | ||||
|         self.build.create_build_line_items() | ||||
|  | ||||
|         # Record number of build items which exist at the start of each test | ||||
|         self.n = BuildItem.objects.count() | ||||
|  | ||||
|     def test_update_overallocated(self): | ||||
|         """Test update of overallocated stock items.""" | ||||
|  | ||||
|         si = StockItem.objects.get(pk=2) | ||||
|  | ||||
|         # Find line item | ||||
|         line = self.build.build_lines.all().filter(bom_item__sub_part=si.part).first() | ||||
|  | ||||
|         # Set initial stock item quantity | ||||
|         si.quantity = 100 | ||||
|         si.save() | ||||
|  | ||||
|         # Create build item | ||||
|         bi = BuildItem( | ||||
|             build_line=line, | ||||
|             stock_item=si, | ||||
|             quantity=100 | ||||
|         ) | ||||
|         bi.save() | ||||
|  | ||||
|         # Reduce stock item quantity | ||||
|         si.quantity = 50 | ||||
|         si.save() | ||||
|  | ||||
|         # Reduce build item quantity | ||||
|         url = reverse('api-build-item-detail', kwargs={'pk': bi.pk}) | ||||
|  | ||||
|         self.patch( | ||||
|             url, | ||||
|             { | ||||
|                 "quantity": 50, | ||||
|             }, | ||||
|             expected_code=200, | ||||
|         ) | ||||
|  | ||||
| class BuildOverallocationTest(BuildAPITest): | ||||
|     """Unit tests for over allocation of stock items against a build order. | ||||
|   | ||||
| @@ -1190,9 +1190,17 @@ class StockItem( | ||||
|  | ||||
|         return self.sales_order_allocations.count() > 0 | ||||
|  | ||||
|     def build_allocation_count(self): | ||||
|         """Return the total quantity allocated to builds.""" | ||||
|         query = self.allocations.aggregate(q=Coalesce(Sum('quantity'), Decimal(0))) | ||||
|     def build_allocation_count(self, **kwargs): | ||||
|         """Return the total quantity allocated to builds, with optional filters.""" | ||||
|         query = self.allocations.all() | ||||
|  | ||||
|         if filter_allocations := kwargs.get('filter_allocations'): | ||||
|             query = query.filter(**filter_allocations) | ||||
|  | ||||
|         if exclude_allocations := kwargs.get('exclude_allocations'): | ||||
|             query = query.exclude(**exclude_allocations) | ||||
|  | ||||
|         query = query.aggregate(q=Coalesce(Sum('quantity'), Decimal(0))) | ||||
|  | ||||
|         total = query['q'] | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user