Skip to content

Commit

Permalink
Fix build item over-allocation checks
Browse files Browse the repository at this point in the history
  • Loading branch information
fuzeman committed Oct 3, 2024
1 parent 5759b60 commit a5028d2
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 6 deletions.
13 changes: 10 additions & 3 deletions src/backend/InvenTree/build/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1606,12 +1606,19 @@ def clean(self):
'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')
})
Expand Down
59 changes: 59 additions & 0 deletions src/backend/InvenTree/build/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,65 @@ def test_fractional_allocation(self):
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.
Expand Down
14 changes: 11 additions & 3 deletions src/backend/InvenTree/stock/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1190,9 +1190,17 @@ def is_allocated(self):

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']

Expand Down

0 comments on commit a5028d2

Please sign in to comment.