2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-30 04:26:44 +00:00

Merge pull request #2020 from SchrodingersGat/mpn-sorting-fix

Mpn sorting fix
This commit is contained in:
Oliver 2021-08-26 22:06:06 +10:00 committed by GitHub
commit 610c05384b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 24 deletions

View File

@ -0,0 +1,51 @@
# -*- 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.
filter_backends = [
InvenTreeOrderingFilter,
]
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)
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

View File

@ -7,11 +7,12 @@ from __future__ import unicode_literals
from django.conf.urls import url, include 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 generics
from rest_framework import filters, status from rest_framework import filters, status
from rest_framework.response import Response from rest_framework.response import Response
from InvenTree.filters import InvenTreeOrderingFilter
from InvenTree.helpers import str2bool from InvenTree.helpers import str2bool
from InvenTree.api import AttachmentMixin from InvenTree.api import AttachmentMixin
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
@ -144,7 +145,7 @@ class POList(generics.ListCreateAPIView):
return queryset return queryset
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
filters.OrderingFilter, filters.OrderingFilter,
] ]
@ -214,6 +215,14 @@ class POLineItemList(generics.ListCreateAPIView):
queryset = PurchaseOrderLineItem.objects.all() queryset = PurchaseOrderLineItem.objects.all()
serializer_class = POLineItemSerializer 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): def get_serializer(self, *args, **kwargs):
try: try:
@ -226,18 +235,26 @@ class POLineItemList(generics.ListCreateAPIView):
return self.serializer_class(*args, **kwargs) return self.serializer_class(*args, **kwargs)
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
filters.OrderingFilter InvenTreeOrderingFilter
] ]
ordering_field_aliases = {
'MPN': 'part__manufacturer_part__MPN',
'SKU': 'part__SKU',
'part_name': 'part__part__name',
}
ordering_fields = [ ordering_fields = [
'part__part__name', 'MPN',
'part__MPN', 'part_name',
'part__SKU', 'purchase_price',
'reference',
'quantity', 'quantity',
'received', 'received',
'reference',
'SKU',
'total_price',
] ]
search_fields = [ search_fields = [
@ -262,6 +279,14 @@ class POLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = PurchaseOrderLineItem.objects.all() queryset = PurchaseOrderLineItem.objects.all()
serializer_class = POLineItemSerializer serializer_class = POLineItemSerializer
def get_queryset(self):
queryset = super().get_queryset()
queryset = POLineItemSerializer.annotate_queryset(queryset)
return queryset
class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin): class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
""" """
@ -272,7 +297,7 @@ class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
serializer_class = SOAttachmentSerializer serializer_class = SOAttachmentSerializer
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
] ]
filter_fields = [ filter_fields = [
@ -396,7 +421,7 @@ class SOList(generics.ListCreateAPIView):
return queryset return queryset
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
filters.OrderingFilter, filters.OrderingFilter,
] ]
@ -495,7 +520,7 @@ class SOLineItemList(generics.ListCreateAPIView):
return queryset return queryset
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
filters.OrderingFilter filters.OrderingFilter
] ]
@ -580,7 +605,7 @@ class SOAllocationList(generics.ListCreateAPIView):
return queryset return queryset
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
] ]
# Default filterable fields # Default filterable fields
@ -598,7 +623,7 @@ class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
serializer_class = POAttachmentSerializer serializer_class = POAttachmentSerializer
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
] ]
filter_fields = [ filter_fields = [

View File

@ -7,8 +7,9 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _ 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 Case, When, Value
from django.db.models import BooleanField from django.db.models import BooleanField, ExpressionWrapper, F
from rest_framework import serializers from rest_framework import serializers
from sql_util.utils import SubqueryCount from sql_util.utils import SubqueryCount
@ -109,6 +110,23 @@ class POSerializer(InvenTreeModelSerializer):
class POLineItemSerializer(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): def __init__(self, *args, **kwargs):
part_detail = kwargs.pop('part_detail', False) part_detail = kwargs.pop('part_detail', False)
@ -123,6 +141,8 @@ class POLineItemSerializer(InvenTreeModelSerializer):
quantity = serializers.FloatField(default=1) quantity = serializers.FloatField(default=1)
received = serializers.FloatField(default=0) received = serializers.FloatField(default=0)
total_price = serializers.FloatField(read_only=True)
part_detail = PartBriefSerializer(source='get_base_part', many=False, 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) supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True)
@ -158,6 +178,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
'purchase_price_string', 'purchase_price_string',
'destination', 'destination',
'destination_detail', 'destination_detail',
'total_price',
] ]

