diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 0a32a6ee04..ac1dff2f8c 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,14 +1,17 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 265 +INVENTREE_API_VERSION = 266 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ -265 - 2024-10-06 : https://github.com/inventree/InvenTree/pull/8228 +266 - 2024-10-07 : https://github.com/inventree/InvenTree/pull/8249 + - Tweak SalesOrderShipment API for more efficient data retrieval + +265 - 2024-10-07 : https://github.com/inventree/InvenTree/pull/8228 - Adds API endpoint for providing custom admin integration details for plugins 264 - 2024-10-03 : https://github.com/inventree/InvenTree/pull/8231 diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py index 74afcdfbed..963c1ee84d 100644 --- a/src/backend/InvenTree/order/api.py +++ b/src/backend/InvenTree/order/api.py @@ -767,11 +767,15 @@ class SalesOrderLineItemMixin: 'part', 'part__stock_items', 'allocations', + 'allocations__shipment', + 'allocations__item__part', 'allocations__item__location', 'order', 'order__stock_items', ) + queryset = queryset.select_related('part__pricing_data') + queryset = serializers.SalesOrderLineItemSerializer.annotate_queryset(queryset) return queryset diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 30b6746a11..0d27512971 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -1878,16 +1878,11 @@ class SalesOrderShipment( 2. Update the "shipped" quantity of all associated line items 3. Set the "shipment_date" to now """ + import order.tasks + # Check if the shipment can be completed (throw error if not) self.check_can_complete() - allocations = self.allocations.all() - - # Iterate through each stock item assigned to this shipment - for allocation in allocations: - # Mark the allocation as "complete" - allocation.complete_allocation(user) - # Update the "shipment" date self.shipment_date = kwargs.get( 'shipment_date', InvenTree.helpers.current_date() @@ -1920,6 +1915,14 @@ class SalesOrderShipment( self.save() + # Offload the "completion" of each line item to the background worker + # This may take some time, and we don't want to block the main thread + InvenTree.tasks.offload_task( + order.tasks.complete_sales_order_shipment, + shipment_id=self.pk, + user_id=user.pk if user else None, + ) + trigger_event('salesordershipment.completed', id=self.pk) def create_attachment(self, *args, **kwargs): diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py index 769ae3f456..9bde2e31f3 100644 --- a/src/backend/InvenTree/order/serializers.py +++ b/src/backend/InvenTree/order/serializers.py @@ -1094,10 +1094,10 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): # Extra detail fields order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True) part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True) - item_detail = stock.serializers.StockItemSerializer( + item_detail = stock.serializers.StockItemSerializerBrief( source='item', many=False, read_only=True ) - location_detail = stock.serializers.LocationSerializer( + location_detail = stock.serializers.LocationBriefSerializer( source='item.location', many=False, read_only=True ) customer_detail = CompanyBriefSerializer( @@ -1659,12 +1659,18 @@ class SalesOrderSerialAllocationSerializer(serializers.Serializer): stock_items = data['stock_items'] shipment = data['shipment'] - with transaction.atomic(): - for stock_item in stock_items: - # Create a new SalesOrderAllocation - order.models.SalesOrderAllocation.objects.create( + allocations = [] + + for stock_item in stock_items: + # Create a new SalesOrderAllocation + allocations.append( + order.models.SalesOrderAllocation( line=line_item, item=stock_item, quantity=1, shipment=shipment ) + ) + + with transaction.atomic(): + order.models.SalesOrderAllocation.objects.bulk_create(allocations) class SalesOrderShipmentAllocationSerializer(serializers.Serializer): diff --git a/src/backend/InvenTree/order/tasks.py b/src/backend/InvenTree/order/tasks.py index 1e1231a810..f2f37327da 100644 --- a/src/backend/InvenTree/order/tasks.py +++ b/src/backend/InvenTree/order/tasks.py @@ -1,7 +1,10 @@ """Background tasks for the 'order' app.""" +import logging from datetime import datetime, timedelta +from django.contrib.auth.models import User +from django.db import transaction from django.utils.translation import gettext_lazy as _ import common.notifications @@ -11,6 +14,8 @@ from InvenTree.tasks import ScheduledTask, scheduled_task from order.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups from plugin.events import trigger_event +logger = logging.getLogger('inventree') + def notify_overdue_purchase_order(po: order.models.PurchaseOrder): """Notify users that a PurchaseOrder has just become 'overdue'.""" @@ -109,3 +114,31 @@ def check_overdue_sales_orders(): for po in overdue_orders: notify_overdue_sales_order(po) + + +def complete_sales_order_shipment(shipment_id: int, user_id: int) -> None: + """Complete allocations for a pending shipment against a SalesOrder. + + At this stage, the shipment is assumed to be complete, + and we need to perform the required "processing" tasks. + """ + try: + shipment = order.models.SalesOrderShipment.objects.get(pk=shipment_id) + except Exception: + # Shipping object does not exist + logger.warning( + 'Failed to complete shipment - no matching SalesOrderShipment for ID <%s>', + shipment_id, + ) + return + + try: + user = User.objects.get(pk=user_id) + except Exception: + user = None + + logger.info('Completing SalesOrderShipment <%s>', shipment) + + with transaction.atomic(): + for allocation in shipment.allocations.all(): + allocation.complete_allocation(user=user)