2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-19 13:35:40 +00:00

[PUI/Feature] Integrate Part "Default Location" into UX (#5972)

* Add default parts to location page

* Fix name strings

* Add Stock Transfer modal

* Add ApiForm Table field

* temp

* Add stock transfer form to part, stock item and location

* All stock operations for Item, Part, and Location added (except order new)

* Add default_location category traversal, and initial PO Line Item Receive form

* .

* Remove debug values

* Added PO line receive form

* Add functionality to PO receive extra fields

* .

* Forgot to bump API version

* Add Category Default to details panel

* Fix stockItem query count

* Fix reviewed issues

* .

* .

* .

* Prevent root category from checking parent for default location
This commit is contained in:
Lavissa
2024-03-15 02:06:18 +01:00
committed by GitHub
parent 6abd33f060
commit 0196dd2f60
22 changed files with 1785 additions and 57 deletions

View File

@ -1,12 +1,18 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 182
INVENTREE_API_VERSION = 183
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v182 - 2024-03-15 : https://github.com/inventree/InvenTree/pull/6714
v183 - 2024-03-14 : https://github.com/inventree/InvenTree/pull/5972
- Adds "category_default_location" annotated field to part serializer
- Adds "part_detail.category_default_location" annotated field to stock item serializer
- Adds "part_detail.category_default_location" annotated field to purchase order line serializer
- Adds "parent_default_location" annotated field to category serializer
v182 - 2024-03-13 : https://github.com/inventree/InvenTree/pull/6714
- Expose ReportSnippet model to the /report/snippet/ API endpoint
- Expose ReportAsset model to the /report/asset/ API endpoint

View File

@ -5,7 +5,16 @@ from decimal import Decimal
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import models, transaction
from django.db.models import BooleanField, Case, ExpressionWrapper, F, Q, Value, When
from django.db.models import (
BooleanField,
Case,
ExpressionWrapper,
F,
Prefetch,
Q,
Value,
When,
)
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
@ -14,6 +23,8 @@ from sql_util.utils import SubqueryCount
import order.models
import part.filters
import part.filters as part_filters
import part.models as part_models
import stock.models
import stock.serializers
from common.serializers import ProjectCodeSerializer
@ -375,6 +386,17 @@ class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer):
- "total_price" = purchase_price * quantity
- "overdue" status (boolean field)
"""
queryset = queryset.prefetch_related(
Prefetch(
'part__part',
queryset=part_models.Part.objects.annotate(
category_default_location=part_filters.annotate_default_location(
'category__'
)
).prefetch_related(None),
)
)
queryset = queryset.annotate(
total_price=ExpressionWrapper(
F('purchase_price') * F('quantity'), output_field=models.DecimalField()

View File

@ -287,6 +287,32 @@ def annotate_category_parts():
)
def annotate_default_location(reference=''):
"""Construct a queryset that finds the closest default location in the part's category tree.
If the part's category has its own default_location, this is returned.
If not, the category tree is traversed until a value is found.
"""
subquery = part.models.PartCategory.objects.filter(
tree_id=OuterRef(f'{reference}tree_id'),
lft__lt=OuterRef(f'{reference}lft'),
rght__gt=OuterRef(f'{reference}rght'),
level__lte=OuterRef(f'{reference}level'),
parent__isnull=False,
)
return Coalesce(
F(f'{reference}default_location'),
Subquery(
subquery.order_by('-level')
.filter(default_location__isnull=False)
.values('default_location')
),
Value(None),
output_field=IntegerField(),
)
def annotate_sub_categories():
"""Construct a queryset annotation which returns the number of subcategories for each provided category."""
subquery = part.models.PartCategory.objects.filter(

View File

@ -81,6 +81,7 @@ class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer):
'url',
'structural',
'icon',
'parent_default_location',
]
def __init__(self, *args, **kwargs):
@ -105,6 +106,10 @@ class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer):
subcategories=part.filters.annotate_sub_categories(),
)
queryset = queryset.annotate(
parent_default_location=part.filters.annotate_default_location('parent__')
)
return queryset
url = serializers.CharField(source='get_absolute_url', read_only=True)
@ -121,6 +126,8 @@ class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer):
child=serializers.DictField(), source='get_path', read_only=True
)
parent_default_location = serializers.IntegerField(read_only=True)
class CategoryTree(InvenTree.serializers.InvenTreeModelSerializer):
"""Serializer for PartCategory tree."""
@ -283,6 +290,7 @@ class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
'pk',
'IPN',
'barcode_hash',
'category_default_location',
'default_location',
'name',
'revision',
@ -314,6 +322,8 @@ class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
self.fields.pop('pricing_min')
self.fields.pop('pricing_max')
category_default_location = serializers.IntegerField(read_only=True)
image = InvenTree.serializers.InvenTreeImageSerializerField(read_only=True)
thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True)
@ -611,6 +621,7 @@ class PartSerializer(
'allocated_to_build_orders',
'allocated_to_sales_orders',
'building',
'category_default_location',
'in_stock',
'ordering',
'required_for_build_orders',
@ -766,6 +777,12 @@ class PartSerializer(
required_for_sales_orders=part.filters.annotate_sales_order_requirements(),
)
queryset = queryset.annotate(
category_default_location=part.filters.annotate_default_location(
'category__'
)
)
return queryset
def get_starred(self, part) -> bool:
@ -805,6 +822,7 @@ class PartSerializer(
unallocated_stock = serializers.FloatField(
read_only=True, label=_('Unallocated Stock')
)
category_default_location = serializers.IntegerField(read_only=True)
variant_stock = serializers.FloatField(read_only=True, label=_('Variant Stock'))
minimum_stock = serializers.FloatField()

View File

@ -6,7 +6,7 @@ from decimal import Decimal
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import transaction
from django.db.models import BooleanField, Case, Count, Q, Value, When
from django.db.models import BooleanField, Case, Count, Prefetch, Q, Value, When
from django.db.models.functions import Coalesce
from django.utils.translation import gettext_lazy as _
@ -20,6 +20,7 @@ import company.models
import InvenTree.helpers
import InvenTree.serializers
import InvenTree.status_codes
import part.filters as part_filters
import part.models as part_models
import stock.filters
from company.serializers import SupplierPartSerializer
@ -289,7 +290,14 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
'location',
'sales_order',
'purchase_order',
'part',
Prefetch(
'part',
queryset=part_models.Part.objects.annotate(
category_default_location=part_filters.annotate_default_location(
'category__'
)
).prefetch_related(None),
),
'part__category',
'part__pricing_data',
'supplier_part',