mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
Fix build item over-allocation checks (#8235)
This commit is contained in:
parent
7e7cfb8ee1
commit
a1024f1a67
@ -1606,12 +1606,19 @@ class BuildItem(InvenTree.models.InvenTreeMetadataModel):
|
|||||||
'quantity': _(f'Allocated quantity ({q}) must not exceed available stock quantity ({a})')
|
'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)
|
available = decimal.Decimal(self.stock_item.quantity)
|
||||||
allocated = decimal.Decimal(self.stock_item.allocation_count())
|
|
||||||
quantity = decimal.Decimal(self.quantity)
|
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({
|
raise ValidationError({
|
||||||
'quantity': _('Stock item is over-allocated')
|
'quantity': _('Stock item is over-allocated')
|
||||||
})
|
})
|
||||||
|
@ -993,6 +993,65 @@ class BuildAllocationTest(BuildAPITest):
|
|||||||
expected_code=201,
|
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):
|
class BuildOverallocationTest(BuildAPITest):
|
||||||
"""Unit tests for over allocation of stock items against a build order.
|
"""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
|
return self.sales_order_allocations.count() > 0
|
||||||
|
|
||||||
def build_allocation_count(self):
|
def build_allocation_count(self, **kwargs):
|
||||||
"""Return the total quantity allocated to builds."""
|
"""Return the total quantity allocated to builds, with optional filters."""
|
||||||
query = self.allocations.aggregate(q=Coalesce(Sum('quantity'), Decimal(0)))
|
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']
|
total = query['q']
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user