2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 19:46:46 +00:00

[Refactor] Sales Order Shipment (#8249)

* Refactor serial number allocation

* Refactor API query

Note: This should be further improved, not to automatically return *all* allocation data

* Push expensive operations off to background worker

* Bump API version
This commit is contained in:
Oliver 2024-10-08 08:00:32 +11:00 committed by GitHub
parent 44d9484715
commit 198a39a33c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 64 additions and 15 deletions

View File

@ -1,14 +1,17 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # 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.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ 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 - Adds API endpoint for providing custom admin integration details for plugins
264 - 2024-10-03 : https://github.com/inventree/InvenTree/pull/8231 264 - 2024-10-03 : https://github.com/inventree/InvenTree/pull/8231

View File

@ -767,11 +767,15 @@ class SalesOrderLineItemMixin:
'part', 'part',
'part__stock_items', 'part__stock_items',
'allocations', 'allocations',
'allocations__shipment',
'allocations__item__part',
'allocations__item__location', 'allocations__item__location',
'order', 'order',
'order__stock_items', 'order__stock_items',
) )
queryset = queryset.select_related('part__pricing_data')
queryset = serializers.SalesOrderLineItemSerializer.annotate_queryset(queryset) queryset = serializers.SalesOrderLineItemSerializer.annotate_queryset(queryset)
return queryset return queryset

View File

@ -1878,16 +1878,11 @@ class SalesOrderShipment(
2. Update the "shipped" quantity of all associated line items 2. Update the "shipped" quantity of all associated line items
3. Set the "shipment_date" to now 3. Set the "shipment_date" to now
""" """
import order.tasks
# Check if the shipment can be completed (throw error if not) # Check if the shipment can be completed (throw error if not)
self.check_can_complete() 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 # Update the "shipment" date
self.shipment_date = kwargs.get( self.shipment_date = kwargs.get(
'shipment_date', InvenTree.helpers.current_date() 'shipment_date', InvenTree.helpers.current_date()
@ -1920,6 +1915,14 @@ class SalesOrderShipment(
self.save() 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) trigger_event('salesordershipment.completed', id=self.pk)
def create_attachment(self, *args, **kwargs): def create_attachment(self, *args, **kwargs):

View File

@ -1094,10 +1094,10 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
# Extra detail fields # Extra detail fields
order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True) order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True)
part_detail = PartBriefSerializer(source='item.part', 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 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 source='item.location', many=False, read_only=True
) )
customer_detail = CompanyBriefSerializer( customer_detail = CompanyBriefSerializer(
@ -1659,12 +1659,18 @@ class SalesOrderSerialAllocationSerializer(serializers.Serializer):
stock_items = data['stock_items'] stock_items = data['stock_items']
shipment = data['shipment'] shipment = data['shipment']
with transaction.atomic(): allocations = []
for stock_item in stock_items:
# Create a new SalesOrderAllocation for stock_item in stock_items:
order.models.SalesOrderAllocation.objects.create( # Create a new SalesOrderAllocation
allocations.append(
order.models.SalesOrderAllocation(
line=line_item, item=stock_item, quantity=1, shipment=shipment line=line_item, item=stock_item, quantity=1, shipment=shipment
) )
)
with transaction.atomic():
order.models.SalesOrderAllocation.objects.bulk_create(allocations)
class SalesOrderShipmentAllocationSerializer(serializers.Serializer): class SalesOrderShipmentAllocationSerializer(serializers.Serializer):

View File

@ -1,7 +1,10 @@
"""Background tasks for the 'order' app.""" """Background tasks for the 'order' app."""
import logging
from datetime import datetime, timedelta 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 _ from django.utils.translation import gettext_lazy as _
import common.notifications import common.notifications
@ -11,6 +14,8 @@ from InvenTree.tasks import ScheduledTask, scheduled_task
from order.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups from order.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups
from plugin.events import trigger_event from plugin.events import trigger_event
logger = logging.getLogger('inventree')
def notify_overdue_purchase_order(po: order.models.PurchaseOrder): def notify_overdue_purchase_order(po: order.models.PurchaseOrder):
"""Notify users that a PurchaseOrder has just become 'overdue'.""" """Notify users that a PurchaseOrder has just become 'overdue'."""
@ -109,3 +114,31 @@ def check_overdue_sales_orders():
for po in overdue_orders: for po in overdue_orders:
notify_overdue_sales_order(po) 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)