From 2923589c4a30fa79ef5a9ac899e1aef84da35fe3 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 25 Aug 2021 12:02:25 +1000 Subject: [PATCH 1/7] Fix sortName for purchase order line item table --- InvenTree/order/templates/order/purchase_order_detail.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index e4647ce756..52355f1266 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -294,7 +294,7 @@ $("#po-table").inventreeTable({ { field: 'part', sortable: true, - sortName: 'part__part__name', + sortName: 'part_name', title: '{% trans "Part" %}', switchable: false, formatter: function(value, row, index, field) { @@ -314,7 +314,7 @@ $("#po-table").inventreeTable({ }, { sortable: true, - sortName: 'part__SKU', + sortName: 'SKU', field: 'supplier_part_detail.SKU', title: '{% trans "SKU" %}', formatter: function(value, row, index, field) { @@ -327,7 +327,7 @@ $("#po-table").inventreeTable({ }, { sortable: true, - sortName: 'part__MPN', + sortName: 'MPN', field: 'supplier_part_detail.manufacturer_part_detail.MPN', title: '{% trans "MPN" %}', formatter: function(value, row, index, field) { @@ -358,6 +358,7 @@ $("#po-table").inventreeTable({ { sortable: true, field: 'purchase_price', + sortName: 'price', title: '{% trans "Unit Price" %}', formatter: function(value, row) { return row.purchase_price_string || row.purchase_price; From c9756d30bda40d8979657f15de931fee5d973a76 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 25 Aug 2021 12:04:15 +1000 Subject: [PATCH 2/7] Add a custom OrderingFilter class Needs further work --- InvenTree/InvenTree/filters.py | 34 ++++++++++++++++++++++++++++++++++ InvenTree/order/api.py | 19 ++++++++++--------- 2 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 InvenTree/InvenTree/filters.py diff --git a/InvenTree/InvenTree/filters.py b/InvenTree/InvenTree/filters.py new file mode 100644 index 0000000000..b7ee52fb5a --- /dev/null +++ b/InvenTree/InvenTree/filters.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from rest_framework.filters import OrderingFilter + + +class InvenTreeOrderingFilter(OrderingFilter): + """ + Custom OrderingFilter class which allows aliased filtering of related fields. + + To use, simply specify this filter in the "filter_backends" section. + + Then, you can specify aliasing for ordering fields (or use ordering_fields as normal), e.g. + + filter_backends = [ + InvenTreeOrderingFilter, + ] + + ordering_fields = [ + 'name', + 'quantity', + ('part__SKU', 'SKU') + ] + + Here, ordering by "SKU" will actually order by the "SKU" field on the related part field + + """ + + def get_ordering(self, request, queryset, view): + + ordering = super().get_ordering(request, queryset, view) + + print("ORDERING:", ordering) + return ordering \ No newline at end of file diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index a834989fd9..b4c0022604 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -7,11 +7,12 @@ from __future__ import unicode_literals from django.conf.urls import url, include -from django_filters.rest_framework import DjangoFilterBackend +from django_filters import rest_framework as rest_filters from rest_framework import generics from rest_framework import filters, status from rest_framework.response import Response +from InvenTree.filters import InvenTreeOrderingFilter from InvenTree.helpers import str2bool from InvenTree.api import AttachmentMixin from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus @@ -144,7 +145,7 @@ class POList(generics.ListCreateAPIView): return queryset filter_backends = [ - DjangoFilterBackend, + rest_filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter, ] @@ -226,9 +227,9 @@ class POLineItemList(generics.ListCreateAPIView): return self.serializer_class(*args, **kwargs) filter_backends = [ - DjangoFilterBackend, + rest_filters.DjangoFilterBackend, filters.SearchFilter, - filters.OrderingFilter + InvenTreeOrderingFilter ] ordering_fields = [ @@ -272,7 +273,7 @@ class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin): serializer_class = SOAttachmentSerializer filter_backends = [ - DjangoFilterBackend, + rest_filters.DjangoFilterBackend, ] filter_fields = [ @@ -396,7 +397,7 @@ class SOList(generics.ListCreateAPIView): return queryset filter_backends = [ - DjangoFilterBackend, + rest_filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter, ] @@ -495,7 +496,7 @@ class SOLineItemList(generics.ListCreateAPIView): return queryset filter_backends = [ - DjangoFilterBackend, + rest_filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter ] @@ -580,7 +581,7 @@ class SOAllocationList(generics.ListCreateAPIView): return queryset filter_backends = [ - DjangoFilterBackend, + rest_filters.DjangoFilterBackend, ] # Default filterable fields @@ -598,7 +599,7 @@ class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin): serializer_class = POAttachmentSerializer filter_backends = [ - DjangoFilterBackend, + rest_filters.DjangoFilterBackend, ] filter_fields = [ From 4b8ef2ad626f6518548743b65a7c773bd445ac03 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 25 Aug 2021 17:46:42 +1000 Subject: [PATCH 3/7] Implements custom filtering back end --- InvenTree/InvenTree/filters.py | 39 ++++++++++++++++++++++++---------- InvenTree/order/api.py | 12 ++++++++--- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/InvenTree/InvenTree/filters.py b/InvenTree/InvenTree/filters.py index b7ee52fb5a..cd1b769646 100644 --- a/InvenTree/InvenTree/filters.py +++ b/InvenTree/InvenTree/filters.py @@ -10,25 +10,42 @@ class InvenTreeOrderingFilter(OrderingFilter): To use, simply specify this filter in the "filter_backends" section. - Then, you can specify aliasing for ordering fields (or use ordering_fields as normal), e.g. - filter_backends = [ InvenTreeOrderingFilter, ] - ordering_fields = [ - 'name', - 'quantity', - ('part__SKU', 'SKU') - ] - - Here, ordering by "SKU" will actually order by the "SKU" field on the related part field + Then, specify a ordering_field_aliases attribute: + ordering_field_alises = { + 'name': 'part__part__name', + 'SKU': 'part__SKU', + } """ def get_ordering(self, request, queryset, view): ordering = super().get_ordering(request, queryset, view) - print("ORDERING:", ordering) - return ordering \ No newline at end of file + aliases = getattr(view, 'ordering_field_aliases', None) + + # Attempt to map ordering fields based on provided aliases + if ordering is not None and aliases is not None: + """ + Ordering fields should be mapped to separate fields + """ + + for idx, field in enumerate(ordering): + + reverse = False + + if field.startswith('-'): + field = field[1:] + reverse = True + + if field in aliases: + ordering[idx] = aliases[field] + + if reverse: + ordering[idx] = '-' + ordering[idx] + + return ordering diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index b4c0022604..e728b19226 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -232,10 +232,16 @@ class POLineItemList(generics.ListCreateAPIView): InvenTreeOrderingFilter ] + ordering_field_aliases = { + 'MPN': 'part__manufacturer_part__MPN', + 'SKU': 'part__SKU', + 'part_name': 'part__part__name', + } + ordering_fields = [ - 'part__part__name', - 'part__MPN', - 'part__SKU', + 'part_name', + 'MPN', + 'SKU', 'reference', 'quantity', 'received', From 8660f13ef5b49c27b98213f763875364d2553e25 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 26 Aug 2021 07:50:19 +1000 Subject: [PATCH 4/7] Allow sorting by purchase price (unit price) --- InvenTree/order/api.py | 7 ++++--- InvenTree/order/templates/order/purchase_order_detail.html | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index e728b19226..cb33b3f680 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -239,12 +239,13 @@ class POLineItemList(generics.ListCreateAPIView): } ordering_fields = [ - 'part_name', 'MPN', - 'SKU', - 'reference', + 'part_name', + 'purchase_price', 'quantity', 'received', + 'reference', + 'SKU', ] search_fields = [ diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index 52355f1266..2048599825 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -358,7 +358,6 @@ $("#po-table").inventreeTable({ { sortable: true, field: 'purchase_price', - sortName: 'price', title: '{% trans "Unit Price" %}', formatter: function(value, row) { return row.purchase_price_string || row.purchase_price; From 212a7eeed12d3fe04192c4128d79156e77af8730 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 26 Aug 2021 07:59:47 +1000 Subject: [PATCH 5/7] Disable filtering for total_price (as this is not a database field!) --- InvenTree/order/templates/order/purchase_order_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index 2048599825..df22e60a20 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -364,7 +364,7 @@ $("#po-table").inventreeTable({ } }, { - sortable: true, + sortable: false, title: '{% trans "Total price" %}', formatter: function(value, row) { var total = row.purchase_price * row.quantity; From bad246bca6f0cbc666a9d47e0d5b6d1afee1f94a Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 26 Aug 2021 08:24:31 +1000 Subject: [PATCH 6/7] Fixes for ordering of stock table --- .../order/purchase_order_detail.html | 2 +- InvenTree/stock/api.py | 14 +++++++++++-- InvenTree/templates/js/translated/stock.js | 20 +++++++++++++++---- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index df22e60a20..ef7a8e8703 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -383,7 +383,7 @@ $("#po-table").inventreeTable({ } }, { - sortable: true, + sortable: false, field: 'received', switchable: false, title: '{% trans "Received" %}', diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index cf58c7d4d9..7ff5c55bbe 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -43,6 +43,7 @@ from .serializers import StockItemTestResultSerializer from InvenTree.views import TreeSerializer from InvenTree.helpers import str2bool, isNull from InvenTree.api import AttachmentMixin +from InvenTree.filters import InvenTreeOrderingFilter from decimal import Decimal, InvalidOperation @@ -882,10 +883,16 @@ class StockList(generics.ListCreateAPIView): filter_backends = [ DjangoFilterBackend, filters.SearchFilter, - filters.OrderingFilter, + InvenTreeOrderingFilter, ] + ordering_field_aliases = { + 'SKU': 'supplier_part__SKU', + } + ordering_fields = [ + 'batch', + 'location', 'part__name', 'part__IPN', 'updated', @@ -893,10 +900,13 @@ class StockList(generics.ListCreateAPIView): 'expiry_date', 'quantity', 'status', + 'SKU', ] ordering = [ - 'part__name' + 'part__name', + 'quantity', + 'location', ] search_fields = [ diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index 585cac1310..99c3824cac 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -921,8 +921,10 @@ function loadStockTable(table, options) { return renderLink(text, link); } - }, - { + }); + + col = { + field: 'supplier_part', title: '{% trans "Supplier Part" %}', visible: params['supplier_part_detail'] || false, @@ -944,15 +946,25 @@ function loadStockTable(table, options) { return renderLink(text, link); } - }); + }; + + if (!options.params.ordering) { + col.sortable = true; + col.sortName = 'SKU'; + } + + columns.push(col); col = { field: 'purchase_price_string', title: '{% trans "Purchase Price" %}', }; + if (!options.params.ordering) { - col['sortable'] = true; + col.sortable = true; + col.sortName = 'purchase_price'; }; + columns.push(col); columns.push({ From ac8a0be74abed45e91b6e4c742695454d887f608 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 26 Aug 2021 08:48:19 +1000 Subject: [PATCH 7/7] Enable sorting by total_price --- InvenTree/order/api.py | 17 ++++++++++++++ InvenTree/order/serializers.py | 23 ++++++++++++++++++- .../order/purchase_order_detail.html | 3 ++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index cb33b3f680..eb8ba22ad0 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -215,6 +215,14 @@ class POLineItemList(generics.ListCreateAPIView): queryset = PurchaseOrderLineItem.objects.all() serializer_class = POLineItemSerializer + def get_queryset(self, *args, **kwargs): + + queryset = super().get_queryset(*args, **kwargs) + + queryset = POLineItemSerializer.annotate_queryset(queryset) + + return queryset + def get_serializer(self, *args, **kwargs): try: @@ -246,6 +254,7 @@ class POLineItemList(generics.ListCreateAPIView): 'received', 'reference', 'SKU', + 'total_price', ] search_fields = [ @@ -270,6 +279,14 @@ class POLineItemDetail(generics.RetrieveUpdateDestroyAPIView): queryset = PurchaseOrderLineItem.objects.all() serializer_class = POLineItemSerializer + def get_queryset(self): + + queryset = super().get_queryset() + + queryset = POLineItemSerializer.annotate_queryset(queryset) + + return queryset + class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin): """ diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index e97d19250a..fe23bd2a17 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -7,8 +7,9 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ +from django.db import models from django.db.models import Case, When, Value -from django.db.models import BooleanField +from django.db.models import BooleanField, ExpressionWrapper, F from rest_framework import serializers from sql_util.utils import SubqueryCount @@ -109,6 +110,23 @@ class POSerializer(InvenTreeModelSerializer): class POLineItemSerializer(InvenTreeModelSerializer): + @staticmethod + def annotate_queryset(queryset): + """ + Add some extra annotations to this queryset: + + - Total price = purchase_price * quantity + """ + + queryset = queryset.annotate( + total_price=ExpressionWrapper( + F('purchase_price') * F('quantity'), + output_field=models.DecimalField() + ) + ) + + return queryset + def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) @@ -123,6 +141,8 @@ class POLineItemSerializer(InvenTreeModelSerializer): quantity = serializers.FloatField(default=1) received = serializers.FloatField(default=0) + total_price = serializers.FloatField(read_only=True) + part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True) supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) @@ -158,6 +178,7 @@ class POLineItemSerializer(InvenTreeModelSerializer): 'purchase_price_string', 'destination', 'destination_detail', + 'total_price', ] diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index ef7a8e8703..d5f8dd048a 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -364,7 +364,8 @@ $("#po-table").inventreeTable({ } }, { - sortable: false, + field: 'total_price', + sortable: true, title: '{% trans "Total price" %}', formatter: function(value, row) { var total = row.purchase_price * row.quantity;