mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +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