From 4ea4456517b607a8d38b36b75907819d0bdf1962 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 17 Feb 2021 23:56:47 +1100 Subject: [PATCH 01/12] Add API LIST endpoint for SalesOrderAllocations --- InvenTree/order/api.py | 84 ++++++++++++++++--- InvenTree/order/serializers.py | 14 ++-- .../templates/order/sales_order_detail.html | 4 +- 3 files changed, 82 insertions(+), 20 deletions(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index ce75a47697..700b97f67a 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -22,10 +22,10 @@ from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrderAttachment from .serializers import POSerializer, POLineItemSerializer, POAttachmentSerializer -from .models import SalesOrder, SalesOrderLineItem +from .models import SalesOrder, SalesOrderLineItem, SalesOrderAllocation from .models import SalesOrderAttachment from .serializers import SalesOrderSerializer, SOLineItemSerializer, SOAttachmentSerializer - +from .serializers import SalesOrderAllocationSerializer class POList(generics.ListCreateAPIView): """ API endpoint for accessing a list of PurchaseOrder objects @@ -427,6 +427,56 @@ class SOLineItemDetail(generics.RetrieveUpdateAPIView): serializer_class = SOLineItemSerializer +class SOAllocationList(generics.ListCreateAPIView): + """ + API endpoint for listing SalesOrderAllocation objects + """ + + queryset = SalesOrderAllocation.objects.all() + serializer_class = SalesOrderAllocationSerializer + + def filter_queryset(self, queryset): + + queryset = super().filter_queryset(queryset) + + # Filter by order + params = self.request.query_params + + # Filter by "part" reference + part = params.get('part', None) + + if part is not None: + queryset = queryset.filter(item__part=part) + + # Filter by "order" reference + order = params.get('order', None) + + if order is not None: + queryset = queryset.filter(line__order=order) + + # Filter by "outstanding" order status + outstanding = params.get('outstanding', None) + + if outstanding is not None: + outstanding = str2bool(outstanding) + + if outstanding: + queryset = queryset.filter(line__order__status__in=SalesOrderStatus.OPEN) + else: + queryset = queryset.exclude(line__order__status__in=SalesOrderStatus.OPEN) + + return queryset + + filter_backends = [ + DjangoFilterBackend, + ] + + # Default filterable fields + filter_fields = [ + 'item', + ] + + class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin): """ API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload) @@ -435,10 +485,6 @@ class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin): queryset = PurchaseOrderAttachment.objects.all() serializer_class = POAttachmentSerializer - filter_fields = [ - 'order', - ] - order_api_urls = [ # API endpoints for purchase orders @@ -453,14 +499,26 @@ order_api_urls = [ url(r'^po-line/$', POLineItemList.as_view(), name='api-po-line-list'), # API endpoints for sales ordesr - url(r'^so/(?P\d+)/$', SODetail.as_view(), name='api-so-detail'), - url(r'so/attachment/', include([ - url(r'^.*$', SOAttachmentList.as_view(), name='api-so-attachment-list'), + url(r'^so/', include([ + url(r'^(?P\d+)/$', SODetail.as_view(), name='api-so-detail'), + url(r'attachment/', include([ + url(r'^.*$', SOAttachmentList.as_view(), name='api-so-attachment-list'), + ])), + + # List all sales orders + url(r'^.*$', SOList.as_view(), name='api-so-list'), ])), - url(r'^so/.*$', SOList.as_view(), name='api-so-list'), - # API endpoints for sales order line items - url(r'^so-line/(?P\d+)/$', SOLineItemDetail.as_view(), name='api-so-line-detail'), - url(r'^so-line/$', SOLineItemList.as_view(), name='api-so-line-list'), + url(r'^so-line/', include([ + url(r'^(?P\d+)/$', SOLineItemDetail.as_view(), name='api-so-line-detail'), + url(r'^$', SOLineItemList.as_view(), name='api-so-line-list'), + ])), + + # API endpoints for sales order allocations + url(r'^so-allocation', include([ + + # List all sales order allocations + url(r'^.*$', SOAllocationList.as_view(), name='api-so-allocation-list'), + ])), ] diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index a04798c303..51a0d6ebf0 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -232,10 +232,12 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): This includes some fields from the related model objects. """ - location_path = serializers.CharField(source='get_location_path') - location_id = serializers.IntegerField(source='get_location') - serial = serializers.CharField(source='get_serial') - quantity = serializers.FloatField() + location_path = serializers.CharField(source='get_location_path', read_only=True) + location = serializers.IntegerField(source='get_location', read_only=True) + part = serializers.PrimaryKeyRelatedField(source='item.part', read_only=True) + order = serializers.PrimaryKeyRelatedField(source='line.order', many=False, read_only=True) + serial = serializers.CharField(source='get_serial', read_only=True) + quantity = serializers.FloatField(read_only=True) class Meta: model = SalesOrderAllocation @@ -245,7 +247,9 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): 'line', 'serial', 'quantity', - 'location_id', + 'order', + 'part', + 'location', 'location_path', 'item', ] diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html index 8e3128e1f3..d4a004a73c 100644 --- a/InvenTree/order/templates/order/sales_order_detail.html +++ b/InvenTree/order/templates/order/sales_order_detail.html @@ -78,10 +78,10 @@ function showAllocationSubTable(index, row, element) { }, }, { - field: 'location_id', + field: 'location', title: 'Location', formatter: function(value, row, index, field) { - return renderLink(row.location_path, `/stock/location/${row.location_id}/`); + return renderLink(row.location_path, `/stock/location/${row.location}/`); }, }, { From 77df82c46d15f8a59a5ea9a32195baf64787817c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 18 Feb 2021 00:07:44 +1100 Subject: [PATCH 02/12] Add optional detail elements to SOAllocation API --- InvenTree/order/api.py | 10 ++++++++++ InvenTree/order/serializers.py | 34 ++++++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 700b97f67a..1b376e9a32 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -435,6 +435,16 @@ class SOAllocationList(generics.ListCreateAPIView): queryset = SalesOrderAllocation.objects.all() serializer_class = SalesOrderAllocationSerializer + def get_serializer(self, *args, **kwargs): + + params = self.request.query_params + + kwargs['part_detail'] = str2bool(params.get('part_detail', False)) + kwargs['item_detail'] = str2bool(params.get('item_detail', False)) + kwargs['order_detail'] = str2bool(params.get('order_detail', False)) + + return self.serializer_class(*args, **kwargs) + def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 51a0d6ebf0..d9d6143253 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -17,6 +17,7 @@ from InvenTree.serializers import InvenTreeAttachmentSerializerField from company.serializers import CompanyBriefSerializer, SupplierPartSerializer from part.serializers import PartBriefSerializer +from stock.serializers import StockItemSerializer from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrderAttachment, SalesOrderAttachment @@ -232,13 +233,33 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): This includes some fields from the related model objects. """ - location_path = serializers.CharField(source='get_location_path', read_only=True) - location = serializers.IntegerField(source='get_location', read_only=True) part = serializers.PrimaryKeyRelatedField(source='item.part', read_only=True) order = serializers.PrimaryKeyRelatedField(source='line.order', many=False, read_only=True) serial = serializers.CharField(source='get_serial', read_only=True) quantity = serializers.FloatField(read_only=True) + # Extra detail fields + order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True) + part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True) + item_detail = StockItemSerializer(source='item', many=False, read_only=True) + + def __init__(self, *args, **kwargs): + + order_detail = kwargs.pop('order_detail', False) + part_detail = kwargs.pop('part_detail', False) + item_detail = kwargs.pop('item_detail', False) + + super().__init__(*args, **kwargs) + + if not order_detail: + self.fields.pop('order_detail') + + if not part_detail: + self.fields.pop('part_detail') + + if not item_detail: + self.fields.pop('item_detail') + class Meta: model = SalesOrderAllocation @@ -247,11 +268,12 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): 'line', 'serial', 'quantity', - 'order', - 'part', - 'location', - 'location_path', 'item', + 'item_detail', + 'order', + 'order_detail', + 'part', + 'part_detail', ] From 228349bea615ecc45eacc22bd3cc06df5dcf45bb Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 18 Feb 2021 00:36:51 +1100 Subject: [PATCH 03/12] Add 'location_detail' filter --- InvenTree/order/api.py | 1 + InvenTree/order/serializers.py | 10 ++- InvenTree/part/templates/part/allocation.html | 10 +++ InvenTree/templates/js/order.js | 82 +++++++++++++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 1b376e9a32..54a88f19be 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -442,6 +442,7 @@ class SOAllocationList(generics.ListCreateAPIView): kwargs['part_detail'] = str2bool(params.get('part_detail', False)) kwargs['item_detail'] = str2bool(params.get('item_detail', False)) kwargs['order_detail'] = str2bool(params.get('order_detail', False)) + kwargs['location_detail'] = str2bool(params.get('location_detail', False)) return self.serializer_class(*args, **kwargs) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index d9d6143253..aa6b05fe39 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -17,7 +17,7 @@ from InvenTree.serializers import InvenTreeAttachmentSerializerField from company.serializers import CompanyBriefSerializer, SupplierPartSerializer from part.serializers import PartBriefSerializer -from stock.serializers import StockItemSerializer +from stock.serializers import StockItemSerializer, LocationSerializer from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrderAttachment, SalesOrderAttachment @@ -237,17 +237,20 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): order = serializers.PrimaryKeyRelatedField(source='line.order', many=False, read_only=True) serial = serializers.CharField(source='get_serial', read_only=True) quantity = serializers.FloatField(read_only=True) + location = serializers.PrimaryKeyRelatedField(source='item.location', many=False, read_only=True) # Extra detail fields order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True) part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True) item_detail = StockItemSerializer(source='item', many=False, read_only=True) + location_detail = LocationSerializer(source='item.location', many=False, read_only=True) def __init__(self, *args, **kwargs): order_detail = kwargs.pop('order_detail', False) part_detail = kwargs.pop('part_detail', False) item_detail = kwargs.pop('item_detail', False) + location_detail = kwargs.pop('location_detail', False) super().__init__(*args, **kwargs) @@ -260,6 +263,9 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): if not item_detail: self.fields.pop('item_detail') + if not location_detail: + self.fields.pop('location_detail') + class Meta: model = SalesOrderAllocation @@ -268,6 +274,8 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): 'line', 'serial', 'quantity', + 'location', + 'location_detail', 'item', 'item_detail', 'order', diff --git a/InvenTree/part/templates/part/allocation.html b/InvenTree/part/templates/part/allocation.html index e574742ad5..93acf6ec4a 100644 --- a/InvenTree/part/templates/part/allocation.html +++ b/InvenTree/part/templates/part/allocation.html @@ -9,6 +9,10 @@

{% trans "Part Stock Allocations" %}

+
+ +
+ @@ -35,6 +39,12 @@ {% block js_ready %} + loadSalesOrderAllocationTable("#sales-order-table", { + params: { + part: {{ part.id }}, + } + }); + $("#build-table").inventreeTable({ columns: [ { diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/order.js index 53063cd709..fcaf54ef63 100644 --- a/InvenTree/templates/js/order.js +++ b/InvenTree/templates/js/order.js @@ -304,3 +304,85 @@ function loadSalesOrderTable(table, options) { ], }); } + + +function loadSalesOrderAllocationTable(table, options={}) { + /** + * Load a table with SalesOrderAllocation items + */ + + options.params = options.params || {}; + + options.params['location_detail'] = true; + options.params['part_detail'] = true; + options.params['item_detail'] = true; + options.params['order_detail'] = true; + + var filters = loadTableFilters("salesorderallocation"); + + for (var key in options.params) { + filters[key] = options.params[key]; + } + + setupFilterList("salesorderallocation", $(table)); + + $(table).inventreeTable({ + url: '{% url "api-so-allocation-list" %}', + queryParams: filters, + name: 'salesorderallocation', + groupBy: false, + original: options.params, + formatNoMatches: function() { return '{% trans "No sales order allocations found" %}'; }, + columns: [ + { + field: 'pk', + visible: false, + switchable: false, + }, + { + field: 'order', + title: '{% trans "Order" %}', + switchable: false, + formatter: function(value, row) { + + var prefix = "{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}"; + + var ref = `${prefix}${row.order_detail.reference}`; + + return renderLink(ref, `/order/sales-order/${row.order}/`); + } + }, + { + field: 'item', + title: '{% trans "Stock Item" %}', + formatter: function(value, row) { + // Render a link to the particular stock item + + var link = `/stock/item/${row.item}/`; + var text = `{% trans "Stock Item" %} ${row.item}`; + + return renderLink(text, link); + } + }, + { + field: 'location', + title: '{% trans "Location" %}', + formatter: function(value, row) { + + if (!value) { + return '{% trans "Location not specified" %}'; + } + + var link = `/stock/location/${value}`; + var text = row.location_detail.description; + + return renderLink(text, link); + } + }, + { + field: 'quantity', + title: '{% trans "Quantity" %}', + } + ] + }); +} \ No newline at end of file From ad8dbb5900f968a04028bf6a6701333d6fb66cef Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 17 Feb 2021 23:56:47 +1100 Subject: [PATCH 04/12] Add API LIST endpoint for SalesOrderAllocations --- InvenTree/order/api.py | 84 ++++++++++++++++--- InvenTree/order/serializers.py | 16 ++-- .../templates/order/sales_order_detail.html | 4 +- 3 files changed, 82 insertions(+), 22 deletions(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 7eda59eacb..d8e189dbfb 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -22,10 +22,10 @@ from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrderAttachment from .serializers import POSerializer, POLineItemSerializer, POAttachmentSerializer -from .models import SalesOrder, SalesOrderLineItem +from .models import SalesOrder, SalesOrderLineItem, SalesOrderAllocation from .models import SalesOrderAttachment from .serializers import SalesOrderSerializer, SOLineItemSerializer, SOAttachmentSerializer - +from .serializers import SalesOrderAllocationSerializer class POList(generics.ListCreateAPIView): """ API endpoint for accessing a list of PurchaseOrder objects @@ -486,6 +486,56 @@ class SOLineItemDetail(generics.RetrieveUpdateAPIView): serializer_class = SOLineItemSerializer +class SOAllocationList(generics.ListCreateAPIView): + """ + API endpoint for listing SalesOrderAllocation objects + """ + + queryset = SalesOrderAllocation.objects.all() + serializer_class = SalesOrderAllocationSerializer + + def filter_queryset(self, queryset): + + queryset = super().filter_queryset(queryset) + + # Filter by order + params = self.request.query_params + + # Filter by "part" reference + part = params.get('part', None) + + if part is not None: + queryset = queryset.filter(item__part=part) + + # Filter by "order" reference + order = params.get('order', None) + + if order is not None: + queryset = queryset.filter(line__order=order) + + # Filter by "outstanding" order status + outstanding = params.get('outstanding', None) + + if outstanding is not None: + outstanding = str2bool(outstanding) + + if outstanding: + queryset = queryset.filter(line__order__status__in=SalesOrderStatus.OPEN) + else: + queryset = queryset.exclude(line__order__status__in=SalesOrderStatus.OPEN) + + return queryset + + filter_backends = [ + DjangoFilterBackend, + ] + + # Default filterable fields + filter_fields = [ + 'item', + ] + + class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin): """ API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload) @@ -494,10 +544,6 @@ class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin): queryset = PurchaseOrderAttachment.objects.all() serializer_class = POAttachmentSerializer - filter_fields = [ - 'order', - ] - order_api_urls = [ # API endpoints for purchase orders @@ -512,14 +558,26 @@ order_api_urls = [ url(r'^po-line/$', POLineItemList.as_view(), name='api-po-line-list'), # API endpoints for sales ordesr - url(r'^so/(?P\d+)/$', SODetail.as_view(), name='api-so-detail'), - url(r'so/attachment/', include([ - url(r'^.*$', SOAttachmentList.as_view(), name='api-so-attachment-list'), + url(r'^so/', include([ + url(r'^(?P\d+)/$', SODetail.as_view(), name='api-so-detail'), + url(r'attachment/', include([ + url(r'^.*$', SOAttachmentList.as_view(), name='api-so-attachment-list'), + ])), + + # List all sales orders + url(r'^.*$', SOList.as_view(), name='api-so-list'), ])), - url(r'^so/.*$', SOList.as_view(), name='api-so-list'), - # API endpoints for sales order line items - url(r'^so-line/(?P\d+)/$', SOLineItemDetail.as_view(), name='api-so-line-detail'), - url(r'^so-line/$', SOLineItemList.as_view(), name='api-so-line-list'), + url(r'^so-line/', include([ + url(r'^(?P\d+)/$', SOLineItemDetail.as_view(), name='api-so-line-detail'), + url(r'^$', SOLineItemList.as_view(), name='api-so-line-list'), + ])), + + # API endpoints for sales order allocations + url(r'^so-allocation', include([ + + # List all sales order allocations + url(r'^.*$', SOAllocationList.as_view(), name='api-so-allocation-list'), + ])), ] diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index a50c72e13e..6879cdc401 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -236,11 +236,12 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): This includes some fields from the related model objects. """ - location_path = serializers.CharField(source='get_location_path') - location_id = serializers.IntegerField(source='get_location') - serial = serializers.CharField(source='get_serial') - po = serializers.CharField(source='get_po') - quantity = serializers.FloatField() + location_path = serializers.CharField(source='get_location_path', read_only=True) + location = serializers.IntegerField(source='get_location', read_only=True) + part = serializers.PrimaryKeyRelatedField(source='item.part', read_only=True) + order = serializers.PrimaryKeyRelatedField(source='line.order', many=False, read_only=True) + serial = serializers.CharField(source='get_serial', read_only=True) + quantity = serializers.FloatField(read_only=True) class Meta: model = SalesOrderAllocation @@ -250,9 +251,10 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): 'line', 'serial', 'quantity', - 'location_id', + 'order', + 'part', + 'location', 'location_path', - 'po', 'item', ] diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html index 3bbd458da5..b760409fe4 100644 --- a/InvenTree/order/templates/order/sales_order_detail.html +++ b/InvenTree/order/templates/order/sales_order_detail.html @@ -81,10 +81,10 @@ function showAllocationSubTable(index, row, element) { }, }, { - field: 'location_id', + field: 'location', title: 'Location', formatter: function(value, row, index, field) { - return renderLink(row.location_path, `/stock/location/${row.location_id}/`); + return renderLink(row.location_path, `/stock/location/${row.location}/`); }, }, { From caf52c6ce55793eb8ebb692339eaea1f60155638 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 18 Feb 2021 00:07:44 +1100 Subject: [PATCH 05/12] Add optional detail elements to SOAllocation API --- InvenTree/order/api.py | 10 ++++++++++ InvenTree/order/serializers.py | 35 +++++++++++++++++++++++++++------- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index d8e189dbfb..e50bc376cb 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -494,6 +494,16 @@ class SOAllocationList(generics.ListCreateAPIView): queryset = SalesOrderAllocation.objects.all() serializer_class = SalesOrderAllocationSerializer + def get_serializer(self, *args, **kwargs): + + params = self.request.query_params + + kwargs['part_detail'] = str2bool(params.get('part_detail', False)) + kwargs['item_detail'] = str2bool(params.get('item_detail', False)) + kwargs['order_detail'] = str2bool(params.get('order_detail', False)) + + return self.serializer_class(*args, **kwargs) + def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 6879cdc401..db39eec8b3 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -17,7 +17,7 @@ from InvenTree.serializers import InvenTreeAttachmentSerializerField from company.serializers import CompanyBriefSerializer, SupplierPartSerializer from part.serializers import PartBriefSerializer -from stock.serializers import LocationBriefSerializer +from stock.serializers import LocationBriefSerializer, StockItemSerializer from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrderAttachment, SalesOrderAttachment @@ -236,13 +236,33 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): This includes some fields from the related model objects. """ - location_path = serializers.CharField(source='get_location_path', read_only=True) - location = serializers.IntegerField(source='get_location', read_only=True) part = serializers.PrimaryKeyRelatedField(source='item.part', read_only=True) order = serializers.PrimaryKeyRelatedField(source='line.order', many=False, read_only=True) serial = serializers.CharField(source='get_serial', read_only=True) quantity = serializers.FloatField(read_only=True) + # Extra detail fields + order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True) + part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True) + item_detail = StockItemSerializer(source='item', many=False, read_only=True) + + def __init__(self, *args, **kwargs): + + order_detail = kwargs.pop('order_detail', False) + part_detail = kwargs.pop('part_detail', False) + item_detail = kwargs.pop('item_detail', False) + + super().__init__(*args, **kwargs) + + if not order_detail: + self.fields.pop('order_detail') + + if not part_detail: + self.fields.pop('part_detail') + + if not item_detail: + self.fields.pop('item_detail') + class Meta: model = SalesOrderAllocation @@ -251,11 +271,12 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): 'line', 'serial', 'quantity', - 'order', - 'part', - 'location', - 'location_path', 'item', + 'item_detail', + 'order', + 'order_detail', + 'part', + 'part_detail', ] From 0b8a50cd92170c1e8f781f78205d5a1976f3a7c9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 18 Feb 2021 00:36:51 +1100 Subject: [PATCH 06/12] Add 'location_detail' filter --- InvenTree/order/api.py | 1 + InvenTree/order/serializers.py | 11 ++- InvenTree/part/templates/part/allocation.html | 13 +++ InvenTree/templates/js/order.js | 82 +++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index e50bc376cb..d5569a95f7 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -501,6 +501,7 @@ class SOAllocationList(generics.ListCreateAPIView): kwargs['part_detail'] = str2bool(params.get('part_detail', False)) kwargs['item_detail'] = str2bool(params.get('item_detail', False)) kwargs['order_detail'] = str2bool(params.get('order_detail', False)) + kwargs['location_detail'] = str2bool(params.get('location_detail', False)) return self.serializer_class(*args, **kwargs) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index db39eec8b3..44fffc19df 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -17,7 +17,8 @@ from InvenTree.serializers import InvenTreeAttachmentSerializerField from company.serializers import CompanyBriefSerializer, SupplierPartSerializer from part.serializers import PartBriefSerializer -from stock.serializers import LocationBriefSerializer, StockItemSerializer +from stock.serializers import LocationBriefSerializer +from stock.serializers import StockItemSerializer, StockItemSerializer from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrderAttachment, SalesOrderAttachment @@ -240,17 +241,20 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): order = serializers.PrimaryKeyRelatedField(source='line.order', many=False, read_only=True) serial = serializers.CharField(source='get_serial', read_only=True) quantity = serializers.FloatField(read_only=True) + location = serializers.PrimaryKeyRelatedField(source='item.location', many=False, read_only=True) # Extra detail fields order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True) part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True) item_detail = StockItemSerializer(source='item', many=False, read_only=True) + location_detail = LocationSerializer(source='item.location', many=False, read_only=True) def __init__(self, *args, **kwargs): order_detail = kwargs.pop('order_detail', False) part_detail = kwargs.pop('part_detail', False) item_detail = kwargs.pop('item_detail', False) + location_detail = kwargs.pop('location_detail', False) super().__init__(*args, **kwargs) @@ -263,6 +267,9 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): if not item_detail: self.fields.pop('item_detail') + if not location_detail: + self.fields.pop('location_detail') + class Meta: model = SalesOrderAllocation @@ -271,6 +278,8 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): 'line', 'serial', 'quantity', + 'location', + 'location_detail', 'item', 'item_detail', 'order', diff --git a/InvenTree/part/templates/part/allocation.html b/InvenTree/part/templates/part/allocation.html index 803df3906d..20543a9ca0 100644 --- a/InvenTree/part/templates/part/allocation.html +++ b/InvenTree/part/templates/part/allocation.html @@ -12,6 +12,13 @@ {% endblock %} {% block details %} + +

{% trans "Part Stock Allocations" %}

+ +
{% trans "Order" %}
+ +
+ @@ -39,6 +46,12 @@ {% block js_ready %} {{ block.super }} + loadSalesOrderAllocationTable("#sales-order-table", { + params: { + part: {{ part.id }}, + } + }); + $("#build-table").inventreeTable({ columns: [ { diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/order.js index fa60ebdf6d..acfec1b7da 100644 --- a/InvenTree/templates/js/order.js +++ b/InvenTree/templates/js/order.js @@ -310,3 +310,85 @@ function loadSalesOrderTable(table, options) { ], }); } + + +function loadSalesOrderAllocationTable(table, options={}) { + /** + * Load a table with SalesOrderAllocation items + */ + + options.params = options.params || {}; + + options.params['location_detail'] = true; + options.params['part_detail'] = true; + options.params['item_detail'] = true; + options.params['order_detail'] = true; + + var filters = loadTableFilters("salesorderallocation"); + + for (var key in options.params) { + filters[key] = options.params[key]; + } + + setupFilterList("salesorderallocation", $(table)); + + $(table).inventreeTable({ + url: '{% url "api-so-allocation-list" %}', + queryParams: filters, + name: 'salesorderallocation', + groupBy: false, + original: options.params, + formatNoMatches: function() { return '{% trans "No sales order allocations found" %}'; }, + columns: [ + { + field: 'pk', + visible: false, + switchable: false, + }, + { + field: 'order', + title: '{% trans "Order" %}', + switchable: false, + formatter: function(value, row) { + + var prefix = "{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}"; + + var ref = `${prefix}${row.order_detail.reference}`; + + return renderLink(ref, `/order/sales-order/${row.order}/`); + } + }, + { + field: 'item', + title: '{% trans "Stock Item" %}', + formatter: function(value, row) { + // Render a link to the particular stock item + + var link = `/stock/item/${row.item}/`; + var text = `{% trans "Stock Item" %} ${row.item}`; + + return renderLink(text, link); + } + }, + { + field: 'location', + title: '{% trans "Location" %}', + formatter: function(value, row) { + + if (!value) { + return '{% trans "Location not specified" %}'; + } + + var link = `/stock/location/${value}`; + var text = row.location_detail.description; + + return renderLink(text, link); + } + }, + { + field: 'quantity', + title: '{% trans "Quantity" %}', + } + ] + }); +} \ No newline at end of file From 10ecddf9b4eaca263fb759ced10acea53a4f3b23 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 18 Jun 2021 17:25:24 +1000 Subject: [PATCH 07/12] Fixes after rebase --- InvenTree/order/serializers.py | 4 ++-- InvenTree/part/templates/part/allocation.html | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 44fffc19df..9efbf947bb 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -18,7 +18,7 @@ from InvenTree.serializers import InvenTreeAttachmentSerializerField from company.serializers import CompanyBriefSerializer, SupplierPartSerializer from part.serializers import PartBriefSerializer from stock.serializers import LocationBriefSerializer -from stock.serializers import StockItemSerializer, StockItemSerializer +from stock.serializers import StockItemSerializer, LocationSerializer from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrderAttachment, SalesOrderAttachment @@ -43,7 +43,7 @@ class POSerializer(InvenTreeModelSerializer): """ Add extra information to the queryset - - Number of liens in the PurchaseOrder + - Number of lines in the PurchaseOrder - Overdue status of the PurchaseOrder """ diff --git a/InvenTree/part/templates/part/allocation.html b/InvenTree/part/templates/part/allocation.html index 20543a9ca0..6d2826775e 100644 --- a/InvenTree/part/templates/part/allocation.html +++ b/InvenTree/part/templates/part/allocation.html @@ -13,10 +13,12 @@ {% block details %} -

{% trans "Part Stock Allocations" %}

+

{% trans "Build Order Allocations" %}

{% trans "Order" %}
+

{% trans "Sales Order Allocations" %}

+
From 245c9bfd282233a8e0626a95e3ebc0c5332f74f1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 18 Jun 2021 19:08:54 +1000 Subject: [PATCH 08/12] Enhancement for the BuildItem API / serializer - Add optional "part_detail" information - Add optional "build_detail" information - Add optional "location_detail" information --- InvenTree/build/api.py | 10 ++++ InvenTree/build/serializers.py | 34 +++++++++++-- InvenTree/templates/js/build.js | 85 ++++++++++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 7 deletions(-) diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index 160642281a..cd4dbbd95c 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -165,6 +165,16 @@ class BuildItemList(generics.ListCreateAPIView): serializer_class = BuildItemSerializer + def get_serializer(self, *args, **kwargs): + + params = self.request.query_params + + kwargs['part_detail'] = str2bool(params.get('part_detail', False)) + kwargs['build_detail'] = str2bool(params.get('build_detail', False)) + kwargs['location_detail'] = str2bool(params.get('location_detail', False)) + + return self.serializer_class(*args, **kwargs) + def get_queryset(self): """ Override the queryset method, to allow filtering by stock_item.part diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 629422f6e5..d8573cfa70 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -13,7 +13,8 @@ from rest_framework import serializers from InvenTree.serializers import InvenTreeModelSerializer from stock.serializers import StockItemSerializerBrief -from part.serializers import PartBriefSerializer +from stock.serializers import LocationSerializer +from part.serializers import PartSerializer, PartBriefSerializer from .models import Build, BuildItem @@ -99,22 +100,45 @@ class BuildItemSerializer(InvenTreeModelSerializer): bom_part = serializers.IntegerField(source='bom_item.sub_part.pk', read_only=True) part = serializers.IntegerField(source='stock_item.part.pk', read_only=True) - part_name = serializers.CharField(source='stock_item.part.full_name', read_only=True) - part_thumb = serializers.CharField(source='getStockItemThumbnail', read_only=True) + location = serializers.IntegerField(source='stock_item.location.pk', read_only=True) + + # Extra (optional) detail fields + part_detail = PartSerializer(source='stock_item.part', many=False, read_only=True) + build_detail = BuildSerializer(source='build', many=False, read_only=True) stock_item_detail = StockItemSerializerBrief(source='stock_item', read_only=True) + location_detail = LocationSerializer(source='stock_item.location', read_only=True) quantity = serializers.FloatField() + def __init__(self, *args, **kwargs): + + build_detail = kwargs.pop('build_detail', False) + part_detail = kwargs.pop('part_detail', False) + location_detail = kwargs.pop('location_detail', False) + + super().__init__(*args, **kwargs) + + if not build_detail: + self.fields.pop('build_detail') + + if not part_detail: + self.fields.pop('part_detail') + + if not location_detail: + self.fields.pop('location_detail') + class Meta: model = BuildItem fields = [ 'pk', 'bom_part', 'build', + 'build_detail', 'install_into', + 'location', + 'location_detail', 'part', - 'part_name', - 'part_thumb', + 'part_detail', 'stock_item', 'stock_item_detail', 'quantity' diff --git a/InvenTree/templates/js/build.js b/InvenTree/templates/js/build.js index 9523d24d39..ab787e207d 100644 --- a/InvenTree/templates/js/build.js +++ b/InvenTree/templates/js/build.js @@ -155,6 +155,85 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) { } +function loadBuildOrderAllocationTable(table, options={}) { + /** + * Load a table showing all the BuildOrder allocations for a given part + */ + + options.params['part_detail'] = true; + options.params['build_detail'] = true; + options.params['location_detail'] = true; + + var filters = loadTableFilters("buildorderallocation"); + + for (var key in options.params) { + filters[key] = options.params[key]; + } + + setupFilterList("buildorderallocation", $(table)); + + $(table).inventreeTable({ + url: '{% url "api-build-item-list" %}', + queryParams: filters, + name: 'buildorderallocation', + groupBy: false, + original: options.params, + formatNoMatches: function() { + return '{% trans "No build order allocations found" %}' + }, + columns: [ + { + field: 'pk', + visible: false, + switchable: false, + }, + { + field: 'build', + title: '{% trans "Build Order" %}', + formatter: function(value, row) { + var prefix = "{% settings_value 'BUILDORDER_REFERENCE_PREFIX' %}"; + + var ref = `${prefix}${row.build_detail.reference}`; + + return renderLink(ref, `/build/${row.build}/`); + } + }, + { + field: 'item', + title: '{% trans "Stock Item" %}', + formatter: function(value, row) { + // Render a link to the particular stock item + + var link = `/stock/item/${row.stock_item}/`; + var text = `{% trans "Stock Item" %} ${row.stock_item}`; + + return renderLink(text, link); + } + }, + { + field: 'location', + title: '{% trans "Location" %}', + formatter: function(value, row) { + + if (!value) { + return '{% trans "Location not specified" %}'; + } + + var link = `/stock/location/${value}`; + var text = row.location_detail.description; + + return renderLink(text, link); + } + }, + { + field: 'quantity', + title: '{% trans "Quantity" %}', + } + ] + }); +} + + function loadBuildOutputAllocationTable(buildInfo, output, options={}) { /* * Load the "allocation table" for a particular build output. @@ -347,6 +426,8 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { var params = { build: buildId, + part_detail: true, + location_detail: true, } if (output) { @@ -466,8 +547,8 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { title: '{% trans "Part" %}', formatter: function(value, row) { - var html = imageHoverIcon(row.part_thumb); - html += renderLink(row.part_name, `/part/${value}/`); + var html = imageHoverIcon(row.part_detail.thumbnail); + html += renderLink(row.part_detail.full_name, `/part/${value}/`); return html; } }, From 778aa0314d3975a2fa29c6f5c551e06b8ff366bd Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 18 Jun 2021 19:18:35 +1000 Subject: [PATCH 09/12] Load and display build order allocation items via AJAX --- InvenTree/part/templates/part/allocation.html | 58 ++++++------------- InvenTree/part/templates/part/part_base.html | 9 +++ InvenTree/templates/js/build.js | 3 + InvenTree/templates/js/order.js | 3 + InvenTree/templates/js/tables.js | 2 +- 5 files changed, 33 insertions(+), 42 deletions(-) diff --git a/InvenTree/part/templates/part/allocation.html b/InvenTree/part/templates/part/allocation.html index 6d2826775e..e78456ea3a 100644 --- a/InvenTree/part/templates/part/allocation.html +++ b/InvenTree/part/templates/part/allocation.html @@ -8,43 +8,30 @@ {% endblock %} {% block heading %} -{% trans "Part Stock Allocations" %} +{% trans "Build Order Allocations" %} {% endblock %} {% block details %} -

{% trans "Build Order Allocations" %}

-
-

{% trans "Sales Order Allocations" %}

+{% endblock %} -
+{% block pre_content_panel %} - - - - - - -{% for allocation in part.build_order_allocations %} - - - - - -{% endfor %} -{% for allocation in part.sales_order_allocations %} - - - - - -{% endfor %} -
{% trans "Order" %}{% trans "Stock Item" %}{% trans "Quantity" %}
{% trans "Build Order" %}: {{ allocation.build }}{% trans "Stock Item" %}: {{ allocation.stock_item }}{% decimal allocation.quantity %}
{% trans "Sales Order" %}: {{ allocation.line.order }}{% trans "Stock Item" %}: {{ allocation.item }}{% decimal allocation.quantity %}
+
+
+

{% trans "Sales Order Allocations" %}

+
+ +
+
+
+
{% endblock %} + {% block js_ready %} {{ block.super }} @@ -54,21 +41,10 @@ } }); - $("#build-table").inventreeTable({ - columns: [ - { - title: '{% trans "Order" %}', - sortable: true, - }, - { - title: '{% trans "Stock Item" %}', - sortable: true, - }, - { - title: '{% trans "Quantity" %}', - sortable: true, - } - ] + loadBuildOrderAllocationTable("#build-order-table", { + params: { + part: {{ part.id }}, + } }); {% endblock %} diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 2e1cb2e71f..7e1d33bdea 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -195,8 +195,13 @@ +{% block pre_content_panel %} + +{% endblock %} +
+

{% block heading %} @@ -210,7 +215,11 @@ {% endblock %}

+
+{% block post_content_panel %} + +{% endblock %} {% endblock %} diff --git a/InvenTree/templates/js/build.js b/InvenTree/templates/js/build.js index ab787e207d..e8af981817 100644 --- a/InvenTree/templates/js/build.js +++ b/InvenTree/templates/js/build.js @@ -177,6 +177,8 @@ function loadBuildOrderAllocationTable(table, options={}) { queryParams: filters, name: 'buildorderallocation', groupBy: false, + search: false, + paginationVAlign: 'bottom', original: options.params, formatNoMatches: function() { return '{% trans "No build order allocations found" %}' @@ -189,6 +191,7 @@ function loadBuildOrderAllocationTable(table, options={}) { }, { field: 'build', + switchable: false, title: '{% trans "Build Order" %}', formatter: function(value, row) { var prefix = "{% settings_value 'BUILDORDER_REFERENCE_PREFIX' %}"; diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/order.js index acfec1b7da..649357b083 100644 --- a/InvenTree/templates/js/order.js +++ b/InvenTree/templates/js/order.js @@ -337,6 +337,8 @@ function loadSalesOrderAllocationTable(table, options={}) { queryParams: filters, name: 'salesorderallocation', groupBy: false, + search: false, + paginationVAlign: 'bottom', original: options.params, formatNoMatches: function() { return '{% trans "No sales order allocations found" %}'; }, columns: [ @@ -347,6 +349,7 @@ function loadSalesOrderAllocationTable(table, options={}) { }, { field: 'order', + switchable: false, title: '{% trans "Order" %}', switchable: false, formatter: function(value, row) { diff --git a/InvenTree/templates/js/tables.js b/InvenTree/templates/js/tables.js index 26d7ee4da9..645e54fcbb 100644 --- a/InvenTree/templates/js/tables.js +++ b/InvenTree/templates/js/tables.js @@ -135,7 +135,7 @@ $.fn.inventreeTable = function(options) { // Pagingation options (can be server-side or client-side as specified by the caller) options.pagination = true; - options.paginationVAlign = 'both'; + options.paginationVAlign = options.paginationVAlign || 'both'; options.pageSize = inventreeLoad(varName, 25); options.pageList = [25, 50, 100, 250, 'all']; options.totalField = 'count'; From 3fb57abe66b3f838ba022bf13a38de0ad8766cfe Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 18 Jun 2021 19:39:06 +1000 Subject: [PATCH 10/12] Error catching --- InvenTree/build/api.py | 11 +++++++---- InvenTree/company/api.py | 35 +++++++++-------------------------- InvenTree/order/api.py | 27 ++++++++++++--------------- 3 files changed, 28 insertions(+), 45 deletions(-) diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index cd4dbbd95c..1cb973fe05 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -167,11 +167,14 @@ class BuildItemList(generics.ListCreateAPIView): def get_serializer(self, *args, **kwargs): - params = self.request.query_params + try: + params = self.request.query_params - kwargs['part_detail'] = str2bool(params.get('part_detail', False)) - kwargs['build_detail'] = str2bool(params.get('build_detail', False)) - kwargs['location_detail'] = str2bool(params.get('location_detail', False)) + kwargs['part_detail'] = str2bool(params.get('part_detail', False)) + kwargs['build_detail'] = str2bool(params.get('build_detail', False)) + kwargs['location_detail'] = str2bool(params.get('location_detail', False)) + except AttributeError: + pass return self.serializer_class(*args, **kwargs) diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index ff8b6d667b..83aef7531b 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -103,17 +103,11 @@ class ManufacturerPartList(generics.ListCreateAPIView): # Do we wish to include extra detail? try: - kwargs['part_detail'] = str2bool(self.request.query_params.get('part_detail', None)) - except AttributeError: - pass + params = self.request.query_params - try: - kwargs['manufacturer_detail'] = str2bool(self.request.query_params.get('manufacturer_detail', None)) - except AttributeError: - pass - - try: - kwargs['pretty'] = str2bool(self.request.query_params.get('pretty', None)) + kwargs['part_detail'] = str2bool(params.get('part_detail', None)) + kwargs['manufacturer_detail'] = str2bool(params.get('manufacturer_detail', None)) + kwargs['pretty'] = str2bool(params.get('pretty', None)) except AttributeError: pass @@ -252,22 +246,11 @@ class SupplierPartList(generics.ListCreateAPIView): # Do we wish to include extra detail? try: - kwargs['part_detail'] = str2bool(self.request.query_params.get('part_detail', None)) - except AttributeError: - pass - - try: - kwargs['supplier_detail'] = str2bool(self.request.query_params.get('supplier_detail', None)) - except AttributeError: - pass - - try: - kwargs['manufacturer_detail'] = str2bool(self.request.query_params.get('manufacturer_detail', None)) - except AttributeError: - pass - - try: - kwargs['pretty'] = str2bool(self.request.query_params.get('pretty', None)) + params = self.request.query_params + kwargs['part_detail'] = str2bool(params.get('part_detail', None)) + kwargs['supplier_detail'] = str2bool(params.get('supplier_detail', None)) + kwargs['manufacturer_detail'] = str2bool(self.params.get('manufacturer_detail', None)) + kwargs['pretty'] = str2bool(params.get('pretty', None)) except AttributeError: pass diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 4460125557..10173017bb 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -423,17 +423,11 @@ class SOLineItemList(generics.ListCreateAPIView): def get_serializer(self, *args, **kwargs): try: - kwargs['part_detail'] = str2bool(self.request.query_params.get('part_detail', False)) - except AttributeError: - pass + params = self.request.query_params - try: - kwargs['order_detail'] = str2bool(self.request.query_params.get('order_detail', False)) - except AttributeError: - pass - - try: - kwargs['allocations'] = str2bool(self.request.query_params.get('allocations', False)) + kwargs['part_detail'] = str2bool(params.get('part_detail', False)) + kwargs['order_detail'] = str2bool(params.get('order_detail', False)) + kwargs['allocations'] = str2bool(params.get('allocations', False)) except AttributeError: pass @@ -497,12 +491,15 @@ class SOAllocationList(generics.ListCreateAPIView): def get_serializer(self, *args, **kwargs): - params = self.request.query_params + try: + params = self.request.query_params - kwargs['part_detail'] = str2bool(params.get('part_detail', False)) - kwargs['item_detail'] = str2bool(params.get('item_detail', False)) - kwargs['order_detail'] = str2bool(params.get('order_detail', False)) - kwargs['location_detail'] = str2bool(params.get('location_detail', False)) + kwargs['part_detail'] = str2bool(params.get('part_detail', False)) + kwargs['item_detail'] = str2bool(params.get('item_detail', False)) + kwargs['order_detail'] = str2bool(params.get('order_detail', False)) + kwargs['location_detail'] = str2bool(params.get('location_detail', False)) + except ArithmeticError: + pass return self.serializer_class(*args, **kwargs) From e68a4abdf285979d952b5eeb1816710554d4fb54 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 18 Jun 2021 19:51:54 +1000 Subject: [PATCH 11/12] Error: wrong error --- InvenTree/order/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 10173017bb..6661bd568b 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -498,7 +498,7 @@ class SOAllocationList(generics.ListCreateAPIView): kwargs['item_detail'] = str2bool(params.get('item_detail', False)) kwargs['order_detail'] = str2bool(params.get('order_detail', False)) kwargs['location_detail'] = str2bool(params.get('location_detail', False)) - except ArithmeticError: + except AttributeError: pass return self.serializer_class(*args, **kwargs) From 9a4ccedf5c8a4129313b5398b199a44726608d7d Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 18 Jun 2021 21:53:15 +1000 Subject: [PATCH 12/12] Adds a management command to rebuild MPTT models - Important to do after importing records / fixtures! - Otherwise very strange things might happen especially when you try to use the API... --- .../management/commands/rebuild_models.py | 60 +++++++++++++++++++ tasks.py | 12 +++- 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 InvenTree/InvenTree/management/commands/rebuild_models.py diff --git a/InvenTree/InvenTree/management/commands/rebuild_models.py b/InvenTree/InvenTree/management/commands/rebuild_models.py new file mode 100644 index 0000000000..2a60da9365 --- /dev/null +++ b/InvenTree/InvenTree/management/commands/rebuild_models.py @@ -0,0 +1,60 @@ +""" +Custom management command to rebuild all MPTT models + +- This is crucial after importing any fixtures, etc +""" + +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + """ + Rebuild all database models which leverage the MPTT structure. + """ + + def handle(self, *args, **kwargs): + + # Part model + try: + print("Rebuilding Part objects") + + from part.models import Part + Part.objects.rebuild() + except: + print("Error rebuilding Part objects") + + # Part category + try: + print("Rebuilding PartCategory objects") + + from part.models import PartCategory + PartCategory.objects.rebuild() + except: + print("Error rebuilding PartCategory objects") + + # StockItem model + try: + print("Rebuilding StockItem objects") + + from stock.models import StockItem + StockItem.objects.rebuild() + except: + print("Error rebuilding StockItem objects") + + # StockLocation model + try: + print("Rebuilding StockLocation objects") + + from stock.models import StockLocation + StockLocation.objects.rebuild() + except: + print("Error rebuilding StockLocation objects") + + # Build model + try: + print("Rebuilding Build objects") + + from build.models import Build + Build.objects.rebuild() + except: + print("Error rebuilding Build objects") diff --git a/tasks.py b/tasks.py index 5aab30651a..3c4a2175e1 100644 --- a/tasks.py +++ b/tasks.py @@ -129,6 +129,14 @@ def wait(c): manage(c, "wait_for_db") +@task +def rebuild(c): + """ + Rebuild database models with MPTT structures + """ + + manage(c, "rebuild_models") + @task def migrate(c): """ @@ -311,7 +319,7 @@ def export_records(c, filename='data.json'): print("Data export completed") -@task(help={'filename': 'Input filename'}) +@task(help={'filename': 'Input filename'}, post=[rebuild]) def import_records(c, filename='data.json'): """ Import database records from a file @@ -354,7 +362,7 @@ def import_records(c, filename='data.json'): print("Data import completed") -@task +@task(post=[rebuild]) def import_fixtures(c): """ Import fixture data into the database.