diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py index c9b5502546..ba2b725a67 100644 --- a/InvenTree/InvenTree/api.py +++ b/InvenTree/InvenTree/api.py @@ -313,3 +313,44 @@ class APISearchView(APIView): } return Response(results) + + +class StatusView(APIView): + """Generic API endpoint for discovering information on 'status codes' for a particular model. + + This class should be implemented as a subclass for each type of status. + For example, the API endpoint /stock/status/ will have information about + all available 'StockStatus' codes + """ + + permission_classes = [ + permissions.IsAuthenticated, + ] + + # Override status_class for implementing subclass + MODEL_REF = 'statusmodel' + + def get_status_model(self, *args, **kwargs): + """Return the StatusCode moedl based on extra parameters passed to the view""" + + status_model = self.kwargs.get(self.MODEL_REF, None) + + if status_model is None: + raise ValidationError(f"StatusView view called without '{self.MODEL_REF}' parameter") + + return status_model + + def get(self, request, *args, **kwargs): + """Perform a GET request to learn information about status codes""" + + status_class = self.get_status_model() + + if not status_class: + raise NotImplementedError("status_class not defined for this endpoint") + + data = { + 'class': status_class.__name__, + 'values': status_class.dict(), + } + + return Response(data) diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index 40ce5d04e3..70c0149ea0 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -2,11 +2,14 @@ # InvenTree API version -INVENTREE_API_VERSION = 104 +INVENTREE_API_VERSION = 105 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v105 -> 2023-03-31 : https://github.com/inventree/InvenTree/pull/4543 + - Adds API endpoints for status label information on various models + v104 -> 2023-03-23 : https://github.com/inventree/InvenTree/pull/4488 - Adds various endpoints for new "ReturnOrder" models - Adds various endpoints for new "ReturnOrderReport" templates diff --git a/InvenTree/InvenTree/status_codes.py b/InvenTree/InvenTree/status_codes.py index 98452e8fa2..cb410c4f5c 100644 --- a/InvenTree/InvenTree/status_codes.py +++ b/InvenTree/InvenTree/status_codes.py @@ -31,23 +31,7 @@ class StatusCode: @classmethod def list(cls): """Return the StatusCode options as a list of mapped key / value items.""" - codes = [] - - for key in cls.options.keys(): - - opt = { - 'key': key, - 'value': cls.options[key] - } - - color = cls.colors.get(key, None) - - if color: - opt['color'] = color - - codes.append(opt) - - return codes + return list(cls.dict().values()) @classmethod def text(cls, key): @@ -69,6 +53,62 @@ class StatusCode: """All status code labels.""" return cls.options.values() + @classmethod + def names(cls): + """Return a map of all 'names' of status codes in this class + + Will return a dict object, with the attribute name indexed to the integer value. + + e.g. + { + 'PENDING': 10, + 'IN_PROGRESS': 20, + } + """ + keys = cls.keys() + status_names = {} + + for d in dir(cls): + if d.startswith('_'): + continue + if d != d.upper(): + continue + + value = getattr(cls, d, None) + + if value is None: + continue + if callable(value): + continue + if type(value) != int: + continue + if value not in keys: + continue + + status_names[d] = value + + return status_names + + @classmethod + def dict(cls): + """Return a dict representation containing all required information""" + values = {} + + for name, value, in cls.names().items(): + entry = { + 'key': value, + 'name': name, + 'label': cls.label(value), + } + + if hasattr(cls, 'colors'): + if color := cls.colors.get(value, None): + entry['color'] = color + + values[name] = entry + + return values + @classmethod def label(cls, value): """Return the status code label associated with the provided value.""" diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index f4f7bad41b..46f735507d 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -10,7 +10,7 @@ from rest_framework.exceptions import ValidationError from django_filters.rest_framework import DjangoFilterBackend from django_filters import rest_framework as rest_filters -from InvenTree.api import AttachmentMixin, APIDownloadMixin, ListCreateDestroyAPIView +from InvenTree.api import AttachmentMixin, APIDownloadMixin, ListCreateDestroyAPIView, StatusView from InvenTree.helpers import str2bool, isNull, DownloadFile from InvenTree.filters import InvenTreeOrderingFilter from InvenTree.status_codes import BuildStatus @@ -536,6 +536,9 @@ build_api_urls = [ re_path(r'^.*$', BuildDetail.as_view(), name='api-build-detail'), ])), + # Build order status code information + re_path(r'status/', StatusView.as_view(), {StatusView.MODEL_REF: BuildStatus}, name='api-build-status-codes'), + # Build List re_path(r'^.*$', BuildList.as_view(), name='api-build-list'), ] diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 4d5b0e5503..6a5522be0d 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -20,12 +20,13 @@ from common.models import InvenTreeSetting from common.settings import settings from company.models import SupplierPart from InvenTree.api import (APIDownloadMixin, AttachmentMixin, - ListCreateDestroyAPIView) + ListCreateDestroyAPIView, StatusView) from InvenTree.filters import InvenTreeOrderingFilter from InvenTree.helpers import DownloadFile, str2bool from InvenTree.mixins import (CreateAPI, ListAPI, ListCreateAPI, RetrieveUpdateAPI, RetrieveUpdateDestroyAPI) -from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus +from InvenTree.status_codes import (PurchaseOrderStatus, ReturnOrderLineStatus, + ReturnOrderStatus, SalesOrderStatus) from order.admin import (PurchaseOrderExtraLineResource, PurchaseOrderLineItemResource, PurchaseOrderResource, ReturnOrderResource, SalesOrderExtraLineResource, @@ -1610,6 +1611,9 @@ order_api_urls = [ re_path(r'.*$', PurchaseOrderDetail.as_view(), name='api-po-detail'), ])), + # Purchase order status code information + re_path(r'status/', StatusView.as_view(), {StatusView.MODEL_REF: PurchaseOrderStatus}, name='api-po-status-codes'), + # Purchase order list re_path(r'^.*$', PurchaseOrderList.as_view(), name='api-po-list'), ])), @@ -1661,6 +1665,9 @@ order_api_urls = [ re_path(r'^.*$', SalesOrderDetail.as_view(), name='api-so-detail'), ])), + # Sales order status code information + re_path(r'status/', StatusView.as_view(), {StatusView.MODEL_REF: SalesOrderStatus}, name='api-so-status-codes'), + # Sales order list view re_path(r'^.*$', SalesOrderList.as_view(), name='api-so-list'), ])), @@ -1706,6 +1713,9 @@ order_api_urls = [ re_path(r'.*$', ReturnOrderDetail.as_view(), name='api-return-order-detail'), ])), + # Return order status code information + re_path(r'status/', StatusView.as_view(), {StatusView.MODEL_REF: ReturnOrderStatus}, name='api-return-order-status-codes'), + # Return Order list re_path(r'^.*$', ReturnOrderList.as_view(), name='api-return-order-list'), ])), @@ -1713,6 +1723,10 @@ order_api_urls = [ # API endpoints for reutrn order lines re_path(r'^ro-line/', include([ path('/', ReturnOrderLineItemDetail.as_view(), name='api-return-order-line-detail'), + + # Return order line item status code information + re_path(r'status/', StatusView.as_view(), {StatusView.MODEL_REF: ReturnOrderLineStatus}, name='api-return-order-line-status-codes'), + path('', ReturnOrderLineItemList.as_view(), name='api-return-order-line-list'), ])), diff --git a/InvenTree/report/api.py b/InvenTree/report/api.py index e3c9820931..45490a5754 100644 --- a/InvenTree/report/api.py +++ b/InvenTree/report/api.py @@ -502,7 +502,7 @@ report_api_urls = [ ])), # Return order reports - re_path(r'return-order/', include([ + re_path(r'ro/', include([ path(r'/', include([ path(r'print/', ReturnOrderReportPrint.as_view(), name='api-return-order-report-print'), path('', ReturnOrderReportDetail.as_view(), name='api-return-order-report-detail'), diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 36355881c5..c0e58aa8ce 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -23,13 +23,14 @@ from build.models import Build from company.models import Company, SupplierPart from company.serializers import CompanySerializer, SupplierPartSerializer from InvenTree.api import (APIDownloadMixin, AttachmentMixin, - ListCreateDestroyAPIView) + ListCreateDestroyAPIView, StatusView) from InvenTree.filters import InvenTreeOrderingFilter from InvenTree.helpers import (DownloadFile, extract_serial_numbers, isNull, str2bool, str2int) from InvenTree.mixins import (CreateAPI, CustomRetrieveUpdateDestroyAPI, ListAPI, ListCreateAPI, RetrieveAPI, RetrieveUpdateAPI, RetrieveUpdateDestroyAPI) +from InvenTree.status_codes import StockHistoryCode, StockStatus from order.models import (PurchaseOrder, ReturnOrder, SalesOrder, SalesOrderAllocation) from order.serializers import (PurchaseOrderSerializer, ReturnOrderSerializer, @@ -1421,6 +1422,10 @@ stock_api_urls = [ # StockItemTracking API endpoints re_path(r'^track/', include([ path(r'/', StockTrackingDetail.as_view(), name='api-stock-tracking-detail'), + + # Stock tracking status code information + re_path(r'status/', StatusView.as_view(), {StatusView.MODEL_REF: StockHistoryCode}, name='api-stock-tracking-status-codes'), + re_path(r'^.*$', StockTrackingList.as_view(), name='api-stock-tracking-list'), ])), @@ -1435,6 +1440,9 @@ stock_api_urls = [ re_path(r'^.*$', StockDetail.as_view(), name='api-stock-detail'), ])), + # Stock item status code information + re_path(r'status/', StatusView.as_view(), {StatusView.MODEL_REF: StockStatus}, name='api-stock-status-codes'), + # Anything else re_path(r'^.*$', StockList.as_view(), name='api-stock-list'), ] diff --git a/InvenTree/templates/js/translated/status_codes.js b/InvenTree/templates/js/translated/status_codes.js index caab07270e..3b4c38e6df 100644 --- a/InvenTree/templates/js/translated/status_codes.js +++ b/InvenTree/templates/js/translated/status_codes.js @@ -15,10 +15,51 @@ stockStatusDisplay, */ -{% include "status_codes.html" with label='stock' options=StockStatus.list %} -{% include "status_codes.html" with label='stockHistory' options=StockHistoryCode.list %} -{% include "status_codes.html" with label='build' options=BuildStatus.list %} -{% include "status_codes.html" with label='purchaseOrder' options=PurchaseOrderStatus.list %} -{% include "status_codes.html" with label='salesOrder' options=SalesOrderStatus.list %} -{% include "status_codes.html" with label='returnOrder' options=ReturnOrderStatus.list %} -{% include "status_codes.html" with label='returnOrderLineItem' options=ReturnOrderLineStatus.list %} + +/* + * Generic function to render a status label + */ +function renderStatusLabel(key, codes, options={}) { + + let text = null; + let label = null; + + // Find the entry which matches the provided key + for (var name in codes) { + let entry = codes[name]; + + if (entry.key == key) { + text = entry.value; + label = entry.label; + break; + } + } + + if (!text) { + console.error(`renderStatusLabel could not find match for code ${key}`); + } + + // Fallback for color + label = label || 'bg-dark'; + + if (!text) { + text = key; + } + + let classes = `badge rounded-pill ${label}`; + + if (options.classes) { + classes += ` ${options.classes}`; + } + + return `${text}`; +} + + +{% include "status_codes.html" with label='stock' data=StockStatus.list %} +{% include "status_codes.html" with label='stockHistory' data=StockHistoryCode.list %} +{% include "status_codes.html" with label='build' data=BuildStatus.list %} +{% include "status_codes.html" with label='purchaseOrder' data=PurchaseOrderStatus.list %} +{% include "status_codes.html" with label='salesOrder' data=SalesOrderStatus.list %} +{% include "status_codes.html" with label='returnOrder' data=ReturnOrderStatus.list %} +{% include "status_codes.html" with label='returnOrderLineItem' data=ReturnOrderLineStatus.list %} diff --git a/InvenTree/templates/status_codes.html b/InvenTree/templates/status_codes.html index e48424477f..6d241352b2 100644 --- a/InvenTree/templates/status_codes.html +++ b/InvenTree/templates/status_codes.html @@ -1,11 +1,15 @@ +{% load report %} + /* * Status codes for the {{ label }} model. + * Generated from the values specified in "status_codes.py" */ const {{ label }}Codes = { - {% for opt in options %}'{{ opt.key }}': { - key: '{{ opt.key }}', - value: '{{ opt.value }}',{% if opt.color %} - label: 'bg-{{ opt.color }}',{% endif %} + {% for entry in data %} + '{{ entry.name }}': { + key: {{ entry.key }}, + value: '{{ entry.label }}',{% if entry.color %} + label: 'bg-{{ entry.color }}',{% endif %} }, {% endfor %} }; @@ -13,33 +17,7 @@ const {{ label }}Codes = { /* * Render the status for a {{ label }} object. * Uses the values specified in "status_codes.py" - * This function is generated by the "status_codes.html" template */ function {{ label }}StatusDisplay(key, options={}) { - - key = String(key); - - var value = null; - var label = null; - - if (key in {{ label }}Codes) { - value = {{ label }}Codes[key].value; - label = {{ label }}Codes[key].label; - } - - // Fallback option for label - label = label || 'bg-dark'; - - if (value == null || value.length == 0) { - value = key; - label = ''; - } - - var classes = `badge rounded-pill ${label}`; - - if (options.classes) { - classes += ' ' + options.classes; - } - - return `${value}`; + return renderStatusLabel(key, {{ label }}Codes, options); }