Fix serializer nesting

- Add new API endpoint to receive items
- Add unit testing
This commit is contained in:
Oliver 2021-08-23 23:35:22 +10:00
parent 28cc241354
commit dc53a433a7
3 changed files with 136 additions and 10 deletions

View File

@ -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'),

View File

@ -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)

View File

@ -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