mirror of
https://github.com/inventree/InvenTree.git
synced 2025-09-13 06:01:35 +00:00
Build source location (#10220)
* Display build source location * Fix docstring * Enhance build availability filter - Take build source location into account - Improve pre-fetch * Enhance type annotations
This commit is contained in:
@@ -1519,7 +1519,10 @@ class BuildLineSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
|
||||
'allocations',
|
||||
'allocations__stock_item',
|
||||
'allocations__stock_item__part',
|
||||
'allocations__stock_item__supplier_part',
|
||||
'allocations__stock_item__supplier_part__manufacturer_part',
|
||||
'allocations__stock_item__location',
|
||||
'allocations__stock_item__tags',
|
||||
'bom_item',
|
||||
'bom_item__part',
|
||||
'bom_item__sub_part',
|
||||
@@ -1603,6 +1606,8 @@ class BuildLineSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
|
||||
location__rght__lte=location.rght,
|
||||
location__level__gte=location.level,
|
||||
)
|
||||
else:
|
||||
location = None
|
||||
|
||||
# Annotate the "in_production" quantity
|
||||
queryset = queryset.annotate(
|
||||
@@ -1623,10 +1628,10 @@ class BuildLineSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
|
||||
reference=ref, filter=stock_filter
|
||||
),
|
||||
allocated_to_sales_orders=part.filters.annotate_sales_order_allocations(
|
||||
reference=ref
|
||||
reference=ref, location=location
|
||||
),
|
||||
allocated_to_build_orders=part.filters.annotate_build_order_allocations(
|
||||
reference=ref
|
||||
reference=ref, location=location
|
||||
),
|
||||
)
|
||||
|
||||
|
@@ -42,7 +42,7 @@ from build.status_codes import BuildStatusGroups
|
||||
from order.status_codes import PurchaseOrderStatusGroups, SalesOrderStatusGroups
|
||||
|
||||
|
||||
def annotate_in_production_quantity(reference=''):
|
||||
def annotate_in_production_quantity(reference: str = '') -> QuerySet:
|
||||
"""Annotate the 'in production' quantity for each part in a queryset.
|
||||
|
||||
- Sum the 'quantity' field for all stock items which are 'in production' for each part.
|
||||
@@ -63,7 +63,7 @@ def annotate_in_production_quantity(reference=''):
|
||||
)
|
||||
|
||||
|
||||
def annotate_scheduled_to_build_quantity(reference: str = ''):
|
||||
def annotate_scheduled_to_build_quantity(reference: str = '') -> QuerySet:
|
||||
"""Annotate the 'scheduled to build' quantity for each part in a queryset.
|
||||
|
||||
- This is total scheduled quantity for all build orders which are 'active'
|
||||
@@ -91,7 +91,7 @@ def annotate_scheduled_to_build_quantity(reference: str = ''):
|
||||
)
|
||||
|
||||
|
||||
def annotate_on_order_quantity(reference: str = ''):
|
||||
def annotate_on_order_quantity(reference: str = '') -> QuerySet:
|
||||
"""Annotate the 'on order' quantity for each part in a queryset.
|
||||
|
||||
Sum the 'remaining quantity' of each line item for any open purchase orders for each part:
|
||||
@@ -137,7 +137,7 @@ def annotate_on_order_quantity(reference: str = ''):
|
||||
)
|
||||
|
||||
|
||||
def annotate_total_stock(reference: str = '', filter: Q = None):
|
||||
def annotate_total_stock(reference: str = '', filter: Q = None) -> QuerySet:
|
||||
"""Annotate 'total stock' quantity against a queryset.
|
||||
|
||||
- This function calculates the 'total stock' for a given part
|
||||
@@ -161,7 +161,7 @@ def annotate_total_stock(reference: str = '', filter: Q = None):
|
||||
)
|
||||
|
||||
|
||||
def annotate_build_order_requirements(reference: str = ''):
|
||||
def annotate_build_order_requirements(reference: str = '') -> QuerySet:
|
||||
"""Annotate the total quantity of each part required for build orders.
|
||||
|
||||
- Only interested in 'active' build orders
|
||||
@@ -179,20 +179,30 @@ def annotate_build_order_requirements(reference: str = ''):
|
||||
)
|
||||
|
||||
|
||||
def annotate_build_order_allocations(reference: str = ''):
|
||||
def annotate_build_order_allocations(reference: str = '', location=None) -> QuerySet:
|
||||
"""Annotate the total quantity of each part allocated to build orders.
|
||||
|
||||
- This function calculates the total part quantity allocated to open build orders
|
||||
- Finds all build order allocations for each part (using the provided filter)
|
||||
- Aggregates the 'allocated quantity' for each relevant build order allocation item
|
||||
|
||||
Args:
|
||||
Arguments:
|
||||
reference: The relationship reference of the part from the current model
|
||||
build_filter: Q object which defines how to filter the allocation items
|
||||
location: If provided, only allocated stock items from this location are considered
|
||||
"""
|
||||
# Build filter only returns 'active' build orders
|
||||
build_filter = Q(build_line__build__status__in=BuildStatusGroups.ACTIVE_CODES)
|
||||
|
||||
if location is not None:
|
||||
# Filter by location (including any child locations)
|
||||
|
||||
build_filter &= Q(
|
||||
stock_item__location__tree_id=location.tree_id,
|
||||
stock_item__location__lft__gte=location.lft,
|
||||
stock_item__location__rght__lte=location.rght,
|
||||
stock_item__location__level__gte=location.level,
|
||||
)
|
||||
|
||||
return Coalesce(
|
||||
SubquerySum(
|
||||
f'{reference}stock_items__allocations__quantity', filter=build_filter
|
||||
@@ -202,7 +212,7 @@ def annotate_build_order_allocations(reference: str = ''):
|
||||
)
|
||||
|
||||
|
||||
def annotate_sales_order_requirements(reference: str = ''):
|
||||
def annotate_sales_order_requirements(reference: str = '') -> QuerySet:
|
||||
"""Annotate the total quantity of each part required for sales orders.
|
||||
|
||||
- Only interested in 'active' sales orders
|
||||
@@ -222,16 +232,16 @@ def annotate_sales_order_requirements(reference: str = ''):
|
||||
)
|
||||
|
||||
|
||||
def annotate_sales_order_allocations(reference: str = ''):
|
||||
def annotate_sales_order_allocations(reference: str = '', location=None) -> QuerySet:
|
||||
"""Annotate the total quantity of each part allocated to sales orders.
|
||||
|
||||
- This function calculates the total part quantity allocated to open sales orders"
|
||||
- Finds all sales order allocations for each part (using the provided filter)
|
||||
- Aggregates the 'allocated quantity' for each relevant sales order allocation item
|
||||
|
||||
Args:
|
||||
Arguments:
|
||||
reference: The relationship reference of the part from the current model
|
||||
order_filter: Q object which defines how to filter the allocation items
|
||||
location: If provided, only allocated stock items from this location are considered
|
||||
"""
|
||||
# Order filter only returns incomplete shipments for open orders
|
||||
order_filter = Q(
|
||||
@@ -239,6 +249,16 @@ def annotate_sales_order_allocations(reference: str = ''):
|
||||
shipment__shipment_date=None,
|
||||
)
|
||||
|
||||
if location is not None:
|
||||
# Filter by location (including any child locations)
|
||||
|
||||
order_filter &= Q(
|
||||
item__location__tree_id=location.tree_id,
|
||||
item__location__lft__gte=location.lft,
|
||||
item__location__rght__lte=location.rght,
|
||||
item__location__level__gte=location.level,
|
||||
)
|
||||
|
||||
return Coalesce(
|
||||
SubquerySum(
|
||||
f'{reference}stock_items__sales_order_allocations__quantity',
|
||||
@@ -249,7 +269,7 @@ def annotate_sales_order_allocations(reference: str = ''):
|
||||
)
|
||||
|
||||
|
||||
def variant_stock_query(reference: str = '', filter: Q = None):
|
||||
def variant_stock_query(reference: str = '', filter: Q = None) -> QuerySet:
|
||||
"""Create a queryset to retrieve all stock items for variant parts under the specified part.
|
||||
|
||||
- Useful for annotating a queryset with aggregated information about variant parts
|
||||
@@ -270,7 +290,7 @@ def variant_stock_query(reference: str = '', filter: Q = None):
|
||||
).filter(stock_filter)
|
||||
|
||||
|
||||
def annotate_variant_quantity(subquery: Q, reference: str = 'quantity'):
|
||||
def annotate_variant_quantity(subquery: Q, reference: str = 'quantity') -> QuerySet:
|
||||
"""Create a subquery annotation for all variant part stock items on the given parent query.
|
||||
|
||||
Args:
|
||||
@@ -290,7 +310,7 @@ def annotate_variant_quantity(subquery: Q, reference: str = 'quantity'):
|
||||
)
|
||||
|
||||
|
||||
def annotate_category_parts():
|
||||
def annotate_category_parts() -> QuerySet:
|
||||
"""Construct a queryset annotation which returns the number of parts in a particular category.
|
||||
|
||||
- Includes parts in subcategories also
|
||||
@@ -317,7 +337,7 @@ def annotate_category_parts():
|
||||
)
|
||||
|
||||
|
||||
def annotate_default_location(reference=''):
|
||||
def annotate_default_location(reference: str = '') -> QuerySet:
|
||||
"""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.
|
||||
@@ -340,7 +360,7 @@ def annotate_default_location(reference=''):
|
||||
)
|
||||
|
||||
|
||||
def annotate_sub_categories():
|
||||
def annotate_sub_categories() -> QuerySet:
|
||||
"""Construct a queryset annotation which returns the number of subcategories for each provided category."""
|
||||
subquery = part.models.PartCategory.objects.filter(
|
||||
tree_id=OuterRef('tree_id'),
|
||||
@@ -504,7 +524,9 @@ def annotate_bom_item_can_build(queryset: QuerySet, reference: str = '') -> Quer
|
||||
PARAMETER_FILTER_OPERATORS: list[str] = ['gt', 'gte', 'lt', 'lte', 'ne', 'icontains']
|
||||
|
||||
|
||||
def filter_by_parameter(queryset, template_id: int, value: str, func: str = ''):
|
||||
def filter_by_parameter(
|
||||
queryset: QuerySet, template_id: int, value: str, func: str = ''
|
||||
) -> QuerySet:
|
||||
"""Filter the given queryset by a given template parameter.
|
||||
|
||||
Parts which do not have a value for the given parameter are excluded.
|
||||
@@ -598,7 +620,9 @@ def filter_by_parameter(queryset, template_id: int, value: str, func: str = ''):
|
||||
return queryset.filter(q).distinct()
|
||||
|
||||
|
||||
def order_by_parameter(queryset, template_id: int, ascending=True):
|
||||
def order_by_parameter(
|
||||
queryset: QuerySet, template_id: int, ascending: bool = True
|
||||
) -> QuerySet:
|
||||
"""Order the given queryset by a given template parameter.
|
||||
|
||||
Parts which do not have a value for the given parameter are ordered last.
|
||||
|
@@ -46,6 +46,7 @@ import NotesPanel from '../../components/panels/NotesPanel';
|
||||
import type { PanelType } from '../../components/panels/Panel';
|
||||
import { PanelGroup } from '../../components/panels/PanelGroup';
|
||||
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
||||
import { RenderStockLocation } from '../../components/render/Stock';
|
||||
import { useBuildOrderFields } from '../../forms/BuildForms';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
@@ -86,6 +87,13 @@ function BuildLinesPanel({
|
||||
isLoading: boolean;
|
||||
hasItems: boolean;
|
||||
}>) {
|
||||
const buildLocation = useInstance({
|
||||
endpoint: ApiEndpoints.stock_location_list,
|
||||
pk: build?.take_from,
|
||||
hasPrimaryKey: true,
|
||||
defaultValue: {}
|
||||
});
|
||||
|
||||
if (isLoading || !build.pk) {
|
||||
return <Skeleton w={'100%'} h={400} animate />;
|
||||
}
|
||||
@@ -94,7 +102,16 @@ function BuildLinesPanel({
|
||||
return <NoItems />;
|
||||
}
|
||||
|
||||
return <BuildLineTable build={build} />;
|
||||
return (
|
||||
<Stack gap='xs'>
|
||||
{buildLocation.instance.pk && (
|
||||
<Alert color='blue' icon={<IconSitemap />} title={t`Source Location`}>
|
||||
<RenderStockLocation instance={buildLocation.instance} />
|
||||
</Alert>
|
||||
)}
|
||||
<BuildLineTable build={build} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function BuildAllocationsPanel({
|
||||
|
Reference in New Issue
Block a user