Skip to content

Commit 0bed5cf

Browse files
[refactor] PO receive fix (#10174)
* Enhance 'count_queries' helper * Add threshold * Update typing * Better rendering * Improve StockItem - Make model operations more efficient * improve with_mixin - Cache config map against the session cache * Refactor receive_line_item - Pass multiple line items in simultaneously - DB query optimization - Use bulk_create and bulk_update operations * Remove extraneous save call * Fix for unit tests * Fix return type * Fix serializer return type * Refactor part pricing updates * UI tweaks * Use bulk_create * Refactor API and endpoints * Bump API version * Fix unit tests * Fix playwright tests * Remove debug msg * Fix for table filter hover * Adjust unit test
1 parent 0f04c31 commit 0bed5cf

File tree

21 files changed

+464
-258
lines changed

21 files changed

+464
-258
lines changed

src/backend/InvenTree/InvenTree/api_version.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
"""InvenTree API version information."""
22

33
# InvenTree API version
4-
INVENTREE_API_VERSION = 384
4+
INVENTREE_API_VERSION = 385
55

66
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
77

88
INVENTREE_API_TEXT = """
9+
v385 -> 2025-08-15 : https://github.com/inventree/InvenTree/pull/10174
10+
- Adjust return type of PurchaseOrderReceive API serializer
11+
- Now returns list of of the created stock items when receiving
12+
913
v384 -> 2025-08-08 : https://github.com/inventree/InvenTree/pull/9969
1014
- Bump allauth
1115

src/backend/InvenTree/InvenTree/models.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from datetime import datetime
44
from string import Formatter
5+
from typing import Optional
56

67
from django.contrib.auth import get_user_model
78
from django.core.exceptions import ValidationError
@@ -1154,12 +1155,16 @@ def barcode(self) -> str:
11541155
return self.format_barcode()
11551156

11561157
@classmethod
1157-
def lookup_barcode(cls, barcode_hash):
1158+
def lookup_barcode(cls, barcode_hash: str) -> models.Model:
11581159
"""Check if a model instance exists with the specified third-party barcode hash."""
11591160
return cls.objects.filter(barcode_hash=barcode_hash).first()
11601161

11611162
def assign_barcode(
1162-
self, barcode_hash=None, barcode_data=None, raise_error=True, save=True
1163+
self,
1164+
barcode_hash: Optional[str] = None,
1165+
barcode_data: Optional[str] = None,
1166+
raise_error: bool = True,
1167+
save: bool = True,
11631168
):
11641169
"""Assign an external (third-party) barcode to this object."""
11651170
# Must provide either barcode_hash or barcode_data

src/backend/InvenTree/InvenTree/unit_test.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,40 @@
2828

2929
@contextmanager
3030
def count_queries(
31-
msg: Optional[str] = None, log_to_file: bool = False, using: str = 'default'
31+
msg: Optional[str] = None,
32+
log_to_file: bool = False,
33+
using: str = 'default',
34+
threshold: int = 10,
3235
): # pragma: no cover
3336
"""Helper function to count the number of queries executed.
3437
3538
Arguments:
3639
msg: Optional message to print after counting queries
3740
log_to_file: If True, log the queries to a file (default = False)
3841
using: The database connection to use (default = 'default')
42+
threshold: Minimum number of queries to log (default = 10)
3943
"""
44+
t1 = time.time()
45+
4046
with CaptureQueriesContext(connections[using]) as context:
4147
yield
4248

49+
dt = time.time() - t1
50+
4351
n = len(context.captured_queries)
4452

4553
if log_to_file:
4654
with open('queries.txt', 'w', encoding='utf-8') as f:
4755
for q in context.captured_queries:
4856
f.write(str(q['sql']) + '\n\n')
4957

50-
if msg:
51-
print(f'{msg}: Executed {n} queries')
52-
else:
53-
print(f'Executed {n} queries')
58+
output = f'Executed {n} queries in {dt:.4f}s'
59+
60+
if threshold and n >= threshold:
61+
if msg:
62+
print(f'{msg}: {output}')
63+
else:
64+
print(output)
5465

5566

5667
def addUserPermission(user: User, app_name: str, model_name: str, perm: str) -> None:

src/backend/InvenTree/build/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.core.exceptions import ValidationError
77
from django.core.validators import MinValueValidator
88
from django.db import models, transaction
9-
from django.db.models import F, Q, Sum
9+
from django.db.models import F, Q, QuerySet, Sum
1010
from django.db.models.functions import Coalesce
1111
from django.db.models.signals import post_save
1212
from django.dispatch.dispatcher import receiver
@@ -870,7 +870,7 @@ def deallocate_stock(self, build_line=None, output=None):
870870
allocations.delete()
871871

872872
@transaction.atomic
873-
def create_build_output(self, quantity, **kwargs) -> list[stock.models.StockItem]:
873+
def create_build_output(self, quantity, **kwargs) -> QuerySet:
874874
"""Create a new build output against this BuildOrder.
875875
876876
Arguments:
@@ -883,7 +883,7 @@ def create_build_output(self, quantity, **kwargs) -> list[stock.models.StockItem
883883
auto_allocate: Automatically allocate stock with matching serial numbers
884884
885885
Returns:
886-
A list of the created output (StockItem) objects.
886+
A QuerySet of the created output (StockItem) objects.
887887
"""
888888
trackable_parts = self.part.get_trackable_parts()
889889

src/backend/InvenTree/order/api.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from django_filters import rest_framework as rest_filters
1616
from django_ical.views import ICalFeed
1717
from drf_spectacular.types import OpenApiTypes
18-
from drf_spectacular.utils import extend_schema_field
18+
from drf_spectacular.utils import extend_schema, extend_schema_field
1919
from rest_framework import status
2020
from rest_framework.response import Response
2121

@@ -24,6 +24,7 @@
2424
import common.settings
2525
import company.models
2626
import stock.models as stock_models
27+
import stock.serializers as stock_serializers
2728
from data_exporter.mixins import DataExportViewMixin
2829
from generic.states.api import StatusView
2930
from InvenTree.api import BulkUpdateMixin, ListCreateDestroyAPIView, MetadataView
@@ -472,6 +473,7 @@ class PurchaseOrderIssue(PurchaseOrderContextMixin, CreateAPI):
472473
serializer_class = serializers.PurchaseOrderIssueSerializer
473474

474475

476+
@extend_schema(responses={201: stock_serializers.StockItemSerializer(many=True)})
475477
class PurchaseOrderReceive(PurchaseOrderContextMixin, CreateAPI):
476478
"""API endpoint to receive stock items against a PurchaseOrder.
477479
@@ -489,9 +491,18 @@ class PurchaseOrderReceive(PurchaseOrderContextMixin, CreateAPI):
489491
"""
490492

491493
queryset = models.PurchaseOrderLineItem.objects.none()
492-
493494
serializer_class = serializers.PurchaseOrderReceiveSerializer
494495

496+
def create(self, request, *args, **kwargs):
497+
"""Override the create method to handle stock item creation."""
498+
serializer = self.get_serializer(data=request.data)
499+
serializer.is_valid(raise_exception=True)
500+
items = serializer.save()
501+
queryset = stock_serializers.StockItemSerializer.annotate_queryset(items)
502+
response = stock_serializers.StockItemSerializer(queryset, many=True)
503+
504+
return Response(response.data, status=status.HTTP_201_CREATED)
505+
495506

496507
class PurchaseOrderLineItemFilter(LineItemFilter):
497508
"""Custom filters for the PurchaseOrderLineItemList endpoint."""

0 commit comments

Comments
 (0)