mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 03:00:54 +00:00
Stock status change API (#5064)
* Add API endpoint for changing stock item status - Change status for multiple items simultaneously - Reduce number of database queries required * Perform bulk update in serializer * Update 'updated' field * Add front-end code * Bump API version * Bug fix and unit test
This commit is contained in:
@ -158,6 +158,12 @@ class StockAdjustView(CreateAPI):
|
||||
return context
|
||||
|
||||
|
||||
class StockChangeStatus(StockAdjustView):
|
||||
"""API endpoint to change the status code of multiple StockItem objects."""
|
||||
|
||||
serializer_class = StockSerializers.StockChangeStatusSerializer
|
||||
|
||||
|
||||
class StockCount(StockAdjustView):
|
||||
"""Endpoint for counting stock (performing a stocktake)."""
|
||||
|
||||
@ -1371,6 +1377,7 @@ stock_api_urls = [
|
||||
re_path(r'^transfer/', StockTransfer.as_view(), name='api-stock-transfer'),
|
||||
re_path(r'^assign/', StockAssign.as_view(), name='api-stock-assign'),
|
||||
re_path(r'^merge/', StockMerge.as_view(), name='api-stock-merge'),
|
||||
re_path(r'^change_status/', StockChangeStatus.as_view(), name='api-stock-change-status'),
|
||||
|
||||
# StockItemAttachment API endpoints
|
||||
re_path(r'^attachment/', include([
|
||||
|
@ -18,6 +18,7 @@ import common.models
|
||||
import company.models
|
||||
import InvenTree.helpers
|
||||
import InvenTree.serializers
|
||||
import InvenTree.status_codes
|
||||
import part.models as part_models
|
||||
import stock.filters
|
||||
from company.serializers import SupplierPartSerializer
|
||||
@ -481,6 +482,7 @@ class InstallStockItemSerializer(serializers.Serializer):
|
||||
|
||||
note = serializers.CharField(
|
||||
label=_('Note'),
|
||||
help_text=_('Add transaction note (optional)'),
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
)
|
||||
@ -641,6 +643,100 @@ class ReturnStockItemSerializer(serializers.Serializer):
|
||||
)
|
||||
|
||||
|
||||
class StockChangeStatusSerializer(serializers.Serializer):
|
||||
"""Serializer for changing status of multiple StockItem objects"""
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options"""
|
||||
fields = [
|
||||
'items',
|
||||
'status',
|
||||
'note',
|
||||
]
|
||||
|
||||
items = serializers.PrimaryKeyRelatedField(
|
||||
queryset=StockItem.objects.all(),
|
||||
many=True,
|
||||
required=True,
|
||||
allow_null=False,
|
||||
label=_('Stock Items'),
|
||||
help_text=_('Select stock items to change status'),
|
||||
)
|
||||
|
||||
def validate_items(self, items):
|
||||
"""Validate the selected stock items"""
|
||||
|
||||
if len(items) == 0:
|
||||
raise ValidationError(_("No stock items selected"))
|
||||
|
||||
return items
|
||||
|
||||
status = serializers.ChoiceField(
|
||||
choices=InvenTree.status_codes.StockStatus.items(),
|
||||
default=InvenTree.status_codes.StockStatus.OK.value,
|
||||
label=_('Status'),
|
||||
)
|
||||
|
||||
note = serializers.CharField(
|
||||
label=_('Notes'),
|
||||
help_text=_('Add transaction note (optional)'),
|
||||
required=False, allow_blank=True,
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def save(self):
|
||||
"""Save the serializer to change the status of the selected stock items"""
|
||||
|
||||
data = self.validated_data
|
||||
|
||||
items = data['items']
|
||||
status = data['status']
|
||||
|
||||
request = self.context['request']
|
||||
user = getattr(request, 'user', None)
|
||||
|
||||
note = data.get('note', '')
|
||||
|
||||
items_to_update = []
|
||||
transaction_notes = []
|
||||
|
||||
deltas = {
|
||||
'status': status,
|
||||
}
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
# Instead of performing database updates for each item,
|
||||
# perform bulk database updates (much more efficient)
|
||||
|
||||
for item in items:
|
||||
# Ignore items which are already in the desired status
|
||||
if item.status == status:
|
||||
continue
|
||||
|
||||
item.updated = now
|
||||
item.status = status
|
||||
items_to_update.append(item)
|
||||
|
||||
# Create a new transaction note for each item
|
||||
transaction_notes.append(
|
||||
StockItemTracking(
|
||||
item=item,
|
||||
tracking_type=InvenTree.status_codes.StockHistoryCode.EDITED.value,
|
||||
date=now,
|
||||
deltas=deltas,
|
||||
user=user,
|
||||
notes=note,
|
||||
)
|
||||
)
|
||||
|
||||
# Update status
|
||||
StockItem.objects.bulk_update(items_to_update, ['status', 'updated'])
|
||||
|
||||
# Create entries
|
||||
StockItemTracking.objects.bulk_create(transaction_notes)
|
||||
|
||||
|
||||
class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for a simple tree view."""
|
||||
|
||||
|
@ -16,7 +16,7 @@ from rest_framework import status
|
||||
import company.models
|
||||
import part.models
|
||||
from common.models import InvenTreeSetting
|
||||
from InvenTree.status_codes import StockStatus
|
||||
from InvenTree.status_codes import StockHistoryCode, StockStatus
|
||||
from InvenTree.unit_test import InvenTreeAPITestCase
|
||||
from part.models import Part
|
||||
from stock.models import StockItem, StockItemTestResult, StockLocation
|
||||
@ -1153,6 +1153,51 @@ class StockItemTest(StockAPITestCase):
|
||||
stock_item.refresh_from_db()
|
||||
self.assertEqual(stock_item.part, variant)
|
||||
|
||||
def test_set_status(self):
|
||||
"""Test API endpoint for setting StockItem status"""
|
||||
|
||||
url = reverse('api-stock-change-status')
|
||||
|
||||
prt = Part.objects.first()
|
||||
|
||||
# Create a bunch of items
|
||||
items = [
|
||||
StockItem.objects.create(part=prt, quantity=10) for _ in range(10)
|
||||
]
|
||||
|
||||
for item in items:
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.status, StockStatus.OK.value)
|
||||
|
||||
data = {
|
||||
'items': [item.pk for item in items],
|
||||
'status': StockStatus.DAMAGED.value,
|
||||
}
|
||||
|
||||
self.post(url, data, expected_code=201)
|
||||
|
||||
# Check that the item has been updated correctly
|
||||
for item in items:
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.status, StockStatus.DAMAGED.value)
|
||||
self.assertEqual(item.tracking_info.count(), 1)
|
||||
|
||||
# Same test, but with one item unchanged
|
||||
items[0].status = StockStatus.ATTENTION.value
|
||||
items[0].save()
|
||||
|
||||
data['status'] = StockStatus.ATTENTION.value
|
||||
|
||||
self.post(url, data, expected_code=201)
|
||||
|
||||
for item in items:
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.status, StockStatus.ATTENTION.value)
|
||||
self.assertEqual(item.tracking_info.count(), 2)
|
||||
|
||||
tracking = item.tracking_info.last()
|
||||
self.assertEqual(tracking.tracking_type, StockHistoryCode.EDITED.value)
|
||||
|
||||
|
||||
class StocktakeTest(StockAPITestCase):
|
||||
"""Series of tests for the Stocktake API."""
|
||||
|
Reference in New Issue
Block a user