mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Actually receive items
This commit is contained in:
parent
2aa505b2cb
commit
d30173132a
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user