diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 2bb8e60673..3c06a7732e 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,12 +1,16 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 361 +INVENTREE_API_VERSION = 362 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v362 -> 2025-07-02 : https://github.com/inventree/InvenTree/pull/9939 + - Allow filtering of BuildItem API by "location" of StockItem + - Allow filtering of SalesOrderAllocation API by "location" of StockItem + v361 -> 2025-07-03 : https://github.com/inventree/InvenTree/pull/9944 - Enable SalesOrderAllocation list to be filtered by part IPN value - Enable SalesOrderAllocation list to be ordered by part MPN value diff --git a/src/backend/InvenTree/build/api.py b/src/backend/InvenTree/build/api.py index 3bb1636ca5..00e9fa606b 100644 --- a/src/backend/InvenTree/build/api.py +++ b/src/backend/InvenTree/build/api.py @@ -16,6 +16,7 @@ from rest_framework.response import Response import build.serializers import common.models import part.models as part_models +import stock.models as stock_models import stock.serializers from build.models import Build, BuildItem, BuildLine from build.status_codes import BuildStatus, BuildStatusGroups @@ -830,6 +831,18 @@ class BuildItemFilter(rest_filters.FilterSet): return queryset.exclude(install_into=None) return queryset.filter(install_into=None) + location = rest_filters.ModelChoiceFilter( + queryset=stock_models.StockLocation.objects.all(), + label=_('Location'), + method='filter_location', + ) + + @extend_schema_field(serializers.IntegerField(help_text=_('Location'))) + def filter_location(self, queryset, name, location): + """Filter the queryset based on the specified location.""" + locations = location.get_descendants(include_self=True) + return queryset.filter(stock_item__location__in=locations) + class BuildItemList(DataExportViewMixin, BulkDeleteMixin, ListCreateAPI): """API endpoint for accessing a list of BuildItem objects. diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py index 04279c4e98..2847933e8f 100644 --- a/src/backend/InvenTree/order/api.py +++ b/src/backend/InvenTree/order/api.py @@ -23,6 +23,7 @@ import build.models import common.models import common.settings import company.models +import stock.models as stock_models from data_exporter.mixins import DataExportViewMixin from generic.states.api import StatusView from InvenTree.api import BulkUpdateMixin, ListCreateDestroyAPIView, MetadataView @@ -1178,6 +1179,20 @@ class SalesOrderAllocationFilter(rest_filters.FilterSet): return queryset.exclude(shipment=None) return queryset.filter(shipment=None) + location = rest_filters.ModelChoiceFilter( + queryset=stock_models.StockLocation.objects.all(), + label=_('Location'), + method='filter_location', + ) + + @extend_schema_field( + rest_framework.serializers.IntegerField(help_text=_('Location')) + ) + def filter_location(self, queryset, name, location): + """Filter by the location of the allocated StockItem.""" + locations = location.get_descendants(include_self=True) + return queryset.filter(item__location__in=locations) + class SalesOrderAllocationMixin: """Mixin class for SalesOrderAllocation endpoints.""" diff --git a/src/frontend/src/tables/Filter.tsx b/src/frontend/src/tables/Filter.tsx index bf238f55bf..627600644e 100644 --- a/src/frontend/src/tables/Filter.tsx +++ b/src/frontend/src/tables/Filter.tsx @@ -350,3 +350,14 @@ export function PartCategoryFilter(): TableFilter { modelRenderer: (instance: any) => instance.name }; } + +export function StockLocationFilter(): TableFilter { + return { + name: 'location', + label: t`Location`, + description: t`Filter by stock location`, + apiUrl: apiUrl(ApiEndpoints.stock_location_list), + model: ModelType.stocklocation, + modelRenderer: (instance: any) => instance.name + }; +} diff --git a/src/frontend/src/tables/build/BuildAllocatedStockTable.tsx b/src/frontend/src/tables/build/BuildAllocatedStockTable.tsx index 502590fbb6..4239db665f 100644 --- a/src/frontend/src/tables/build/BuildAllocatedStockTable.tsx +++ b/src/frontend/src/tables/build/BuildAllocatedStockTable.tsx @@ -21,6 +21,7 @@ import { ReferenceColumn, StatusColumn } from '../ColumnRenderers'; +import { StockLocationFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions'; @@ -69,6 +70,8 @@ export default function BuildAllocatedStockTable({ }); } + filters.push(StockLocationFilter()); + return filters; }, [partId]); diff --git a/src/frontend/src/tables/build/BuildOutputTable.tsx b/src/frontend/src/tables/build/BuildOutputTable.tsx index a744de4367..bff67ae96b 100644 --- a/src/frontend/src/tables/build/BuildOutputTable.tsx +++ b/src/frontend/src/tables/build/BuildOutputTable.tsx @@ -57,7 +57,8 @@ import { SerialFilter, SerialGTEFilter, SerialLTEFilter, - StatusFilterOptions + StatusFilterOptions, + StockLocationFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; import { type RowAction, RowEditAction, RowViewAction } from '../RowActions'; @@ -363,6 +364,7 @@ export default function BuildOutputTable({ description: t`Filter by stock status`, choiceFunction: StatusFilterOptions(ModelType.stockitem) }, + StockLocationFilter(), HasBatchCodeFilter(), BatchFilter(), IsSerializedFilter(), diff --git a/src/frontend/src/tables/sales/SalesOrderAllocationTable.tsx b/src/frontend/src/tables/sales/SalesOrderAllocationTable.tsx index 92f477f82b..f91057850e 100644 --- a/src/frontend/src/tables/sales/SalesOrderAllocationTable.tsx +++ b/src/frontend/src/tables/sales/SalesOrderAllocationTable.tsx @@ -27,6 +27,7 @@ import { ReferenceColumn, StatusColumn } from '../ColumnRenderers'; +import { StockLocationFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions'; @@ -84,7 +85,8 @@ export default function SalesOrderAllocationTable({ name: 'assigned_to_shipment', label: t`Assigned to Shipment`, description: t`Show allocations assigned to a shipment` - } + }, + StockLocationFilter() ]; if (!!partId) { diff --git a/src/frontend/src/tables/stock/StockItemTable.tsx b/src/frontend/src/tables/stock/StockItemTable.tsx index 30a9e4e1d7..17ab55f1e7 100644 --- a/src/frontend/src/tables/stock/StockItemTable.tsx +++ b/src/frontend/src/tables/stock/StockItemTable.tsx @@ -486,7 +486,7 @@ export function StockItemTable({ [showLocation, showPricing] ); - const tableFilters = useMemo( + const tableFilters: TableFilter[] = useMemo( () => stockItemTableFilters({ enableExpiry: stockExpiryEnabled