diff --git a/docs/docs/assets/images/build/allocated_stock_table.png b/docs/docs/assets/images/build/allocated_stock_table.png new file mode 100644 index 0000000000..676f4492f9 Binary files /dev/null and b/docs/docs/assets/images/build/allocated_stock_table.png differ diff --git a/docs/docs/build/build.md b/docs/docs/build/build.md index 22f4084761..594b98846a 100644 --- a/docs/docs/build/build.md +++ b/docs/docs/build/build.md @@ -26,14 +26,6 @@ To navigate to the Build Order display, select *Build* from the main navigation {% include "img.html" %} {% endwith %} -#### Tree View - -*Tree View* also provides a tabulated view of Build Orders. Orders are displayed in a hierarchical manner, showing any parent / child relationships between different build orders. - -{% with id="build_tree", url="build/build_tree.png", description="Build Tree" %} -{% include "img.html" %} -{% endwith %} - #### Calendar View *Calendar View* shows a calendar display with upcoming build orders, based on the various dates specified for each build. @@ -121,9 +113,9 @@ The *Build Details* tab provides an overview of the Build Order: {% include "img.html" %} {% endwith %} -### Allocate Stock +### Line Items -The *Allocate Stock* tab provides an interface to allocate required stock (as specified by the BOM) to the build: +The *Line Items* tab provides an interface to allocate required stock (as specified by the BOM) to the build: {% with id="build_allocate", url="build/build_allocate.png", description="Allocation tab" %} {% include "img.html" %} @@ -131,8 +123,13 @@ The *Allocate Stock* tab provides an interface to allocate required stock (as sp The allocation table (as shown above) shows the stock allocation progress for this build. In the example above, there are two BOM lines, which have been partially allocated. -!!! info "Completed Builds" - The *Allocate Stock* tab is not available if the build has been completed! +### Allocated Stock + +The *Allocated Stock* tab displays all stock items which have been *allocated* to this build order. These stock items are reserved for this build, and will be consumed when the build is completed: + +{% with id="allocated_stock_table", url="build/allocated_stock_table.png", description="Allocated Stock Table" %} +{% include "img.html" %} +{% endwith %} ### Consumed Stock diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 19dc675787..bdb4330d90 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 = 218 +INVENTREE_API_VERSION = 219 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v219 - 2024-07-11 : https://github.com/inventree/InvenTree/pull/7611 + - Adds new fields to the BuildItem API endpoints + - Adds new ordering / filtering options to the BuildItem API endpoints + v218 - 2024-07-11 : https://github.com/inventree/InvenTree/pull/7619 - Adds "can_build" field to the BomItem API diff --git a/src/backend/InvenTree/build/api.py b/src/backend/InvenTree/build/api.py index 8944ecb9a7..e21c60037d 100644 --- a/src/backend/InvenTree/build/api.py +++ b/src/backend/InvenTree/build/api.py @@ -8,12 +8,11 @@ from django.contrib.auth.models import User from rest_framework.exceptions import ValidationError -from django_filters.rest_framework import DjangoFilterBackend from django_filters import rest_framework as rest_filters from importer.mixins import DataExportViewMixin -from InvenTree.api import MetadataView +from InvenTree.api import BulkDeleteMixin, MetadataView from generic.states.api import StatusView from InvenTree.helpers import str2bool, isNull from build.status_codes import BuildStatus, BuildStatusGroups @@ -546,15 +545,17 @@ class BuildItemFilter(rest_filters.FilterSet): return queryset.filter(install_into=None) -class BuildItemList(DataExportViewMixin, ListCreateAPI): +class BuildItemList(DataExportViewMixin, BulkDeleteMixin, ListCreateAPI): """API endpoint for accessing a list of BuildItem objects. - GET: Return list of objects - POST: Create a new BuildItem object """ + queryset = BuildItem.objects.all() serializer_class = build.serializers.BuildItemSerializer filterset_class = BuildItemFilter + filter_backends = SEARCH_ORDER_FILTER_ALIAS def get_serializer(self, *args, **kwargs): """Returns a BuildItemSerializer instance based on the request.""" @@ -571,7 +572,7 @@ class BuildItemList(DataExportViewMixin, ListCreateAPI): def get_queryset(self): """Override the queryset method, to allow filtering by stock_item.part.""" - queryset = BuildItem.objects.all() + queryset = super().get_queryset() queryset = queryset.select_related( 'build_line', @@ -607,8 +608,25 @@ class BuildItemList(DataExportViewMixin, ListCreateAPI): return queryset - filter_backends = [ - DjangoFilterBackend, + ordering_fields = [ + 'part', + 'sku', + 'quantity', + 'location', + 'reference', + ] + + ordering_field_aliases = { + 'part': 'stock_item__part__name', + 'sku': 'stock_item__supplier_part__SKU', + 'location': 'stock_item__location__name', + 'reference': 'build_line__bom_item__reference', + } + + search_fields = [ + 'stock_item__supplier_part__SKU', + 'stock_item__part__name', + 'build_line__bom_item__reference', ] diff --git a/src/backend/InvenTree/build/serializers.py b/src/backend/InvenTree/build/serializers.py index 83b046c0a7..ef84817123 100644 --- a/src/backend/InvenTree/build/serializers.py +++ b/src/backend/InvenTree/build/serializers.py @@ -26,8 +26,9 @@ from stock.serializers import StockItemSerializerBrief, LocationSerializer import common.models from common.serializers import ProjectCodeSerializer from importer.mixins import DataImportExportSerializerMixin +import company.serializers import part.filters -from part.serializers import BomItemSerializer, PartSerializer, PartBriefSerializer +import part.serializers as part_serializers from users.serializers import OwnerSerializer from .models import Build, BuildLine, BuildItem @@ -85,7 +86,7 @@ class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTre status_text = serializers.CharField(source='get_status_display', read_only=True) - part_detail = PartBriefSerializer(source='part', many=False, read_only=True) + part_detail = part_serializers.PartBriefSerializer(source='part', many=False, read_only=True) part_name = serializers.CharField(source='part.name', read_only=True, label=_('Part Name')) @@ -1062,10 +1063,13 @@ class BuildItemSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali # These fields are only used for data export export_only_fields = [ 'build_reference', - 'bom_reference', 'sku', 'mpn', 'location_name', + 'part_id', + 'part_name', + 'part_ipn', + 'available_quantity', ] class Meta: @@ -1085,6 +1089,7 @@ class BuildItemSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali 'location_detail', 'part_detail', 'stock_item_detail', + 'supplier_part_detail', # The following fields are only used for data export 'bom_reference', @@ -1092,27 +1097,12 @@ class BuildItemSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali 'location_name', 'mpn', 'sku', + 'part_id', + 'part_name', + 'part_ipn', + 'available_quantity', ] - # Export-only fields - sku = serializers.CharField(source='stock_item.supplier_part.SKU', label=_('Supplier Part Number'), read_only=True) - mpn = serializers.CharField(source='stock_item.supplier_part.manufacturer_part.MPN', label=_('Manufacturer Part Number'), read_only=True) - location_name = serializers.CharField(source='stock_item.location.name', label=_('Location Name'), read_only=True) - build_reference = serializers.CharField(source='build.reference', label=_('Build Reference'), read_only=True) - bom_reference = serializers.CharField(source='build_line.bom_item.reference', label=_('BOM Reference'), read_only=True) - - # Annotated fields - build = serializers.PrimaryKeyRelatedField(source='build_line.build', many=False, read_only=True) - - # Extra (optional) detail fields - part_detail = PartBriefSerializer(source='stock_item.part', many=False, read_only=True, pricing=False) - stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True) - location = serializers.PrimaryKeyRelatedField(source='stock_item.location', many=False, read_only=True) - location_detail = LocationSerializer(source='stock_item.location', read_only=True) - build_detail = BuildSerializer(source='build_line.build', many=False, read_only=True) - - quantity = InvenTreeDecimalField() - def __init__(self, *args, **kwargs): """Determine which extra details fields should be included""" part_detail = kwargs.pop('part_detail', True) @@ -1134,6 +1124,32 @@ class BuildItemSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali if not build_detail: self.fields.pop('build_detail', None) + # Export-only fields + sku = serializers.CharField(source='stock_item.supplier_part.SKU', label=_('Supplier Part Number'), read_only=True) + mpn = serializers.CharField(source='stock_item.supplier_part.manufacturer_part.MPN', label=_('Manufacturer Part Number'), read_only=True) + location_name = serializers.CharField(source='stock_item.location.name', label=_('Location Name'), read_only=True) + build_reference = serializers.CharField(source='build.reference', label=_('Build Reference'), read_only=True) + bom_reference = serializers.CharField(source='build_line.bom_item.reference', label=_('BOM Reference'), read_only=True) + + # Part detail fields + part_id = serializers.PrimaryKeyRelatedField(source='stock_item.part', label=_('Part ID'), many=False, read_only=True) + part_name = serializers.CharField(source='stock_item.part.name', label=_('Part Name'), read_only=True) + part_ipn = serializers.CharField(source='stock_item.part.IPN', label=_('Part IPN'), read_only=True) + + # Annotated fields + build = serializers.PrimaryKeyRelatedField(source='build_line.build', many=False, read_only=True) + + # Extra (optional) detail fields + part_detail = part_serializers.PartBriefSerializer(source='stock_item.part', many=False, read_only=True, pricing=False) + stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True) + location = serializers.PrimaryKeyRelatedField(source='stock_item.location', many=False, read_only=True) + location_detail = LocationSerializer(source='stock_item.location', read_only=True) + build_detail = BuildSerializer(source='build_line.build', many=False, read_only=True) + supplier_part_detail = company.serializers.SupplierPartSerializer(source='stock_item.supplier_part', many=False, read_only=True) + + quantity = InvenTreeDecimalField(label=_('Allocated Quantity')) + available_quantity = InvenTreeDecimalField(source='stock_item.quantity', read_only=True, label=_('Available Quantity')) + class BuildLineSerializer(DataImportExportSerializerMixin, InvenTreeModelSerializer): """Serializer for a BuildItem object.""" @@ -1217,8 +1233,8 @@ class BuildLineSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali bom_item = serializers.PrimaryKeyRelatedField(label=_('BOM Item'), read_only=True) # Foreign key fields - bom_item_detail = BomItemSerializer(source='bom_item', many=False, read_only=True, pricing=False) - part_detail = PartSerializer(source='bom_item.sub_part', many=False, read_only=True, pricing=False) + bom_item_detail = part_serializers.BomItemSerializer(source='bom_item', many=False, read_only=True, pricing=False) + part_detail = part_serializers.PartSerializer(source='bom_item.sub_part', many=False, read_only=True, pricing=False) allocations = BuildItemSerializer(many=True, read_only=True) # Annotated (calculated) fields diff --git a/src/backend/InvenTree/build/templates/build/detail.html b/src/backend/InvenTree/build/templates/build/detail.html index 7daa1dc218..6cabe1f01c 100644 --- a/src/backend/InvenTree/build/templates/build/detail.html +++ b/src/backend/InvenTree/build/templates/build/detail.html @@ -174,7 +174,7 @@