mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Fix serializer nesting
- Add new API endpoint to receive items - Add unit testing
This commit is contained in:
parent
28cc241354
commit
dc53a433a7
@ -11,6 +11,9 @@ from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import generics
|
||||
from rest_framework import filters, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from InvenTree.helpers import str2bool
|
||||
from InvenTree.api import AttachmentMixin
|
||||
@ -27,6 +30,7 @@ from .models import SalesOrder, SalesOrderLineItem, SalesOrderAllocation
|
||||
from .models import SalesOrderAttachment
|
||||
from .serializers import SalesOrderSerializer, SOLineItemSerializer, SOAttachmentSerializer
|
||||
from .serializers import SalesOrderAllocationSerializer
|
||||
from .serializers import POReceiveSerializer, POLineItemReceiveSerializer
|
||||
|
||||
|
||||
class POList(generics.ListCreateAPIView):
|
||||
@ -204,6 +208,41 @@ class PODetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
return queryset
|
||||
|
||||
|
||||
class POReceive(generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint to receive stock items against a purchase order.
|
||||
|
||||
- The purchase order is specified in the URL.
|
||||
- Items to receive are specified as a list called "items" with the following options:
|
||||
- supplier_part: pk value of the supplier part
|
||||
- quantity: quantity to receive
|
||||
- status: stock item status
|
||||
- location: destination for stock item (optional)
|
||||
- A global location can also be specified
|
||||
"""
|
||||
|
||||
queryset = PurchaseOrderLineItem.objects.none()
|
||||
|
||||
serializer_class = POReceiveSerializer
|
||||
|
||||
def get_order(self):
|
||||
"""
|
||||
Returns the PurchaseOrder associated with this API endpoint
|
||||
"""
|
||||
|
||||
order = PurchaseOrder.objects.get(pk=self.kwargs['pk'])
|
||||
|
||||
return order
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
# Validate the serialized data
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
|
||||
|
||||
class POLineItemList(generics.ListCreateAPIView):
|
||||
""" API endpoint for accessing a list of POLineItem objects
|
||||
|
||||
@ -616,13 +655,25 @@ class POAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin)
|
||||
|
||||
|
||||
order_api_urls = [
|
||||
|
||||
# API endpoints for purchase orders
|
||||
url(r'po/attachment/', include([
|
||||
url(r'^(?P<pk>\d+)/$', POAttachmentDetail.as_view(), name='api-po-attachment-detail'),
|
||||
url(r'^.*$', POAttachmentList.as_view(), name='api-po-attachment-list'),
|
||||
url(r'^po/', include([
|
||||
|
||||
# Purchase order attachments
|
||||
url(r'attachment/', include([
|
||||
url(r'^(?P<pk>\d+)/$', POAttachmentDetail.as_view(), name='api-po-attachment-detail'),
|
||||
url(r'^.*$', POAttachmentList.as_view(), name='api-po-attachment-list'),
|
||||
])),
|
||||
|
||||
# Individual purchase order detail URLs
|
||||
url(r'^(?P<pk>\d+)/', include([
|
||||
url(r'^receive/', POReceive.as_view(), name='api-po-receive'),
|
||||
url(r'.*$', PODetail.as_view(), name='api-po-detail'),
|
||||
])),
|
||||
|
||||
# Purchase order list
|
||||
url(r'^.*$', POList.as_view(), name='api-po-list'),
|
||||
])),
|
||||
url(r'^po/(?P<pk>\d+)/$', PODetail.as_view(), name='api-po-detail'),
|
||||
url(r'^po/.*$', POList.as_view(), name='api-po-list'),
|
||||
|
||||
# API endpoints for purchase order line items
|
||||
url(r'^po-line/(?P<pk>\d+)/$', POLineItemDetail.as_view(), name='api-po-line-detail'),
|
||||
|
@ -186,6 +186,13 @@ class POLineItemReceiveSerializer(serializers.Serializer):
|
||||
help_text=_('Select destination location for received items'),
|
||||
)
|
||||
|
||||
quantity = serializers.DecimalField(
|
||||
max_digits=15,
|
||||
decimal_places=5,
|
||||
min_value=0,
|
||||
required=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = [
|
||||
'supplier_part',
|
||||
@ -198,8 +205,8 @@ class POReceiveSerializer(serializers.Serializer):
|
||||
Serializer for receiving items against a purchase order
|
||||
"""
|
||||
|
||||
items = serializers.StringRelatedField(
|
||||
many=True
|
||||
items = POLineItemReceiveSerializer(
|
||||
many=True,
|
||||
)
|
||||
|
||||
location = serializers.PrimaryKeyRelatedField(
|
||||
@ -220,9 +227,10 @@ class POReceiveSerializer(serializers.Serializer):
|
||||
items = data.get('items', [])
|
||||
|
||||
if len(items) == 0:
|
||||
raise ValidationError({
|
||||
'items': _('Line items must be provided'),
|
||||
})
|
||||
self._errors['items'] = _('Line items must be provided')
|
||||
|
||||
if self._errors and raise_exception:
|
||||
raise ValidationError(self.errors)
|
||||
|
||||
return not bool(self._errors)
|
||||
|
||||
|
@ -201,6 +201,73 @@ class PurchaseOrderTest(OrderTest):
|
||||
response = self.get(url, expected_code=404)
|
||||
|
||||
|
||||
class PurchaseOrderReceiveTest(OrderTest):
|
||||
"""
|
||||
Unit tests for receiving items against a PurchaseOrder
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.assignRole('purchase_order.add')
|
||||
|
||||
self.url = reverse('api-po-receive', kwargs={'pk': 1})
|
||||
|
||||
def test_empty(self):
|
||||
"""
|
||||
Test without any POST data
|
||||
"""
|
||||
|
||||
data = self.post(self.url, {}, expected_code=400).data
|
||||
|
||||
self.assertIn('This field is required', str(data['items']))
|
||||
self.assertIn('This field is required', str(data['location']))
|
||||
|
||||
def test_no_items(self):
|
||||
"""
|
||||
Test with an empty list of items
|
||||
"""
|
||||
|
||||
data = self.post(
|
||||
self.url,
|
||||
{
|
||||
"items": [],
|
||||
"location": None,
|
||||
},
|
||||
expected_code=400
|
||||
).data
|
||||
|
||||
self.assertIn('Line items must be provided', str(data['items']))
|
||||
|
||||
def test_invalid_items(self):
|
||||
"""
|
||||
Test than errors are returned as expected for invalid data
|
||||
"""
|
||||
|
||||
data = self.post(
|
||||
self.url,
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"supplier_part": 12345,
|
||||
"location": 12345
|
||||
}
|
||||
]
|
||||
},
|
||||
expected_code=400
|
||||
).data
|
||||
|
||||
items = data['items']
|
||||
|
||||
self.assertIn('Invalid pk "12345"', str(items['supplier_part']))
|
||||
self.assertIn("object does not exist", str(items['location']))
|
||||
|
||||
def test_mismatched_items(self):
|
||||
"""
|
||||
Test for supplier parts which *do* exist but do not match the order supplier
|
||||
"""
|
||||
|
||||
|
||||
class SalesOrderTest(OrderTest):
|
||||
"""
|
||||
Tests for the SalesOrder API
|
||||
|
Loading…
Reference in New Issue
Block a user