View File

@ -294,7 +294,7 @@ $("#po-line-table").inventreeTable({
{ {
field: 'part', field: 'part',
sortable: true, sortable: true,
sortName: 'part__part__name', sortName: 'part_name',
title: '{% trans "Part" %}', title: '{% trans "Part" %}',
switchable: false, switchable: false,
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
@ -314,7 +314,7 @@ $("#po-line-table").inventreeTable({
}, },
{ {
sortable: true, sortable: true,
sortName: 'part__SKU', sortName: 'SKU',
field: 'supplier_part_detail.SKU', field: 'supplier_part_detail.SKU',
title: '{% trans "SKU" %}', title: '{% trans "SKU" %}',
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
@ -327,7 +327,7 @@ $("#po-line-table").inventreeTable({
}, },
{ {
sortable: true, sortable: true,
sortName: 'part__MPN', sortName: 'MPN',
field: 'supplier_part_detail.manufacturer_part_detail.MPN', field: 'supplier_part_detail.manufacturer_part_detail.MPN',
title: '{% trans "MPN" %}', title: '{% trans "MPN" %}',
formatter: function(value, row, index, field) { formatter: function(value, row, index, field) {
@ -364,6 +364,7 @@ $("#po-line-table").inventreeTable({
} }
}, },
{ {
field: 'total_price',
sortable: true, sortable: true,
field: 'total_price', field: 'total_price',
title: '{% trans "Total price" %}', title: '{% trans "Total price" %}',
@ -384,7 +385,7 @@ $("#po-line-table").inventreeTable({
} }
}, },
{ {
sortable: true, sortable: false,
field: 'received', field: 'received',
switchable: false, switchable: false,
title: '{% trans "Received" %}', title: '{% trans "Received" %}',

View File

@ -43,6 +43,7 @@ from .serializers import StockItemTestResultSerializer
from InvenTree.views import TreeSerializer from InvenTree.views import TreeSerializer
from InvenTree.helpers import str2bool, isNull from InvenTree.helpers import str2bool, isNull
from InvenTree.api import AttachmentMixin from InvenTree.api import AttachmentMixin
from InvenTree.filters import InvenTreeOrderingFilter
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
@ -882,10 +883,16 @@ class StockList(generics.ListCreateAPIView):
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
filters.OrderingFilter, InvenTreeOrderingFilter,
] ]
ordering_field_aliases = {
'SKU': 'supplier_part__SKU',
}
ordering_fields = [ ordering_fields = [
'batch',
'location',
'part__name', 'part__name',
'part__IPN', 'part__IPN',
'updated', 'updated',
@ -893,10 +900,13 @@ class StockList(generics.ListCreateAPIView):
'expiry_date', 'expiry_date',
'quantity', 'quantity',
'status', 'status',
'SKU',
] ]
ordering = [ ordering = [
'part__name' 'part__name',
'quantity',
'location',
] ]
search_fields = [ search_fields = [

View File

@ -921,8 +921,10 @@ function loadStockTable(table, options) {
return renderLink(text, link); return renderLink(text, link);
} }
}, });
{
col = {
field: 'supplier_part', field: 'supplier_part',
title: '{% trans "Supplier Part" %}', title: '{% trans "Supplier Part" %}',
visible: params['supplier_part_detail'] || false, visible: params['supplier_part_detail'] || false,
@ -944,15 +946,25 @@ function loadStockTable(table, options) {
return renderLink(text, link); return renderLink(text, link);
} }
}); };
if (!options.params.ordering) {
col.sortable = true;
col.sortName = 'SKU';
}
columns.push(col);
col = { col = {
field: 'purchase_price_string', field: 'purchase_price_string',
title: '{% trans "Purchase Price" %}', title: '{% trans "Purchase Price" %}',
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col.sortable = true;
col.sortName = 'purchase_price';
}; };
columns.push(col); columns.push(col);
columns.push({ columns.push({