diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py index d68ecd67ad..bfcc02794e 100644 --- a/InvenTree/InvenTree/api.py +++ b/InvenTree/InvenTree/api.py @@ -18,9 +18,10 @@ from .version import inventreeVersion, inventreeInstanceName from plugins import plugins as inventree_plugins # Load barcode plugins -print("INFO: Loading plugins") - +print("Loading barcode plugins") barcode_plugins = inventree_plugins.load_barcode_plugins() + +print("Loading action plugins") action_plugins = inventree_plugins.load_action_plugins() diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index d9600333f4..7d9633ced6 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -25,7 +25,7 @@ from part.api import part_api_urls, bom_api_urls from company.api import company_api_urls from stock.api import stock_api_urls from build.api import build_api_urls -from order.api import po_api_urls +from order.api import order_api_urls from django.conf import settings from django.conf.urls.static import static @@ -49,7 +49,7 @@ apipatterns = [ url(r'^company/', include(company_api_urls)), url(r'^stock/', include(stock_api_urls)), url(r'^build/', include(build_api_urls)), - url(r'^po/', include(po_api_urls)), + url(r'^order/', include(order_api_urls)), # User URLs url(r'^user/', include(user_urls)), diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 1dd0657930..0a7426b394 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -19,9 +19,12 @@ from company.models import SupplierPart from .models import PurchaseOrder, PurchaseOrderLineItem from .serializers import POSerializer, POLineItemSerializer +from .models import SalesOrder, SalesOrderLineItem +from .serializers import SalseOrderSerializer + class POList(generics.ListCreateAPIView): - """ API endpoint for accessing a list of Order objects + """ API endpoint for accessing a list of PurchaseOrder objects - GET: Return list of PO objects (with filters) - POST: Create a new PurchaseOrder object @@ -184,10 +187,124 @@ class POLineItemDetail(generics.RetrieveUpdateAPIView): ] -po_api_urls = [ - url(r'^order/(?P\d+)/?$', PODetail.as_view(), name='api-po-detail'), - url(r'^order/?$', POList.as_view(), name='api-po-list'), +class SOList(generics.ListCreateAPIView): + """ + API endpoint for accessing a list of SalesOrder objects. - url(r'^line/(?P\d+)/?$', POLineItemDetail.as_view(), name='api-po-line-detail'), - url(r'^line/?$', POLineItemList.as_view(), name='api-po-line-list'), + - GET: Return list of SO objects (with filters) + - POST: Create a new SalesOrder + """ + + queryset = SalesOrder.objects.all() + serializer_class = SalseOrderSerializer + + def get_serializer(self, *args, **kwargs): + + try: + kwargs['customer_detail'] = str2bool(self.request.query_params.get('customer_detail', False)) + except AttributeError: + pass + + # Ensure the context is passed through to the serializer + 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( + 'customer', + 'lines' + ) + + queryset = SalseOrderSerializer.annotate_queryset(queryset) + + return queryset + + def filter_queryset(self, queryset): + """ + Perform custom filtering operations on the SalesOrder queryset. + """ + + queryset = super().filter_queryset(queryset) + + params = self.request.query_params + + status = params.get('status', None) + + if status is not None: + queryset = queryset.filter(status=status) + + # TODO - Filter by part / stockitem / etc + + return queryset + + permission_classes = [ + permissions.IsAuthenticated + ] + + filter_backends = [ + DjangoFilterBackend, + filters.SearchFilter, + filters.OrderingFilter, + ] + + filter_fields = [ + 'customer', + ] + + ordering_fields = [ + 'creation_date', + 'reference' + ] + + ordering = '-creation_date' + + +class SODetail(generics.RetrieveUpdateAPIView): + """ + API endpoint for detail view of a SalesOrder object. + """ + + queryset = SalesOrder.objects.all() + serializer_class = SalseOrderSerializer + + def get_serializer(self, *args, **kwargs): + + try: + kwargs['customer_detail'] = str2bool(self.request.query_params.get('customer_detail', False)) + except AttributeError: + pass + + 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('customer', 'lines') + + queryset = SalseOrderSerializer.annotate_queryset(queryset) + + return queryset + + permission_classes = [permissions.IsAuthenticated] + + +order_api_urls = [ + # API endpoints for purchase orders + url(r'^po/(?P\d+)/$', PODetail.as_view(), name='api-po-detail'), + url(r'^po/$', POList.as_view(), name='api-po-list'), + + # API endpoints for purchase order line items + url(r'^po-line/(?P\d+)/$', POLineItemDetail.as_view(), name='api-po-line-detail'), + 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/$', SOList.as_view(), name='api-so-list'), ] diff --git a/InvenTree/order/migrations/0021_auto_20200420_1010.py b/InvenTree/order/migrations/0021_auto_20200420_1010.py new file mode 100644 index 0000000000..0f8351b660 --- /dev/null +++ b/InvenTree/order/migrations/0021_auto_20200420_1010.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.5 on 2020-04-20 10:10 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0021_remove_supplierpart_manufacturer_name'), + ('order', '0020_auto_20200420_0940'), + ] + + operations = [ + migrations.AlterField( + model_name='salesorder', + name='customer', + field=models.ForeignKey(help_text='Customer', limit_choices_to={'is_customer': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sales_orders', to='company.Company'), + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 77ea885b5c..544c54ec7b 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -257,7 +257,7 @@ class SalesOrder(Order): customer = models.ForeignKey(Company, on_delete=models.SET_NULL, null=True, - limit_choices_to={'is_supplier', True}, + limit_choices_to={'is_customer': True}, related_name='sales_orders', help_text=_("Customer"), ) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 935166e82a..9171a093a6 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -13,10 +13,11 @@ from InvenTree.serializers import InvenTreeModelSerializer from company.serializers import CompanyBriefSerializer from .models import PurchaseOrder, PurchaseOrderLineItem +from .models import SalesOrder, SalesOrderLineItem class POSerializer(InvenTreeModelSerializer): - """ Serializes an Order object """ + """ Serializer for a PurchaseOrder object """ def __init__(self, *args, **kwargs): @@ -83,3 +84,58 @@ class POLineItemSerializer(InvenTreeModelSerializer): 'part', 'received', ] + + +class SalseOrderSerializer(InvenTreeModelSerializer): + """ + Serializers for the SalesOrder object + """ + + def __init__(self, *args, **kwargs): + + customer_detail = kwargs.pop('customer_detail', False) + + super().__init__(*args, **kwargs) + + if customer_detail is not True: + self.fields.pop('customer_detail') + + @staticmethod + def annotate_queryset(queryset): + """ + Add extra information to the queryset + """ + + return queryset.annotate( + line_items=Count('lines'), + ) + + customer_detail = CompanyBriefSerializer(source='customer', 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: + model = SalesOrder + + fields = [ + 'pk', + 'issue_date', + 'complete_date', + 'creation_date', + 'line_items', + 'link', + 'reference', + 'customer', + 'customer_detail', + 'customer_reference', + 'status', + 'status_text', + 'notes', + ] + + read_only_fields = [ + 'reference', + 'status' + ]