[bug] Quantity fixes (#12375)
- Ensure valid quantity values on stock operations - Regression testing
This commit is contained in:
parent
b64182b565
commit
d9b9a87893
|
|
@ -1376,9 +1376,16 @@ class StockItem(
|
|||
user: User that performed the action
|
||||
notes: Notes field
|
||||
"""
|
||||
if quantity is None:
|
||||
if quantity is None or self.serialized:
|
||||
quantity = self.quantity
|
||||
|
||||
# Cannot allocate more than the available quantity
|
||||
# (also ensures the recorded history matches the actual allocation)
|
||||
quantity = min(quantity, self.quantity)
|
||||
|
||||
if quantity <= 0:
|
||||
raise ValidationError({'quantity': _('Quantity must be greater than zero')})
|
||||
|
||||
if quantity >= self.quantity:
|
||||
item = self
|
||||
else:
|
||||
|
|
@ -1708,6 +1715,18 @@ class StockItem(
|
|||
notes: Any notes associated with the operation
|
||||
build: The BuildOrder to associate with the operation (optional)
|
||||
"""
|
||||
try:
|
||||
quantity = Decimal(quantity)
|
||||
except (InvalidOperation, TypeError):
|
||||
raise ValidationError({'quantity': _('Invalid quantity value')})
|
||||
|
||||
if quantity <= 0:
|
||||
raise ValidationError({'quantity': _('Quantity must be greater than zero')})
|
||||
|
||||
# Cannot install more than the available quantity
|
||||
# (also ensures the recorded history matches the actual installation)
|
||||
quantity = min(quantity, other_item.quantity)
|
||||
|
||||
# If the quantity is less than the stock item, split the stock!
|
||||
stock_item = other_item.splitStock(quantity, None, user)
|
||||
|
||||
|
|
@ -3065,6 +3084,10 @@ class StockItem(
|
|||
except InvalidOperation:
|
||||
return False
|
||||
|
||||
# Cannot remove more than the available quantity
|
||||
# (also ensures the recorded history matches the actual removal)
|
||||
quantity = min(quantity, self.quantity)
|
||||
|
||||
if quantity <= 0:
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -351,6 +351,92 @@ class StockTest(StockTestBase):
|
|||
stock.splitStock(stock.quantity, None, self.user)
|
||||
self.assertEqual(StockItem.objects.filter(part=3).count(), n + 1)
|
||||
|
||||
def test_over_adjustment_quantities(self):
|
||||
"""Stock adjustments are clamped to the available stock quantity.
|
||||
|
||||
Regression test: take_stock / allocateToCustomer / installStockItem
|
||||
previously recorded the *requested* quantity in the stock history,
|
||||
even when less stock was actually removed / allocated / installed.
|
||||
"""
|
||||
part = Part.objects.create(
|
||||
name='Clamp part',
|
||||
description='A part for quantity clamping tests',
|
||||
salable=True,
|
||||
component=True,
|
||||
)
|
||||
|
||||
# --- take_stock: remove more than available ---
|
||||
item = StockItem.objects.create(part=part, quantity=10, delete_on_deplete=False)
|
||||
|
||||
self.assertTrue(item.take_stock(25, self.user, notes='over-remove'))
|
||||
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.quantity, 0)
|
||||
|
||||
# History records the quantity which was *actually* removed
|
||||
entry = item.tracking_info.latest('pk')
|
||||
self.assertEqual(entry.deltas['removed'], 10.0)
|
||||
self.assertEqual(entry.deltas['quantity'], 0.0)
|
||||
|
||||
# Removing stock from an empty item fails, and adds no history
|
||||
n = item.tracking_info.count()
|
||||
self.assertFalse(item.take_stock(5, self.user))
|
||||
self.assertEqual(item.tracking_info.count(), n)
|
||||
|
||||
# --- allocateToCustomer: allocate more than available ---
|
||||
customer = Company.objects.create(name='Clamp customer', is_customer=True)
|
||||
|
||||
item = StockItem.objects.create(part=part, quantity=10)
|
||||
|
||||
allocated = item.allocateToCustomer(customer, quantity=25, user=self.user)
|
||||
|
||||
# The whole item is allocated (no split occurs)
|
||||
self.assertEqual(allocated.pk, item.pk)
|
||||
self.assertEqual(allocated.customer, customer)
|
||||
|
||||
# History records the quantity which was *actually* allocated
|
||||
entry = allocated.tracking_info.latest('pk')
|
||||
self.assertEqual(entry.deltas['quantity'], 10.0)
|
||||
|
||||
# A zero quantity is rejected outright
|
||||
item = StockItem.objects.create(part=part, quantity=10)
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
item.allocateToCustomer(customer, quantity=0, user=self.user)
|
||||
|
||||
# --- installStockItem: install more than available ---
|
||||
assembly = Part.objects.create(
|
||||
name='Clamp assembly',
|
||||
description='An assembly for quantity clamping tests',
|
||||
assembly=True,
|
||||
)
|
||||
|
||||
parent_item = StockItem.objects.create(part=assembly, quantity=1)
|
||||
component = StockItem.objects.create(part=part, quantity=10)
|
||||
|
||||
parent_item.installStockItem(component, 25, self.user, 'over-install')
|
||||
|
||||
component.refresh_from_db()
|
||||
self.assertEqual(component.belongs_to, parent_item)
|
||||
self.assertEqual(component.quantity, 10)
|
||||
|
||||
# Both history entries record the quantity which was *actually* installed
|
||||
entry = component.tracking_info.filter(
|
||||
tracking_type=StockHistoryCode.INSTALLED_INTO_ASSEMBLY
|
||||
).latest('pk')
|
||||
self.assertEqual(entry.deltas['quantity'], 10.0)
|
||||
|
||||
entry = parent_item.tracking_info.filter(
|
||||
tracking_type=StockHistoryCode.INSTALLED_CHILD_ITEM
|
||||
).latest('pk')
|
||||
self.assertEqual(entry.deltas['quantity'], 10.0)
|
||||
|
||||
# A zero quantity is rejected outright
|
||||
other = StockItem.objects.create(part=part, quantity=5)
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
parent_item.installStockItem(other, 0, self.user, 'bad install')
|
||||
|
||||
def test_uninstall_into_structural_location(self):
|
||||
"""Test that an item cannot be uninstalled into a structural location."""
|
||||
parent = StockItem.objects.get(pk=1)
|
||||
|
|
|
|||
Loading…
Reference in New Issue