mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 03:56:43 +00:00
Shipment date edit (#3050)
* Allow user to select shipment date when shipping a salesorder - Defaults to 'today' * Retain the tracking number information through the from * JS linting * Add unit testing for the SalesOrderShipmentComplete serializer / API endpoint
This commit is contained in:
parent
840ade25cd
commit
3c02b95f85
@ -1205,14 +1205,23 @@ class SalesOrderShipment(models.Model):
|
|||||||
def is_complete(self):
|
def is_complete(self):
|
||||||
return self.shipment_date is not None
|
return self.shipment_date is not None
|
||||||
|
|
||||||
def check_can_complete(self):
|
def check_can_complete(self, raise_error=True):
|
||||||
|
|
||||||
if self.shipment_date:
|
try:
|
||||||
# Shipment has already been sent!
|
if self.shipment_date:
|
||||||
raise ValidationError(_("Shipment has already been sent"))
|
# Shipment has already been sent!
|
||||||
|
raise ValidationError(_("Shipment has already been sent"))
|
||||||
|
|
||||||
if self.allocations.count() == 0:
|
if self.allocations.count() == 0:
|
||||||
raise ValidationError(_("Shipment has no allocated stock items"))
|
raise ValidationError(_("Shipment has no allocated stock items"))
|
||||||
|
|
||||||
|
except ValidationError as e:
|
||||||
|
if raise_error:
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def complete_shipment(self, user, **kwargs):
|
def complete_shipment(self, user, **kwargs):
|
||||||
@ -1235,7 +1244,7 @@ class SalesOrderShipment(models.Model):
|
|||||||
allocation.complete_allocation(user)
|
allocation.complete_allocation(user)
|
||||||
|
|
||||||
# Update the "shipment" date
|
# Update the "shipment" date
|
||||||
self.shipment_date = datetime.now()
|
self.shipment_date = kwargs.get('shipment_date', datetime.now())
|
||||||
self.shipped_by = user
|
self.shipped_by = user
|
||||||
|
|
||||||
# Was a tracking number provided?
|
# Was a tracking number provided?
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
JSON serializers for the Order API
|
JSON serializers for the Order API
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||||
@ -899,6 +900,7 @@ class SalesOrderShipmentCompleteSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
'tracking_number',
|
'tracking_number',
|
||||||
|
'shipment_date',
|
||||||
]
|
]
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
@ -910,7 +912,7 @@ class SalesOrderShipmentCompleteSerializer(serializers.ModelSerializer):
|
|||||||
if not shipment:
|
if not shipment:
|
||||||
raise ValidationError(_("No shipment details provided"))
|
raise ValidationError(_("No shipment details provided"))
|
||||||
|
|
||||||
shipment.check_can_complete()
|
shipment.check_can_complete(raise_error=True)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -927,9 +929,16 @@ class SalesOrderShipmentCompleteSerializer(serializers.ModelSerializer):
|
|||||||
user = request.user
|
user = request.user
|
||||||
|
|
||||||
# Extract provided tracking number (optional)
|
# Extract provided tracking number (optional)
|
||||||
tracking_number = data.get('tracking_number', None)
|
tracking_number = data.get('tracking_number', shipment.tracking_number)
|
||||||
|
|
||||||
shipment.complete_shipment(user, tracking_number=tracking_number)
|
# Extract shipping date (defaults to today's date)
|
||||||
|
shipment_date = data.get('shipment_date', datetime.now())
|
||||||
|
|
||||||
|
shipment.complete_shipment(
|
||||||
|
user,
|
||||||
|
tracking_number=tracking_number,
|
||||||
|
shipment_date=shipment_date,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderShipmentAllocationItemSerializer(serializers.Serializer):
|
class SalesOrderShipmentAllocationItemSerializer(serializers.Serializer):
|
||||||
|
@ -5,6 +5,7 @@ Tests for the Order API
|
|||||||
import io
|
import io
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -1275,3 +1276,96 @@ class SalesOrderAllocateTest(OrderTest):
|
|||||||
|
|
||||||
for line in self.order.lines.all():
|
for line in self.order.lines.all():
|
||||||
self.assertEqual(line.allocations.count(), 1)
|
self.assertEqual(line.allocations.count(), 1)
|
||||||
|
|
||||||
|
def test_shipment_complete(self):
|
||||||
|
"""Test that we can complete a shipment via the API"""
|
||||||
|
|
||||||
|
url = reverse('api-so-shipment-ship', kwargs={'pk': self.shipment.pk})
|
||||||
|
|
||||||
|
self.assertFalse(self.shipment.is_complete())
|
||||||
|
self.assertFalse(self.shipment.check_can_complete(raise_error=False))
|
||||||
|
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
self.shipment.check_can_complete()
|
||||||
|
|
||||||
|
# Attempting to complete this shipment via the API should fail
|
||||||
|
response = self.post(
|
||||||
|
url, {},
|
||||||
|
expected_code=400
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('Shipment has no allocated stock items', str(response.data))
|
||||||
|
|
||||||
|
# Allocate stock against this shipment
|
||||||
|
line = self.order.lines.first()
|
||||||
|
part = line.part
|
||||||
|
|
||||||
|
models.SalesOrderAllocation.objects.create(
|
||||||
|
shipment=self.shipment,
|
||||||
|
line=line,
|
||||||
|
item=part.stock_items.last(),
|
||||||
|
quantity=5
|
||||||
|
)
|
||||||
|
|
||||||
|
# Shipment should now be able to be completed
|
||||||
|
self.assertTrue(self.shipment.check_can_complete())
|
||||||
|
|
||||||
|
# Attempt with an invalid date
|
||||||
|
response = self.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'shipment_date': 'asfasd',
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('Date has wrong format', str(response.data))
|
||||||
|
|
||||||
|
response = self.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'tracking_number': 'TRK12345',
|
||||||
|
'shipment_date': '2020-12-05',
|
||||||
|
},
|
||||||
|
expected_code=201,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.shipment.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertTrue(self.shipment.is_complete())
|
||||||
|
self.assertEqual(self.shipment.tracking_number, 'TRK12345')
|
||||||
|
|
||||||
|
def test_sales_order_shipment_list(self):
|
||||||
|
|
||||||
|
url = reverse('api-so-shipment-list')
|
||||||
|
|
||||||
|
# Create some new shipments via the API
|
||||||
|
for order in models.SalesOrder.objects.all():
|
||||||
|
|
||||||
|
for idx in range(3):
|
||||||
|
self.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'order': order.pk,
|
||||||
|
'reference': f"SH{idx + 1}",
|
||||||
|
'tracking_number': f"TRK_{order.pk}_{idx}"
|
||||||
|
},
|
||||||
|
expected_code=201
|
||||||
|
)
|
||||||
|
|
||||||
|
# Filter API by order
|
||||||
|
response = self.get(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'order': order.pk,
|
||||||
|
},
|
||||||
|
expected_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3 shipments returned for each SalesOrder instance
|
||||||
|
self.assertGreaterEqual(len(response.data), 3)
|
||||||
|
|
||||||
|
# List *all* shipments
|
||||||
|
response = self.get(url, expected_code=200)
|
||||||
|
|
||||||
|
self.assertEqual(len(response.data), 1 + 3 * models.SalesOrder.objects.count())
|
||||||
|
@ -129,7 +129,12 @@ function completeShipment(shipment_id, options={}) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
title: `{% trans "Complete Shipment" %} ${shipment.reference}`,
|
title: `{% trans "Complete Shipment" %} ${shipment.reference}`,
|
||||||
fields: {
|
fields: {
|
||||||
tracking_number: {},
|
tracking_number: {
|
||||||
|
value: shipment.tracking_number,
|
||||||
|
},
|
||||||
|
shipment_date: {
|
||||||
|
value: moment().format('YYYY-MM-DD'),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
preFormContent: html,
|
preFormContent: html,
|
||||||
confirm: true,
|
confirm: true,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user