Actually receive items

This commit is contained in:
Oliver 2021-08-24 08:49:23 +10:00
parent 2aa505b2cb
commit d30173132a
4 changed files with 79 additions and 11 deletions

View File

@ -5,7 +5,9 @@ JSON API for the Order app
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from django.conf.urls import url, include from django.conf.urls import url, include
from django.db import transaction
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics from rest_framework import generics
@ -13,7 +15,6 @@ from rest_framework import filters, status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from django.utils.translation import ugettext_lazy as _
from InvenTree.helpers import str2bool from InvenTree.helpers import str2bool
from InvenTree.api import AttachmentMixin from InvenTree.api import AttachmentMixin
@ -252,25 +253,34 @@ class POReceive(generics.CreateAPIView):
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
# Check that the received line items are indeed correct # Receive the line items
self.validate(serializer.validated_data) self.receive_items(serializer)
headers = self.get_success_headers(serializer.data) headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def validate(self, data): @transaction.atomic
def receive_items(self, serializer):
""" """
Validate the deserialized data. Receive the items
At this point, much of the heavy lifting has been done for us by DRF serializers At this point, much of the heavy lifting has been done for us by DRF serializers!
We have a list of "items", each a dict which contains:
- line_item: A PurchaseOrderLineItem matching this order
- location: A destination location
- quantity: A validated numerical quantity
- status: The status code for the received item
""" """
data = serializer.validated_data
location = data['location'] location = data['location']
# Keep track of validated data "on the fly" items = data['items']
self.items = []
for item in data['items']: # Check if the location is not specified for any particular item
for item in items:
supplier_part = item['supplier_part'] supplier_part = item['supplier_part']
@ -287,7 +297,15 @@ class POReceive(generics.CreateAPIView):
item['location'] = location item['location'] = location
quantity = item['quantity'] # Now we can actually receive the items
for item in items:
self.order.receive_line_item(
item['line_item'],
item['location'],
item['quantity'],
self.request.user,
status=item['status'],
)
class POLineItemList(generics.ListCreateAPIView): class POLineItemList(generics.ListCreateAPIView):

View File

@ -433,7 +433,7 @@ class PurchaseOrder(Order):
quantity=quantity, quantity=quantity,
purchase_order=self, purchase_order=self,
status=status, status=status,
purchase_price=purchase_price, purchase_price=line.purchase_price,
) )
stock.save(add_note=False) stock.save(add_note=False)

View File

@ -20,6 +20,8 @@ from InvenTree.serializers import InvenTreeAttachmentSerializer
from InvenTree.serializers import InvenTreeMoneySerializer from InvenTree.serializers import InvenTreeMoneySerializer
from InvenTree.serializers import InvenTreeAttachmentSerializerField from InvenTree.serializers import InvenTreeAttachmentSerializerField
from InvenTree.status_codes import StockStatus
import company.models import company.models
from company.serializers import CompanyBriefSerializer, SupplierPartSerializer from company.serializers import CompanyBriefSerializer, SupplierPartSerializer
@ -200,10 +202,17 @@ class POLineItemReceiveSerializer(serializers.Serializer):
required=True, required=True,
) )
status = serializers.ChoiceField(
choices=StockStatus.options,
default=StockStatus.OK,
label=_('Status'),
)
class Meta: class Meta:
fields = [ fields = [
'supplier_part', 'supplier_part',
'location', 'location',
'status',
] ]

View File

@ -2,6 +2,7 @@
Tests for the Order API Tests for the Order API
""" """
from InvenTree.stock.models import StockItem
from datetime import datetime, timedelta from datetime import datetime, timedelta
from rest_framework import status from rest_framework import status
@ -213,6 +214,9 @@ class PurchaseOrderReceiveTest(OrderTest):
self.url = reverse('api-po-receive', kwargs={'pk': 1}) self.url = reverse('api-po-receive', kwargs={'pk': 1})
# Number of stock items which exist at the start of each test
self.n = StockItem.objects.count()
def test_empty(self): def test_empty(self):
""" """
Test without any POST data Test without any POST data
@ -223,6 +227,9 @@ class PurchaseOrderReceiveTest(OrderTest):
self.assertIn('This field is required', str(data['items'])) self.assertIn('This field is required', str(data['items']))
self.assertIn('This field is required', str(data['location'])) self.assertIn('This field is required', str(data['location']))
# No new stock items have been created
self.assertEqual(self.n, StockItem.objects.count())
def test_no_items(self): def test_no_items(self):
""" """
Test with an empty list of items Test with an empty list of items
@ -239,6 +246,9 @@ class PurchaseOrderReceiveTest(OrderTest):
self.assertIn('Line items must be provided', str(data['items'])) self.assertIn('Line items must be provided', str(data['items']))
# No new stock items have been created
self.assertEqual(self.n, StockItem.objects.count())
def test_invalid_items(self): def test_invalid_items(self):
""" """
Test than errors are returned as expected for invalid data Test than errors are returned as expected for invalid data
@ -262,6 +272,34 @@ class PurchaseOrderReceiveTest(OrderTest):
self.assertIn('Invalid pk "12345"', str(items['line_item'])) self.assertIn('Invalid pk "12345"', str(items['line_item']))
self.assertIn("object does not exist", str(items['location'])) self.assertIn("object does not exist", str(items['location']))
# No new stock items have been created
self.assertEqual(self.n, StockItem.objects.count())
def test_invalid_status(self):
"""
Test with an invalid StockStatus value
"""
data = self.post(
self.url,
{
"items": [
{
"line_item": 22,
"location": 1,
"status": 99999,
"quantity": 5,
}
]
},
expected_code=400
).data
self.assertIn('"99999" is not a valid choice.', str(data))
# No new stock items have been created
self.assertEqual(self.n, StockItem.objects.count())
def test_mismatched_items(self): def test_mismatched_items(self):
""" """
Test for supplier parts which *do* exist but do not match the order supplier Test for supplier parts which *do* exist but do not match the order supplier
@ -284,6 +322,9 @@ class PurchaseOrderReceiveTest(OrderTest):
self.assertIn('Line item does not match purchase order', str(data)) self.assertIn('Line item does not match purchase order', str(data))
# No new stock items have been created
self.assertEqual(self.n, StockItem.objects.count())
class SalesOrderTest(OrderTest): class SalesOrderTest(OrderTest):
""" """