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:
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
|
@ -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',
|
||||
|
Reference in New Issue
Block a user