mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-30 12:36:45 +00:00
All stock adjustment actions ported to new scheme
- Bumped API version too
This commit is contained in:
parent
f197d8b1da
commit
102f886d81
@ -10,11 +10,15 @@ import common.models
|
|||||||
|
|
||||||
INVENTREE_SW_VERSION = "0.6.0 dev"
|
INVENTREE_SW_VERSION = "0.6.0 dev"
|
||||||
|
|
||||||
INVENTREE_API_VERSION = 13
|
INVENTREE_API_VERSION = 14
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||||
|
|
||||||
|
v14 -> 2021-20-05
|
||||||
|
- Stock adjustment actions API is improved, using native DRF serializer support
|
||||||
|
- However adjustment actions now only support 'pk' as a lookup field
|
||||||
|
|
||||||
v13 -> 2021-10-05
|
v13 -> 2021-10-05
|
||||||
- Adds API endpoint to allocate stock items against a BuildOrder
|
- Adds API endpoint to allocate stock items against a BuildOrder
|
||||||
- Updates StockItem API with improved filtering against BomItem data
|
- Updates StockItem API with improved filtering against BomItem data
|
||||||
|
@ -120,7 +120,7 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
instance.mark_for_deletion()
|
instance.mark_for_deletion()
|
||||||
|
|
||||||
|
|
||||||
class StockAdjust(APIView):
|
class StockAdjustView(generics.CreateAPIView):
|
||||||
"""
|
"""
|
||||||
A generic class for handling stocktake actions.
|
A generic class for handling stocktake actions.
|
||||||
|
|
||||||
@ -134,174 +134,50 @@ class StockAdjust(APIView):
|
|||||||
|
|
||||||
queryset = StockItem.objects.none()
|
queryset = StockItem.objects.none()
|
||||||
|
|
||||||
def get_items(self, request):
|
def get_serializer_context(self):
|
||||||
"""
|
|
||||||
Return a list of items posted to the endpoint.
|
context = super().get_serializer_context()
|
||||||
Will raise validation errors if the items are not
|
|
||||||
correctly formatted.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_items = []
|
context['request'] = self.request
|
||||||
|
|
||||||
if 'item' in request.data:
|
return context
|
||||||
_items = [request.data['item']]
|
|
||||||
elif 'items' in request.data:
|
|
||||||
_items = request.data['items']
|
|
||||||
else:
|
|
||||||
_items = []
|
|
||||||
|
|
||||||
if len(_items) == 0:
|
|
||||||
raise ValidationError(_('Request must contain list of stock items'))
|
|
||||||
|
|
||||||
# List of validated items
|
|
||||||
self.items = []
|
|
||||||
|
|
||||||
for entry in _items:
|
|
||||||
|
|
||||||
if not type(entry) == dict:
|
|
||||||
raise ValidationError(_('Improperly formatted data'))
|
|
||||||
|
|
||||||
# Look for a 'pk' value (use 'id' as a backup)
|
|
||||||
pk = entry.get('pk', entry.get('id', None))
|
|
||||||
|
|
||||||
try:
|
|
||||||
pk = int(pk)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
raise ValidationError(_('Each entry must contain a valid integer primary-key'))
|
|
||||||
|
|
||||||
try:
|
|
||||||
item = StockItem.objects.get(pk=pk)
|
|
||||||
except (StockItem.DoesNotExist):
|
|
||||||
raise ValidationError({
|
|
||||||
pk: [_('Primary key does not match valid stock item')]
|
|
||||||
})
|
|
||||||
|
|
||||||
if self.allow_missing_quantity and 'quantity' not in entry:
|
|
||||||
entry['quantity'] = item.quantity
|
|
||||||
|
|
||||||
try:
|
|
||||||
quantity = Decimal(str(entry.get('quantity', None)))
|
|
||||||
except (ValueError, TypeError, InvalidOperation):
|
|
||||||
raise ValidationError({
|
|
||||||
pk: [_('Invalid quantity value')]
|
|
||||||
})
|
|
||||||
|
|
||||||
if quantity < 0:
|
|
||||||
raise ValidationError({
|
|
||||||
pk: [_('Quantity must not be less than zero')]
|
|
||||||
})
|
|
||||||
|
|
||||||
self.items.append({
|
|
||||||
'item': item,
|
|
||||||
'quantity': quantity
|
|
||||||
})
|
|
||||||
|
|
||||||
# Extract 'notes' field
|
|
||||||
self.notes = str(request.data.get('notes', ''))
|
|
||||||
|
|
||||||
|
|
||||||
class StockCount(generics.CreateAPIView):
|
class StockCount(StockAdjustView):
|
||||||
"""
|
"""
|
||||||
Endpoint for counting stock (performing a stocktake).
|
Endpoint for counting stock (performing a stocktake).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = StockItem.objects.none()
|
|
||||||
|
|
||||||
serializer_class = StockSerializers.StockCountSerializer
|
serializer_class = StockSerializers.StockCountSerializer
|
||||||
|
|
||||||
def get_serializer_context(self):
|
|
||||||
|
|
||||||
context = super().get_serializer_context()
|
|
||||||
|
|
||||||
context['request'] = self.request
|
class StockAdd(StockAdjustView):
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class StockAdd(StockAdjust):
|
|
||||||
"""
|
"""
|
||||||
Endpoint for adding a quantity of stock to an existing StockItem
|
Endpoint for adding a quantity of stock to an existing StockItem
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
serializer_class = StockSerializers.StockAddSerializer
|
||||||
|
|
||||||
self.get_items(request)
|
|
||||||
|
|
||||||
n = 0
|
|
||||||
|
|
||||||
for item in self.items:
|
|
||||||
if item['item'].add_stock(item['quantity'], request.user, notes=self.notes):
|
|
||||||
n += 1
|
|
||||||
|
|
||||||
return Response({"success": "Added stock for {n} items".format(n=n)})
|
|
||||||
|
|
||||||
|
|
||||||
class StockRemove(StockAdjust):
|
class StockRemove(StockAdjustView):
|
||||||
"""
|
"""
|
||||||
Endpoint for removing a quantity of stock from an existing StockItem.
|
Endpoint for removing a quantity of stock from an existing StockItem.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
serializer_class = StockSerializers.StockRemoveSerializer
|
||||||
|
|
||||||
self.get_items(request)
|
|
||||||
|
|
||||||
n = 0
|
|
||||||
|
|
||||||
for item in self.items:
|
|
||||||
|
|
||||||
if item['quantity'] > item['item'].quantity:
|
|
||||||
raise ValidationError({
|
|
||||||
item['item'].pk: [_('Specified quantity exceeds stock quantity')]
|
|
||||||
})
|
|
||||||
|
|
||||||
if item['item'].take_stock(item['quantity'], request.user, notes=self.notes):
|
|
||||||
n += 1
|
|
||||||
|
|
||||||
return Response({"success": "Removed stock for {n} items".format(n=n)})
|
|
||||||
|
|
||||||
|
|
||||||
class StockTransfer(StockAdjust):
|
class StockTransfer(StockAdjustView):
|
||||||
"""
|
"""
|
||||||
API endpoint for performing stock movements
|
API endpoint for performing stock movements
|
||||||
"""
|
"""
|
||||||
|
|
||||||
allow_missing_quantity = True
|
serializer_class = StockSerializers.StockTransferSerializer
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
|
|
||||||
data = request.data
|
|
||||||
|
|
||||||
try:
|
|
||||||
location = StockLocation.objects.get(pk=data.get('location', None))
|
|
||||||
except (ValueError, StockLocation.DoesNotExist):
|
|
||||||
raise ValidationError({'location': [_('Valid location must be specified')]})
|
|
||||||
|
|
||||||
n = 0
|
|
||||||
|
|
||||||
self.get_items(request)
|
|
||||||
|
|
||||||
for item in self.items:
|
|
||||||
|
|
||||||
if item['quantity'] > item['item'].quantity:
|
|
||||||
raise ValidationError({
|
|
||||||
item['item'].pk: [_('Specified quantity exceeds stock quantity')]
|
|
||||||
})
|
|
||||||
|
|
||||||
# If quantity is not specified, move the entire stock
|
|
||||||
if item['quantity'] in [0, None]:
|
|
||||||
item['quantity'] = item['item'].quantity
|
|
||||||
|
|
||||||
if item['item'].move(location, self.notes, request.user, quantity=item['quantity']):
|
|
||||||
n += 1
|
|
||||||
|
|
||||||
return Response({'success': _('Moved {n} parts to {loc}').format(
|
|
||||||
n=n,
|
|
||||||
loc=str(location),
|
|
||||||
)})
|
|
||||||
|
|
||||||
|
|
||||||
class StockLocationList(generics.ListCreateAPIView):
|
class StockLocationList(generics.ListCreateAPIView):
|
||||||
""" API endpoint for list view of StockLocation objects:
|
"""
|
||||||
|
API endpoint for list view of StockLocation objects:
|
||||||
|
|
||||||
- GET: Return list of StockLocation objects
|
- GET: Return list of StockLocation objects
|
||||||
- POST: Create a new StockLocation
|
- POST: Create a new StockLocation
|
||||||
|
@ -420,7 +420,8 @@ class StockAdjustmentItemSerializer(serializers.Serializer):
|
|||||||
many=False,
|
many=False,
|
||||||
allow_null=False,
|
allow_null=False,
|
||||||
required=True,
|
required=True,
|
||||||
label=_('StockItem primary key value')
|
label='stock_item',
|
||||||
|
help_text=_('StockItem primary key value')
|
||||||
)
|
)
|
||||||
|
|
||||||
quantity = serializers.DecimalField(
|
quantity = serializers.DecimalField(
|
||||||
@ -446,7 +447,9 @@ class StockAdjustmentSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
notes = serializers.CharField(
|
notes = serializers.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
allow_blank=False,
|
allow_blank=True,
|
||||||
|
label=_("Notes"),
|
||||||
|
help_text=_("Stock transaction notes"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
@ -467,9 +470,7 @@ class StockCountSerializer(StockAdjustmentSerializer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""
|
|
||||||
Perform the database transactions to count the stock
|
|
||||||
"""
|
|
||||||
request = self.context['request']
|
request = self.context['request']
|
||||||
|
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
@ -487,3 +488,105 @@ class StockCountSerializer(StockAdjustmentSerializer):
|
|||||||
request.user,
|
request.user,
|
||||||
notes=notes
|
notes=notes
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class StockAddSerializer(StockAdjustmentSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for adding stock to stock item(s)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
|
||||||
|
request = self.context['request']
|
||||||
|
|
||||||
|
data = self.validated_data
|
||||||
|
notes = data['notes']
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for item in data['items']:
|
||||||
|
|
||||||
|
stock_item = item['pk']
|
||||||
|
quantity = item['quantity']
|
||||||
|
|
||||||
|
stock_item.add_stock(
|
||||||
|
quantity,
|
||||||
|
request.user,
|
||||||
|
notes=notes
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class StockRemoveSerializer(StockAdjustmentSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for removing stock from stock item(s)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
|
||||||
|
request = self.context['request']
|
||||||
|
|
||||||
|
data = self.validated_data
|
||||||
|
notes = data['notes']
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for item in data['items']:
|
||||||
|
|
||||||
|
stock_item = item['pk']
|
||||||
|
quantity = item['quantity']
|
||||||
|
|
||||||
|
stock_item.take_stock(
|
||||||
|
quantity,
|
||||||
|
request.user,
|
||||||
|
notes=notes
|
||||||
|
)
|
||||||
|
|
||||||
|
class StockTransferSerializer(StockAdjustmentSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for transferring (moving) stock item(s)
|
||||||
|
"""
|
||||||
|
|
||||||
|
location = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=StockLocation.objects.all(),
|
||||||
|
many=False,
|
||||||
|
required=True,
|
||||||
|
allow_null=False,
|
||||||
|
label=_('Location'),
|
||||||
|
help_text=_('Destination stock location'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = [
|
||||||
|
'items',
|
||||||
|
'notes',
|
||||||
|
'location',
|
||||||
|
]
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
|
||||||
|
super().validate(data)
|
||||||
|
|
||||||
|
# TODO: Any specific validation of location field?
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
|
||||||
|
request = self.context['request']
|
||||||
|
|
||||||
|
data = self.validated_data
|
||||||
|
|
||||||
|
items = data['items']
|
||||||
|
notes = data['notes']
|
||||||
|
location = data['location']
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for item in items:
|
||||||
|
|
||||||
|
stock_item = item['pk']
|
||||||
|
quantity = item['quantity']
|
||||||
|
|
||||||
|
stock_item.move(
|
||||||
|
location,
|
||||||
|
notes,
|
||||||
|
request.user,
|
||||||
|
quantity=item['quantity']
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user