diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index 1808182cbb..627433fe95 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -7,6 +7,9 @@ INVENTREE_API_VERSION = 113 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v114 -> 2023-05-16 : https://github.com/inventree/InvenTree/pull/4825 + - Adds "delivery_date" to shipments + v113 -> 2023-05-13 : https://github.com/inventree/InvenTree/pull/4800 - Adds API endpoints for scrapping a build output diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 00ac96e29b..8b949b50d1 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -1020,6 +1020,15 @@ class SalesOrderShipmentFilter(rest_filters.FilterSet): else: return queryset.filter(shipment_date=None) + delivered = rest_filters.BooleanFilter(label='delivered', method='filter_delivered') + + def filter_delivered(self, queryset, name, value): + """Filter SalesOrder list by 'delivered' status (boolean)""" + if str2bool(value): + return queryset.exclude(delivery_date=None) + else: + return queryset.filter(delivery_date=None) + class SalesOrderShipmentList(ListCreateAPI): """API list endpoint for SalesOrderShipment model.""" diff --git a/InvenTree/order/migrations/0095_salesordershipment_delivery_date.py b/InvenTree/order/migrations/0095_salesordershipment_delivery_date.py new file mode 100644 index 0000000000..fb825b5858 --- /dev/null +++ b/InvenTree/order/migrations/0095_salesordershipment_delivery_date.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-05-13 06:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0094_auto_20230514_2331'), + ] + + operations = [ + migrations.AddField( + model_name='salesordershipment', + name='delivery_date', + field=models.DateField(blank=True, help_text='Date of delivery of shipment', null=True, verbose_name='Delivery Date'), + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index ca91d7d2a3..5fdd4d337d 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -1406,6 +1406,12 @@ class SalesOrderShipment(InvenTreeNotesMixin, MetadataMixin, models.Model): help_text=_('Date of shipment'), ) + delivery_date = models.DateField( + null=True, blank=True, + verbose_name=_('Delivery Date'), + help_text=_('Date of delivery of shipment'), + ) + checked_by = models.ForeignKey( User, on_delete=models.SET_NULL, @@ -1449,6 +1455,10 @@ class SalesOrderShipment(InvenTreeNotesMixin, MetadataMixin, models.Model): """Return True if this shipment has already been completed""" return self.shipment_date is not None + def is_delivered(self): + """Return True if this shipment has already been delivered""" + return self.delivery_date is not None + def check_can_complete(self, raise_error=True): """Check if this shipment is able to be completed""" try: @@ -1508,6 +1518,12 @@ class SalesOrderShipment(InvenTreeNotesMixin, MetadataMixin, models.Model): if link is not None: self.link = link + # Was a delivery date provided? + delivery_date = kwargs.get('delivery_date', None) + + if delivery_date is not None: + self.delivery_date = delivery_date + self.save() trigger_event('salesordershipment.completed', id=self.pk) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index c0b359e04b..e016219b1e 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -965,6 +965,7 @@ class SalesOrderShipmentSerializer(InvenTreeModelSerializer): 'order_detail', 'allocations', 'shipment_date', + 'delivery_date', 'checked_by', 'reference', 'tracking_number', @@ -988,6 +989,7 @@ class SalesOrderShipmentCompleteSerializer(serializers.ModelSerializer): fields = [ 'shipment_date', + 'delivery_date', 'tracking_number', 'invoice_number', 'link', @@ -1034,6 +1036,7 @@ class SalesOrderShipmentCompleteSerializer(serializers.ModelSerializer): invoice_number=data.get('invoice_number', shipment.invoice_number), link=data.get('link', shipment.link), shipment_date=shipment_date, + delivery_date=data.get('delivery_date', shipment.delivery_date), ) diff --git a/InvenTree/order/test_api.py b/InvenTree/order/test_api.py index 64d3430939..212036d148 100644 --- a/InvenTree/order/test_api.py +++ b/InvenTree/order/test_api.py @@ -1829,6 +1829,7 @@ class SalesOrderAllocateTest(OrderTest): 'link': 'http://test.com/link.html', 'tracking_number': 'TRK12345', 'shipment_date': '2020-12-05', + 'delivery_date': '2023-12-05', }, expected_code=201, ) @@ -1839,6 +1840,48 @@ class SalesOrderAllocateTest(OrderTest): self.assertEqual(self.shipment.tracking_number, 'TRK12345') self.assertEqual(self.shipment.invoice_number, 'INV01234') self.assertEqual(self.shipment.link, 'http://test.com/link.html') + self.assertEqual(self.shipment.delivery_date, datetime(2023, 12, 5).date()) + self.assertTrue(self.shipment.is_delivered()) + + def test_shipment_deliverydate(self): + """Test delivery date functions via API.""" + url = reverse('api-so-shipment-detail', kwargs={'pk': self.shipment.pk}) + + # Attempt remove delivery_date from shipment + response = self.patch( + url, + { + 'delivery_date': None, + }, + expected_code=200, + ) + + # Shipment should not be marked as delivered + self.assertFalse(self.shipment.is_delivered()) + + # Attempt to set delivery date + response = self.patch( + url, + { + 'delivery_date': 'asfasd', + }, + expected_code=400, + ) + + self.assertIn('Date has wrong format', str(response.data)) + + response = self.patch( + url, + { + 'delivery_date': '2023-05-15', + }, + expected_code=200, + ) + self.shipment.refresh_from_db() + + # Shipment should now be marked as delivered + self.assertTrue(self.shipment.is_delivered()) + self.assertEqual(self.shipment.delivery_date, datetime(2023, 5, 15).date()) def test_sales_order_shipment_list(self): """Test the SalesOrderShipment list API endpoint""" diff --git a/InvenTree/order/test_sales_order.py b/InvenTree/order/test_sales_order.py index d957d7c1f8..77c4a900aa 100644 --- a/InvenTree/order/test_sales_order.py +++ b/InvenTree/order/test_sales_order.py @@ -257,6 +257,13 @@ class SalesOrderTest(TestCase): # Shipment should have default reference of '1' self.assertEqual('1', order_2.pending_shipments()[0].reference) + def test_shipment_delivery(self): + """Test the shipment delivery settings""" + + # Shipment delivery date should be empty before setting date + self.assertIsNone(self.shipment.delivery_date) + self.assertFalse(self.shipment.is_delivered()) + def test_overdue_notification(self): """Test overdue sales order notification""" diff --git a/InvenTree/templates/js/translated/sales_order.js b/InvenTree/templates/js/translated/sales_order.js index 5d77807be6..5ba4dfa041 100644 --- a/InvenTree/templates/js/translated/sales_order.js +++ b/InvenTree/templates/js/translated/sales_order.js @@ -209,6 +209,9 @@ function salesOrderShipmentFields(options={}) { }, link: { icon: 'fa-link', + }, + delivery_date: { + icon: 'fa-calendar-check', } }; @@ -298,7 +301,11 @@ function completeSalesOrderShipment(shipment_id, options={}) { link: { value: shipment.link, icon: 'fa-link', - } + }, + delivery_date: { + value: shipment.delivery_date, + icon: 'fa-calendar-check', + }, }, preFormContent: html, confirm: true, @@ -979,6 +986,18 @@ function loadSalesOrderShipmentTable(table, options={}) { } } }, + { + field: 'delivery_date', + title: '{% trans "Delivery Date" %}', + sortable: true, + formatter: function(value, row) { + if (value) { + return renderDate(value); + } else { + return '{% trans "Unknown" %}'; + } + } + }, { field: 'tracking_number', title: '{% trans "Tracking" %}',