mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	Merge remote-tracking branch 'inventree/master'
This commit is contained in:
		| @@ -104,21 +104,23 @@ function removePurchaseOrderLineItem(e) { | |||||||
| function loadPurchaseOrderTable(table, options) { | function loadPurchaseOrderTable(table, options) { | ||||||
|     /* Create a purchase-order table */ |     /* Create a purchase-order table */ | ||||||
|  |  | ||||||
|     var params = options.params || {}; |     options.params = options.params || {}; | ||||||
|  |  | ||||||
|  |     options.params['supplier_detail'] = true; | ||||||
|  |  | ||||||
|     var filters = loadTableFilters("order"); |     var filters = loadTableFilters("order"); | ||||||
|  |  | ||||||
|     for (var key in params) { |     for (var key in options.params) { | ||||||
|         filters[key] = params[key]; |         filters[key] = options.params[key]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     setupFilterList("order", table); |     setupFilterList("order", $(table)); | ||||||
|  |  | ||||||
|     $(table).inventreeTable({ |     $(table).inventreeTable({ | ||||||
|         url: options.url, |         url: options.url, | ||||||
|         queryParams: filters, |         queryParams: filters, | ||||||
|         groupBy: false, |         groupBy: false, | ||||||
|         original: params, |         original: options.params, | ||||||
|         formatNoMatches: function() { return "No purchase orders found"; }, |         formatNoMatches: function() { return "No purchase orders found"; }, | ||||||
|         columns: [ |         columns: [ | ||||||
|             { |             { | ||||||
| @@ -131,15 +133,15 @@ function loadPurchaseOrderTable(table, options) { | |||||||
|                 field: 'reference', |                 field: 'reference', | ||||||
|                 title: 'Purchase Order', |                 title: 'Purchase Order', | ||||||
|                 formatter: function(value, row, index, field) { |                 formatter: function(value, row, index, field) { | ||||||
|                     return renderLink(value, "/order/purchase-order/" + row.pk + "/"); |                     return renderLink(value, `/order/purchase-order/${row.pk}/`); | ||||||
|                 } |                 } | ||||||
|             },   |             },   | ||||||
|             { |             { | ||||||
|                 sortable: true, |                 sortable: true, | ||||||
|                 field: 'supplier', |                 field: 'supplier_detail', | ||||||
|                 title: 'Supplier', |                 title: 'Supplier', | ||||||
|                 formatter: function(value, row, index, field) { |                 formatter: function(value, row, index, field) { | ||||||
|                     return imageHoverIcon(row.supplier__image) + renderLink(row.supplier__name, '/company/' + value + '/purchase-orders/'); |                     return imageHoverIcon(row.supplier_detail.image) + renderLink(row.supplier_detail.name, `/company/${row.supplier}/purchase-orders/`); | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
| @@ -162,7 +164,7 @@ function loadPurchaseOrderTable(table, options) { | |||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|                 sortable: true, |                 sortable: true, | ||||||
|                 field: 'lines', |                 field: 'line_items', | ||||||
|                 title: 'Items' |                 title: 'Items' | ||||||
|             }, |             }, | ||||||
|         ], |         ], | ||||||
|   | |||||||
| @@ -8,14 +8,10 @@ from __future__ import unicode_literals | |||||||
| from django_filters.rest_framework import DjangoFilterBackend | from django_filters.rest_framework import DjangoFilterBackend | ||||||
| from rest_framework import generics, permissions | from rest_framework import generics, permissions | ||||||
| from rest_framework import filters | from rest_framework import filters | ||||||
| from rest_framework.response import Response |  | ||||||
|  |  | ||||||
| from django.conf import settings |  | ||||||
| from django.conf.urls import url | from django.conf.urls import url | ||||||
|  |  | ||||||
| from InvenTree.status_codes import OrderStatus | from InvenTree.helpers import str2bool | ||||||
|  |  | ||||||
| import os |  | ||||||
|  |  | ||||||
| from part.models import Part | from part.models import Part | ||||||
| from company.models import SupplierPart | from company.models import SupplierPart | ||||||
| @@ -34,66 +30,66 @@ class POList(generics.ListCreateAPIView): | |||||||
|     queryset = PurchaseOrder.objects.all() |     queryset = PurchaseOrder.objects.all() | ||||||
|     serializer_class = POSerializer |     serializer_class = POSerializer | ||||||
|  |  | ||||||
|     def list(self, request, *args, **kwargs): |     def get_serializer(self, *args, **kwargs): | ||||||
|  |  | ||||||
|         queryset = self.get_queryset().prefetch_related('supplier', 'lines') |         try: | ||||||
|  |             kwargs['supplier_detail'] = str2bool(self.request.query_params.get('supplier_detail', False)) | ||||||
|  |         except AttributeError: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|         queryset = self.filter_queryset(queryset) |         # Ensure the request context is passed through | ||||||
|  |         kwargs['context'] = self.get_serializer_context() | ||||||
|  |  | ||||||
|  |         return self.serializer_class(*args, **kwargs) | ||||||
|  |  | ||||||
|  |     def get_queryset(self, *args, **kwargs): | ||||||
|  |  | ||||||
|  |         queryset = super().get_queryset(*args, **kwargs) | ||||||
|  |  | ||||||
|  |         queryset = queryset.prefetch_related( | ||||||
|  |             'supplier', | ||||||
|  |             'lines', | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         queryset = POSerializer.annotate_queryset(queryset) | ||||||
|  |  | ||||||
|  |         return queryset | ||||||
|  |  | ||||||
|  |     def filter_queryset(self, queryset): | ||||||
|  |  | ||||||
|  |         # Perform basic filtering | ||||||
|  |         queryset = super().filter_queryset(queryset) | ||||||
|  |  | ||||||
|  |         params = self.request.query_params | ||||||
|  |  | ||||||
|         # Special filtering for 'status' field |         # Special filtering for 'status' field | ||||||
|         if 'status' in request.GET: |         status = params.get('status', None) | ||||||
|             status = request.GET['status'] |  | ||||||
|  |  | ||||||
|  |         if status is not None: | ||||||
|             # First attempt to filter by integer value |             # First attempt to filter by integer value | ||||||
|             try: |             queryset = queryset.filter(status=status) | ||||||
|                 status = int(status) |  | ||||||
|                 queryset = queryset.filter(status=status) |  | ||||||
|             except ValueError: |  | ||||||
|                 try: |  | ||||||
|                     value = OrderStatus.value(status) |  | ||||||
|                     queryset = queryset.filter(status=value) |  | ||||||
|                 except ValueError: |  | ||||||
|                     pass |  | ||||||
|  |  | ||||||
|         # Attempt to filter by part |         # Attempt to filter by part | ||||||
|         if 'part' in request.GET: |         part = params.get('part', None) | ||||||
|  |  | ||||||
|  |         if part is not None: | ||||||
|             try: |             try: | ||||||
|                 part = Part.objects.get(pk=request.GET['part']) |                 part = Part.objects.get(pk=part) | ||||||
|                 queryset = queryset.filter(id__in=[p.id for p in part.purchase_orders()]) |                 queryset = queryset.filter(id__in=[p.id for p in part.purchase_orders()]) | ||||||
|             except (Part.DoesNotExist, ValueError): |             except (Part.DoesNotExist, ValueError): | ||||||
|                 pass |                 pass | ||||||
|  |  | ||||||
|         # Attempt to filter by supplier part |         # Attempt to filter by supplier part | ||||||
|         if 'supplier_part' in request.GET: |         supplier_part = params.get('supplier_part', None) | ||||||
|  |  | ||||||
|  |         if supplier_part is not None: | ||||||
|             try: |             try: | ||||||
|                 supplier_part = SupplierPart.objects.get(pk=request.GET['supplier_part']) |                 supplier_part = SupplierPart.objects.get(pk=supplier_part) | ||||||
|                 queryset = queryset.filter(id__in=[p.id for p in supplier_part.purchase_orders()]) |                 queryset = queryset.filter(id__in=[p.id for p in supplier_part.purchase_orders()]) | ||||||
|             except (ValueError, SupplierPart.DoesNotExist): |             except (ValueError, SupplierPart.DoesNotExist): | ||||||
|                 pass |                 pass | ||||||
|  |  | ||||||
|         data = queryset.values( |         return queryset | ||||||
|             'pk', |  | ||||||
|             'supplier', |  | ||||||
|             'supplier_reference', |  | ||||||
|             'supplier__name', |  | ||||||
|             'supplier__image', |  | ||||||
|             'reference', |  | ||||||
|             'description', |  | ||||||
|             'link', |  | ||||||
|             'status', |  | ||||||
|             'notes', |  | ||||||
|             'creation_date', |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         for item in data: |  | ||||||
|  |  | ||||||
|             order = queryset.get(pk=item['pk']) |  | ||||||
|  |  | ||||||
|             item['supplier__image'] = os.path.join(settings.MEDIA_URL, item['supplier__image']) |  | ||||||
|             item['status_text'] = OrderStatus.label(item['status']) |  | ||||||
|             item['lines'] = order.lines.count() |  | ||||||
|  |  | ||||||
|         return Response(data) |  | ||||||
|  |  | ||||||
|     permission_classes = [ |     permission_classes = [ | ||||||
|         permissions.IsAuthenticated, |         permissions.IsAuthenticated, | ||||||
| @@ -123,6 +119,31 @@ class PODetail(generics.RetrieveUpdateAPIView): | |||||||
|     queryset = PurchaseOrder.objects.all() |     queryset = PurchaseOrder.objects.all() | ||||||
|     serializer_class = POSerializer |     serializer_class = POSerializer | ||||||
|  |  | ||||||
|  |     def get_serializer(self, *args, **kwargs): | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             kwargs['supplier_detail'] = str2bool(self.request.query_params.get('supplier_detail', False)) | ||||||
|  |         except AttributeError: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |         # Ensure the request context is passed through | ||||||
|  |         kwargs['context'] = self.get_serializer_context() | ||||||
|  |  | ||||||
|  |         return self.serializer_class(*args, **kwargs) | ||||||
|  |  | ||||||
|  |     def get_queryset(self, *args, **kwargs): | ||||||
|  |  | ||||||
|  |         queryset = super().get_queryset(*args, **kwargs) | ||||||
|  |  | ||||||
|  |         queryset = queryset.prefetch_related( | ||||||
|  |             'supplier', | ||||||
|  |             'lines', | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         queryset = POSerializer.annotate_queryset(queryset) | ||||||
|  |  | ||||||
|  |         return queryset | ||||||
|  |  | ||||||
|     permission_classes = [ |     permission_classes = [ | ||||||
|         permissions.IsAuthenticated |         permissions.IsAuthenticated | ||||||
|     ] |     ] | ||||||
|   | |||||||
| @@ -5,7 +5,12 @@ JSON serializers for the Order API | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | from rest_framework import serializers | ||||||
|  |  | ||||||
|  | from django.db.models import Count | ||||||
|  |  | ||||||
| from InvenTree.serializers import InvenTreeModelSerializer | from InvenTree.serializers import InvenTreeModelSerializer | ||||||
|  | from company.serializers import CompanyBriefSerializer | ||||||
|  |  | ||||||
| from .models import PurchaseOrder, PurchaseOrderLineItem | from .models import PurchaseOrder, PurchaseOrderLineItem | ||||||
|  |  | ||||||
| @@ -13,17 +18,48 @@ from .models import PurchaseOrder, PurchaseOrderLineItem | |||||||
| class POSerializer(InvenTreeModelSerializer): | class POSerializer(InvenTreeModelSerializer): | ||||||
|     """ Serializes an Order object """ |     """ Serializes an Order object """ | ||||||
|  |  | ||||||
|  |     def __init__(self, *args, **kwargs): | ||||||
|  |  | ||||||
|  |         supplier_detail = kwargs.pop('supplier_detail', False) | ||||||
|  |  | ||||||
|  |         super().__init__(*args, **kwargs) | ||||||
|  |  | ||||||
|  |         if supplier_detail is not True: | ||||||
|  |             self.fields.pop('supplier_detail') | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def annotate_queryset(queryset): | ||||||
|  |         """ | ||||||
|  |         Add extra information to the queryset | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         return queryset.annotate( | ||||||
|  |             line_items=Count('lines'), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True) | ||||||
|  |      | ||||||
|  |     line_items = serializers.IntegerField(read_only=True) | ||||||
|  |  | ||||||
|  |     status_text = serializers.CharField(source='get_status_display', read_only=True) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = PurchaseOrder |         model = PurchaseOrder | ||||||
|          |          | ||||||
|         fields = [ |         fields = [ | ||||||
|             'pk', |             'pk', | ||||||
|             'supplier', |             'issue_date', | ||||||
|             'supplier_reference', |             'complete_date', | ||||||
|             'reference', |             'creation_date', | ||||||
|             'description', |             'description', | ||||||
|  |             'line_items', | ||||||
|             'link', |             'link', | ||||||
|  |             'reference', | ||||||
|  |             'supplier', | ||||||
|  |             'supplier_detail', | ||||||
|  |             'supplier_reference', | ||||||
|             'status', |             'status', | ||||||
|  |             'status_text', | ||||||
|             'notes', |             'notes', | ||||||
|         ] |         ] | ||||||
|          |          | ||||||
|   | |||||||
| @@ -135,10 +135,40 @@ class PartDetail(generics.RetrieveUpdateAPIView): | |||||||
|     queryset = Part.objects.all() |     queryset = Part.objects.all() | ||||||
|     serializer_class = part_serializers.PartSerializer |     serializer_class = part_serializers.PartSerializer | ||||||
|      |      | ||||||
|  |     starred_parts = None | ||||||
|  |  | ||||||
|  |     def get_queryset(self, *args, **kwargs): | ||||||
|  |         queryset = super().get_queryset(*args, **kwargs) | ||||||
|  |  | ||||||
|  |         queryset = part_serializers.PartSerializer.prefetch_queryset(queryset) | ||||||
|  |         queryset = part_serializers.PartSerializer.annotate_queryset(queryset) | ||||||
|  |         return queryset | ||||||
|  |  | ||||||
|     permission_classes = [ |     permission_classes = [ | ||||||
|         permissions.IsAuthenticated, |         permissions.IsAuthenticated, | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  |     def get_serializer(self, *args, **kwargs): | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             cat_detail = str2bool(self.request.query_params.get('category_detail', False)) | ||||||
|  |         except AttributeError: | ||||||
|  |             cat_detail = None | ||||||
|  |  | ||||||
|  |         # Ensure the request context is passed through | ||||||
|  |         kwargs['context'] = self.get_serializer_context() | ||||||
|  |  | ||||||
|  |         kwargs['category_detail'] = cat_detail | ||||||
|  |  | ||||||
|  |         # Pass a list of "starred" parts fo the current user to the serializer | ||||||
|  |         # We do this to reduce the number of database queries required! | ||||||
|  |         if self.starred_parts is None and self.request is not None: | ||||||
|  |             self.starred_parts = [star.part for star in self.request.user.starred_parts.all()] | ||||||
|  |  | ||||||
|  |         kwargs['starred_parts'] = self.starred_parts | ||||||
|  |  | ||||||
|  |         return self.serializer_class(*args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
| class PartList(generics.ListCreateAPIView): | class PartList(generics.ListCreateAPIView): | ||||||
|     """ API endpoint for accessing a list of Part objects |     """ API endpoint for accessing a list of Part objects | ||||||
|   | |||||||
| @@ -58,20 +58,28 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView): | |||||||
|     serializer_class = StockItemSerializer |     serializer_class = StockItemSerializer | ||||||
|     permission_classes = (permissions.IsAuthenticated,) |     permission_classes = (permissions.IsAuthenticated,) | ||||||
|  |  | ||||||
|  |     def get_queryset(self, *args, **kwargs): | ||||||
|  |  | ||||||
|  |         queryset = super().get_queryset(*args, **kwargs) | ||||||
|  |         queryset = StockItemSerializer.prefetch_queryset(queryset) | ||||||
|  |         queryset = StockItemSerializer.annotate_queryset(queryset) | ||||||
|  |  | ||||||
|  |         return queryset | ||||||
|  |  | ||||||
|     def get_serializer(self, *args, **kwargs): |     def get_serializer(self, *args, **kwargs): | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             kwargs['part_detail'] = str2bool(self.request.GET.get('part_detail', False)) |             kwargs['part_detail'] = str2bool(self.request.query_params.get('part_detail', False)) | ||||||
|         except AttributeError: |         except AttributeError: | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             kwargs['location_detail'] = str2bool(self.request.GET.get('location_detail', False)) |             kwargs['location_detail'] = str2bool(self.request.query_params.get('location_detail', False)) | ||||||
|         except AttributeError: |         except AttributeError: | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             kwargs['supplier_detail'] = str2bool(self.request.GET.get('supplier_detail', False)) |             kwargs['supplier_part_detail'] = str2bool(self.request.query_params.get('supplier_detail', False)) | ||||||
|         except AttributeError: |         except AttributeError: | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
| @@ -321,14 +329,19 @@ class StockList(generics.ListCreateAPIView): | |||||||
|     def get_serializer(self, *args, **kwargs): |     def get_serializer(self, *args, **kwargs): | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             part_detail = str2bool(self.request.query_params.get('part_detail', None)) |             kwargs['part_detail'] = str2bool(self.request.query_params.get('part_detail', None)) | ||||||
|             location_detail = str2bool(self.request.query_params.get('location_detail', None)) |  | ||||||
|         except AttributeError: |         except AttributeError: | ||||||
|             part_detail = None |             pass | ||||||
|             location_detail = None |  | ||||||
|  |  | ||||||
|         kwargs['part_detail'] = part_detail |         try: | ||||||
|         kwargs['location_detail'] = location_detail |             kwargs['location_detail'] = str2bool(self.request.query_params.get('location_detail', None)) | ||||||
|  |         except AttributeError: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             kwargs['supplier_part_detail'] = str2bool(self.request.query_params.get('supplier_part_detail', None)) | ||||||
|  |         except AttributeError: | ||||||
|  |             pass | ||||||
|          |          | ||||||
|         # Ensure the request context is passed through |         # Ensure the request context is passed through | ||||||
|         kwargs['context'] = self.get_serializer_context() |         kwargs['context'] = self.get_serializer_context() | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ from rest_framework import serializers | |||||||
| from .models import StockItem, StockLocation | from .models import StockItem, StockLocation | ||||||
| from .models import StockItemTracking | from .models import StockItemTracking | ||||||
|  |  | ||||||
|  | from company.serializers import SupplierPartSerializer | ||||||
| from part.serializers import PartBriefSerializer | from part.serializers import PartBriefSerializer | ||||||
| from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer | from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer | ||||||
|  |  | ||||||
| @@ -78,13 +79,14 @@ class StockItemSerializer(InvenTreeModelSerializer): | |||||||
|         performing database queries as efficiently as possible. |         performing database queries as efficiently as possible. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         # TODO |         # TODO - Add custom annotated fields | ||||||
|         pass |         return queryset | ||||||
|  |  | ||||||
|     status_text = serializers.CharField(source='get_status_display', read_only=True) |     status_text = serializers.CharField(source='get_status_display', read_only=True) | ||||||
|      |      | ||||||
|     part_detail = PartBriefSerializer(source='part', many=False, read_only=True) |     part_detail = PartBriefSerializer(source='part', many=False, read_only=True) | ||||||
|     location_detail = LocationBriefSerializer(source='location', many=False, read_only=True) |     location_detail = LocationBriefSerializer(source='location', many=False, read_only=True) | ||||||
|  |     supplier_part_detail = SupplierPartSerializer(source='supplier_part', many=False, read_only=True) | ||||||
|  |  | ||||||
|     tracking_items = serializers.IntegerField(source='tracking_info_count', read_only=True) |     tracking_items = serializers.IntegerField(source='tracking_info_count', read_only=True) | ||||||
|  |  | ||||||
| @@ -92,6 +94,7 @@ class StockItemSerializer(InvenTreeModelSerializer): | |||||||
|  |  | ||||||
|         part_detail = kwargs.pop('part_detail', False) |         part_detail = kwargs.pop('part_detail', False) | ||||||
|         location_detail = kwargs.pop('location_detail', False) |         location_detail = kwargs.pop('location_detail', False) | ||||||
|  |         supplier_part_detail = kwargs.pop('supplier_part_detail', False) | ||||||
|  |  | ||||||
|         super(StockItemSerializer, self).__init__(*args, **kwargs) |         super(StockItemSerializer, self).__init__(*args, **kwargs) | ||||||
|  |  | ||||||
| @@ -101,6 +104,9 @@ class StockItemSerializer(InvenTreeModelSerializer): | |||||||
|         if location_detail is not True: |         if location_detail is not True: | ||||||
|             self.fields.pop('location_detail') |             self.fields.pop('location_detail') | ||||||
|  |  | ||||||
|  |         if supplier_part_detail is not True: | ||||||
|  |             self.fields.pop('supplier_part_detail') | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = StockItem |         model = StockItem | ||||||
|         fields = [ |         fields = [ | ||||||
| @@ -116,6 +122,7 @@ class StockItemSerializer(InvenTreeModelSerializer): | |||||||
|             'quantity', |             'quantity', | ||||||
|             'serial', |             'serial', | ||||||
|             'supplier_part', |             'supplier_part', | ||||||
|  |             'supplier_part_detail', | ||||||
|             'status', |             'status', | ||||||
|             'status_text', |             'status_text', | ||||||
|             'tracking_items', |             'tracking_items', | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user