mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 04:55:44 +00:00
BOM / Build Updates (#6604)
* Fix for build line table - Prefill source location correctly * Refactor API filtering for BomList - Make use of RestFilter class * Add "external stock" field to BomItem serializer * Simplify custom filtering * Add "structural" column to part table * Update BOM tables: - Display indication of "external stock" * Annotate "external_stock" to part serializer - Update PartTable [PUI] * Annotate BuildLine serializer too * BuildLine endpoint - filter available stock based on source build order - If build order is specified, and has a source location, use that to filter available stock! * Add message above build line table * Update BuildLineTable * Bump API version
This commit is contained in:
@ -314,11 +314,21 @@ class BuildLineEndpoint:
|
||||
queryset = BuildLine.objects.all()
|
||||
serializer_class = build.serializers.BuildLineSerializer
|
||||
|
||||
def get_source_build(self) -> Build:
|
||||
"""Return the source Build object for the BuildLine queryset.
|
||||
|
||||
This source build is used to filter the available stock for each BuildLine.
|
||||
|
||||
- If this is a "detail" view, use the build associated with the line
|
||||
- If this is a "list" view, use the build associated with the request
|
||||
"""
|
||||
raise NotImplementedError("get_source_build must be implemented in the child class")
|
||||
|
||||
def get_queryset(self):
|
||||
"""Override queryset to select-related and annotate"""
|
||||
queryset = super().get_queryset()
|
||||
|
||||
queryset = build.serializers.BuildLineSerializer.annotate_queryset(queryset)
|
||||
source_build = self.get_source_build()
|
||||
queryset = build.serializers.BuildLineSerializer.annotate_queryset(queryset, build=source_build)
|
||||
|
||||
return queryset
|
||||
|
||||
@ -353,10 +363,26 @@ class BuildLineList(BuildLineEndpoint, ListCreateAPI):
|
||||
'bom_item__reference',
|
||||
]
|
||||
|
||||
def get_source_build(self) -> Build:
|
||||
"""Return the target build for the BuildLine queryset."""
|
||||
|
||||
try:
|
||||
build_id = self.request.query_params.get('build', None)
|
||||
if build_id:
|
||||
build = Build.objects.get(pk=build_id)
|
||||
return build
|
||||
except (Build.DoesNotExist, AttributeError, ValueError):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
class BuildLineDetail(BuildLineEndpoint, RetrieveUpdateDestroyAPI):
|
||||
"""API endpoint for detail view of a BuildLine object."""
|
||||
pass
|
||||
|
||||
def get_source_build(self) -> Build:
|
||||
"""Return the target source location for the BuildLine queryset."""
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class BuildOrderContextMixin:
|
||||
|
@ -1083,6 +1083,7 @@ class BuildLineSerializer(InvenTreeModelSerializer):
|
||||
'available_substitute_stock',
|
||||
'available_variant_stock',
|
||||
'total_available_stock',
|
||||
'external_stock',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
@ -1124,15 +1125,23 @@ class BuildLineSerializer(InvenTreeModelSerializer):
|
||||
available_substitute_stock = serializers.FloatField(read_only=True)
|
||||
available_variant_stock = serializers.FloatField(read_only=True)
|
||||
total_available_stock = serializers.FloatField(read_only=True)
|
||||
external_stock = serializers.FloatField(read_only=True)
|
||||
|
||||
@staticmethod
|
||||
def annotate_queryset(queryset):
|
||||
def annotate_queryset(queryset, build=None):
|
||||
"""Add extra annotations to the queryset:
|
||||
|
||||
- allocated: Total stock quantity allocated against this build line
|
||||
- available: Total stock available for allocation against this build line
|
||||
- on_order: Total stock on order for this build line
|
||||
- in_production: Total stock currently in production for this build line
|
||||
|
||||
Arguments:
|
||||
queryset: The queryset to annotate
|
||||
build: The build order to filter against (optional)
|
||||
|
||||
Note: If the 'build' is provided, we can use it to filter available stock, depending on the specified location for the build
|
||||
|
||||
"""
|
||||
queryset = queryset.select_related(
|
||||
'build', 'bom_item',
|
||||
@ -1169,6 +1178,18 @@ class BuildLineSerializer(InvenTreeModelSerializer):
|
||||
|
||||
ref = 'bom_item__sub_part__'
|
||||
|
||||
stock_filter = None
|
||||
|
||||
if build is not None and build.take_from is not None:
|
||||
location = build.take_from
|
||||
# Filter by locations below the specified location
|
||||
stock_filter = Q(
|
||||
location__tree_id=location.tree_id,
|
||||
location__lft__gte=location.lft,
|
||||
location__rght__lte=location.rght,
|
||||
location__level__gte=location.level,
|
||||
)
|
||||
|
||||
# Annotate the "in_production" quantity
|
||||
queryset = queryset.annotate(
|
||||
in_production=part.filters.annotate_in_production_quantity(reference=ref)
|
||||
@ -1181,10 +1202,8 @@ class BuildLineSerializer(InvenTreeModelSerializer):
|
||||
)
|
||||
|
||||
# Annotate the "available" quantity
|
||||
# TODO: In the future, this should be refactored.
|
||||
# TODO: Note that part.serializers.BomItemSerializer also has a similar annotation
|
||||
queryset = queryset.alias(
|
||||
total_stock=part.filters.annotate_total_stock(reference=ref),
|
||||
total_stock=part.filters.annotate_total_stock(reference=ref, filter=stock_filter),
|
||||
allocated_to_sales_orders=part.filters.annotate_sales_order_allocations(reference=ref),
|
||||
allocated_to_build_orders=part.filters.annotate_build_order_allocations(reference=ref),
|
||||
)
|
||||
@ -1197,11 +1216,21 @@ class BuildLineSerializer(InvenTreeModelSerializer):
|
||||
)
|
||||
)
|
||||
|
||||
external_stock_filter = Q(location__external=True)
|
||||
|
||||
if stock_filter:
|
||||
external_stock_filter &= stock_filter
|
||||
|
||||
# Add 'external stock' annotations
|
||||
queryset = queryset.annotate(
|
||||
external_stock=part.filters.annotate_total_stock(reference=ref, filter=external_stock_filter)
|
||||
)
|
||||
|
||||
ref = 'bom_item__substitutes__part__'
|
||||
|
||||
# Extract similar information for any 'substitute' parts
|
||||
queryset = queryset.alias(
|
||||
substitute_stock=part.filters.annotate_total_stock(reference=ref),
|
||||
substitute_stock=part.filters.annotate_total_stock(reference=ref, filter=stock_filter),
|
||||
substitute_build_allocations=part.filters.annotate_build_order_allocations(reference=ref),
|
||||
substitute_sales_allocations=part.filters.annotate_sales_order_allocations(reference=ref)
|
||||
)
|
||||
@ -1215,7 +1244,7 @@ class BuildLineSerializer(InvenTreeModelSerializer):
|
||||
)
|
||||
|
||||
# Annotate the queryset with 'available variant stock' information
|
||||
variant_stock_query = part.filters.variant_stock_query(reference='bom_item__sub_part__')
|
||||
variant_stock_query = part.filters.variant_stock_query(reference='bom_item__sub_part__', filter=stock_filter)
|
||||
|
||||
queryset = queryset.alias(
|
||||
variant_stock_total=part.filters.annotate_variant_quantity(variant_stock_query, reference='quantity'),
|
||||
|
@ -200,6 +200,11 @@
|
||||
<div id='build-lines-toolbar'>
|
||||
{% include "filter_list.html" with id='buildlines' %}
|
||||
</div>
|
||||
{% if build.take_from %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% trans "Available stock has been filtered based on specified source location for this build order" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<table class='table table-striped table-condensed' id='build-lines-table' data-toolbar='#build-lines-toolbar'></table>
|
||||
</div>
|
||||
</div>
|
||||
@ -374,6 +379,9 @@ onPanelLoad('allocate', function() {
|
||||
"#build-lines-table",
|
||||
{{ build.pk }},
|
||||
{
|
||||
{% if build.take_from %}
|
||||
location: {{ build.take_from.pk }},
|
||||
{% endif %}
|
||||
{% if build.project_code %}
|
||||
project_code: {{ build.project_code.pk }},
|
||||
{% endif %}
|
||||
|
Reference in New Issue
Block a user