mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Report enhancements (#6714)
* Add "enabled" filter to template table * Cleanup * API endpoints - Add API endpoints for report snippet - List endpoint - Details endpoint * Update serializers - Add asset serializer - Update * Check for duplicate asset files - Prevent upload of duplicate asset files - Allow re-upload for same PK * Duplicate checks for ReportSnippet * Bump API version
This commit is contained in:
parent
cbd94fc4b5
commit
6abd33f060
@ -1,11 +1,15 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 181
|
INVENTREE_API_VERSION = 182
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v182 - 2024-03-15 : https://github.com/inventree/InvenTree/pull/6714
|
||||||
|
- Expose ReportSnippet model to the /report/snippet/ API endpoint
|
||||||
|
- Expose ReportAsset model to the /report/asset/ API endpoint
|
||||||
|
|
||||||
v181 - 2024-02-21 : https://github.com/inventree/InvenTree/pull/6541
|
v181 - 2024-02-21 : https://github.com/inventree/InvenTree/pull/6541
|
||||||
- Adds "width" and "height" fields to the LabelTemplate API endpoint
|
- Adds "width" and "height" fields to the LabelTemplate API endpoint
|
||||||
- Adds "page_size" and "landscape" fields to the ReportTemplate API endpoint
|
- Adds "page_size" and "landscape" fields to the ReportTemplate API endpoint
|
||||||
|
@ -17,31 +17,14 @@ import common.models
|
|||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
import order.models
|
import order.models
|
||||||
import part.models
|
import part.models
|
||||||
|
import report.models
|
||||||
|
import report.serializers
|
||||||
from InvenTree.api import MetadataView
|
from InvenTree.api import MetadataView
|
||||||
from InvenTree.exceptions import log_error
|
from InvenTree.exceptions import log_error
|
||||||
from InvenTree.filters import InvenTreeSearchFilter
|
from InvenTree.filters import InvenTreeSearchFilter
|
||||||
from InvenTree.mixins import ListCreateAPI, RetrieveAPI, RetrieveUpdateDestroyAPI
|
from InvenTree.mixins import ListCreateAPI, RetrieveAPI, RetrieveUpdateDestroyAPI
|
||||||
from stock.models import StockItem, StockItemAttachment, StockLocation
|
from stock.models import StockItem, StockItemAttachment, StockLocation
|
||||||
|
|
||||||
from .models import (
|
|
||||||
BillOfMaterialsReport,
|
|
||||||
BuildReport,
|
|
||||||
PurchaseOrderReport,
|
|
||||||
ReturnOrderReport,
|
|
||||||
SalesOrderReport,
|
|
||||||
StockLocationReport,
|
|
||||||
TestReport,
|
|
||||||
)
|
|
||||||
from .serializers import (
|
|
||||||
BOMReportSerializer,
|
|
||||||
BuildReportSerializer,
|
|
||||||
PurchaseOrderReportSerializer,
|
|
||||||
ReturnOrderReportSerializer,
|
|
||||||
SalesOrderReportSerializer,
|
|
||||||
StockLocationReportSerializer,
|
|
||||||
TestReportSerializer,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ReportListView(ListCreateAPI):
|
class ReportListView(ListCreateAPI):
|
||||||
"""Generic API class for report templates."""
|
"""Generic API class for report templates."""
|
||||||
@ -292,8 +275,8 @@ class StockItemTestReportMixin(ReportFilterMixin):
|
|||||||
|
|
||||||
ITEM_MODEL = StockItem
|
ITEM_MODEL = StockItem
|
||||||
ITEM_KEY = 'item'
|
ITEM_KEY = 'item'
|
||||||
queryset = TestReport.objects.all()
|
queryset = report.models.TestReport.objects.all()
|
||||||
serializer_class = TestReportSerializer
|
serializer_class = report.serializers.TestReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class StockItemTestReportList(StockItemTestReportMixin, ReportListView):
|
class StockItemTestReportList(StockItemTestReportMixin, ReportListView):
|
||||||
@ -343,8 +326,8 @@ class BOMReportMixin(ReportFilterMixin):
|
|||||||
ITEM_MODEL = part.models.Part
|
ITEM_MODEL = part.models.Part
|
||||||
ITEM_KEY = 'part'
|
ITEM_KEY = 'part'
|
||||||
|
|
||||||
queryset = BillOfMaterialsReport.objects.all()
|
queryset = report.models.BillOfMaterialsReport.objects.all()
|
||||||
serializer_class = BOMReportSerializer
|
serializer_class = report.serializers.BOMReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class BOMReportList(BOMReportMixin, ReportListView):
|
class BOMReportList(BOMReportMixin, ReportListView):
|
||||||
@ -377,8 +360,8 @@ class BuildReportMixin(ReportFilterMixin):
|
|||||||
ITEM_MODEL = build.models.Build
|
ITEM_MODEL = build.models.Build
|
||||||
ITEM_KEY = 'build'
|
ITEM_KEY = 'build'
|
||||||
|
|
||||||
queryset = BuildReport.objects.all()
|
queryset = report.models.BuildReport.objects.all()
|
||||||
serializer_class = BuildReportSerializer
|
serializer_class = report.serializers.BuildReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class BuildReportList(BuildReportMixin, ReportListView):
|
class BuildReportList(BuildReportMixin, ReportListView):
|
||||||
@ -411,8 +394,8 @@ class PurchaseOrderReportMixin(ReportFilterMixin):
|
|||||||
ITEM_MODEL = order.models.PurchaseOrder
|
ITEM_MODEL = order.models.PurchaseOrder
|
||||||
ITEM_KEY = 'order'
|
ITEM_KEY = 'order'
|
||||||
|
|
||||||
queryset = PurchaseOrderReport.objects.all()
|
queryset = report.models.PurchaseOrderReport.objects.all()
|
||||||
serializer_class = PurchaseOrderReportSerializer
|
serializer_class = report.serializers.PurchaseOrderReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderReportList(PurchaseOrderReportMixin, ReportListView):
|
class PurchaseOrderReportList(PurchaseOrderReportMixin, ReportListView):
|
||||||
@ -439,8 +422,8 @@ class SalesOrderReportMixin(ReportFilterMixin):
|
|||||||
ITEM_MODEL = order.models.SalesOrder
|
ITEM_MODEL = order.models.SalesOrder
|
||||||
ITEM_KEY = 'order'
|
ITEM_KEY = 'order'
|
||||||
|
|
||||||
queryset = SalesOrderReport.objects.all()
|
queryset = report.models.SalesOrderReport.objects.all()
|
||||||
serializer_class = SalesOrderReportSerializer
|
serializer_class = report.serializers.SalesOrderReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderReportList(SalesOrderReportMixin, ReportListView):
|
class SalesOrderReportList(SalesOrderReportMixin, ReportListView):
|
||||||
@ -467,8 +450,8 @@ class ReturnOrderReportMixin(ReportFilterMixin):
|
|||||||
ITEM_MODEL = order.models.ReturnOrder
|
ITEM_MODEL = order.models.ReturnOrder
|
||||||
ITEM_KEY = 'order'
|
ITEM_KEY = 'order'
|
||||||
|
|
||||||
queryset = ReturnOrderReport.objects.all()
|
queryset = report.models.ReturnOrderReport.objects.all()
|
||||||
serializer_class = ReturnOrderReportSerializer
|
serializer_class = report.serializers.ReturnOrderReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class ReturnOrderReportList(ReturnOrderReportMixin, ReportListView):
|
class ReturnOrderReportList(ReturnOrderReportMixin, ReportListView):
|
||||||
@ -494,8 +477,8 @@ class StockLocationReportMixin(ReportFilterMixin):
|
|||||||
|
|
||||||
ITEM_MODEL = StockLocation
|
ITEM_MODEL = StockLocation
|
||||||
ITEM_KEY = 'location'
|
ITEM_KEY = 'location'
|
||||||
queryset = StockLocationReport.objects.all()
|
queryset = report.models.StockLocationReport.objects.all()
|
||||||
serializer_class = StockLocationReportSerializer
|
serializer_class = report.serializers.StockLocationReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class StockLocationReportList(StockLocationReportMixin, ReportListView):
|
class StockLocationReportList(StockLocationReportMixin, ReportListView):
|
||||||
@ -516,7 +499,57 @@ class StockLocationReportPrint(StockLocationReportMixin, ReportPrintMixin, Retri
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ReportSnippetList(ListCreateAPI):
|
||||||
|
"""API endpoint for listing ReportSnippet objects."""
|
||||||
|
|
||||||
|
queryset = report.models.ReportSnippet.objects.all()
|
||||||
|
serializer_class = report.serializers.ReportSnippetSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ReportSnippetDetail(RetrieveUpdateDestroyAPI):
|
||||||
|
"""API endpoint for a single ReportSnippet object."""
|
||||||
|
|
||||||
|
queryset = report.models.ReportSnippet.objects.all()
|
||||||
|
serializer_class = report.serializers.ReportSnippetSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ReportAssetList(ListCreateAPI):
|
||||||
|
"""API endpoint for listing ReportAsset objects."""
|
||||||
|
|
||||||
|
queryset = report.models.ReportAsset.objects.all()
|
||||||
|
serializer_class = report.serializers.ReportAssetSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ReportAssetDetail(RetrieveUpdateDestroyAPI):
|
||||||
|
"""API endpoint for a single ReportAsset object."""
|
||||||
|
|
||||||
|
queryset = report.models.ReportAsset.objects.all()
|
||||||
|
serializer_class = report.serializers.ReportAssetSerializer
|
||||||
|
|
||||||
|
|
||||||
report_api_urls = [
|
report_api_urls = [
|
||||||
|
# Report assets
|
||||||
|
path(
|
||||||
|
'asset/',
|
||||||
|
include([
|
||||||
|
path(
|
||||||
|
'<int:pk>/', ReportAssetDetail.as_view(), name='api-report-asset-detail'
|
||||||
|
),
|
||||||
|
path('', ReportAssetList.as_view(), name='api-report-asset-list'),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
# Report snippets
|
||||||
|
path(
|
||||||
|
'snippet/',
|
||||||
|
include([
|
||||||
|
path(
|
||||||
|
'<int:pk>/',
|
||||||
|
ReportSnippetDetail.as_view(),
|
||||||
|
name='api-report-snippet-detail',
|
||||||
|
),
|
||||||
|
path('', ReportSnippetList.as_view(), name='api-report-snippet-list'),
|
||||||
|
]),
|
||||||
|
),
|
||||||
# Purchase order reports
|
# Purchase order reports
|
||||||
path(
|
path(
|
||||||
'po/',
|
'po/',
|
||||||
@ -533,7 +566,7 @@ report_api_urls = [
|
|||||||
path(
|
path(
|
||||||
'metadata/',
|
'metadata/',
|
||||||
MetadataView.as_view(),
|
MetadataView.as_view(),
|
||||||
{'model': PurchaseOrderReport},
|
{'model': report.models.PurchaseOrderReport},
|
||||||
name='api-po-report-metadata',
|
name='api-po-report-metadata',
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
@ -563,7 +596,7 @@ report_api_urls = [
|
|||||||
path(
|
path(
|
||||||
'metadata/',
|
'metadata/',
|
||||||
MetadataView.as_view(),
|
MetadataView.as_view(),
|
||||||
{'model': SalesOrderReport},
|
{'model': report.models.SalesOrderReport},
|
||||||
name='api-so-report-metadata',
|
name='api-so-report-metadata',
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
@ -591,7 +624,7 @@ report_api_urls = [
|
|||||||
path(
|
path(
|
||||||
'metadata/',
|
'metadata/',
|
||||||
MetadataView.as_view(),
|
MetadataView.as_view(),
|
||||||
{'model': ReturnOrderReport},
|
{'model': report.models.ReturnOrderReport},
|
||||||
name='api-so-report-metadata',
|
name='api-so-report-metadata',
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
@ -622,7 +655,7 @@ report_api_urls = [
|
|||||||
path(
|
path(
|
||||||
'metadata/',
|
'metadata/',
|
||||||
MetadataView.as_view(),
|
MetadataView.as_view(),
|
||||||
{'model': BuildReport},
|
{'model': report.models.BuildReport},
|
||||||
name='api-build-report-metadata',
|
name='api-build-report-metadata',
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
@ -650,7 +683,7 @@ report_api_urls = [
|
|||||||
path(
|
path(
|
||||||
'metadata/',
|
'metadata/',
|
||||||
MetadataView.as_view(),
|
MetadataView.as_view(),
|
||||||
{'model': BillOfMaterialsReport},
|
{'model': report.models.BillOfMaterialsReport},
|
||||||
name='api-bom-report-metadata',
|
name='api-bom-report-metadata',
|
||||||
),
|
),
|
||||||
path('', BOMReportDetail.as_view(), name='api-bom-report-detail'),
|
path('', BOMReportDetail.as_view(), name='api-bom-report-detail'),
|
||||||
@ -676,7 +709,7 @@ report_api_urls = [
|
|||||||
path(
|
path(
|
||||||
'metadata/',
|
'metadata/',
|
||||||
MetadataView.as_view(),
|
MetadataView.as_view(),
|
||||||
{'report': TestReport},
|
{'report': report.models.TestReport},
|
||||||
name='api-stockitem-testreport-metadata',
|
name='api-stockitem-testreport-metadata',
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
@ -710,7 +743,7 @@ report_api_urls = [
|
|||||||
path(
|
path(
|
||||||
'metadata/',
|
'metadata/',
|
||||||
MetadataView.as_view(),
|
MetadataView.as_view(),
|
||||||
{'report': StockLocationReport},
|
{'report': report.models.StockLocationReport},
|
||||||
name='api-stocklocation-report-metadata',
|
name='api-stocklocation-report-metadata',
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
|
@ -7,6 +7,7 @@ import sys
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import FileExtensionValidator
|
from django.core.validators import FileExtensionValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
@ -585,10 +586,7 @@ class ReturnOrderReport(ReportTemplateBase):
|
|||||||
|
|
||||||
def rename_snippet(instance, filename):
|
def rename_snippet(instance, filename):
|
||||||
"""Function to rename a report snippet once uploaded."""
|
"""Function to rename a report snippet once uploaded."""
|
||||||
filename = os.path.basename(filename)
|
path = ReportSnippet.snippet_path(filename)
|
||||||
|
|
||||||
path = os.path.join('report', 'snippets', filename)
|
|
||||||
|
|
||||||
fullpath = settings.MEDIA_ROOT.joinpath(path).resolve()
|
fullpath = settings.MEDIA_ROOT.joinpath(path).resolve()
|
||||||
|
|
||||||
# If the snippet file is the *same* filename as the one being uploaded,
|
# If the snippet file is the *same* filename as the one being uploaded,
|
||||||
@ -610,6 +608,40 @@ class ReportSnippet(models.Model):
|
|||||||
Useful for 'common' template actions, sub-templates, etc
|
Useful for 'common' template actions, sub-templates, etc
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
"""String representation of a ReportSnippet instance."""
|
||||||
|
return f'snippets/{self.filename}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filename(self):
|
||||||
|
"""Return the filename of the asset."""
|
||||||
|
path = self.snippet.name
|
||||||
|
if path:
|
||||||
|
return os.path.basename(path)
|
||||||
|
else:
|
||||||
|
return '-'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def snippet_path(filename):
|
||||||
|
"""Return the fully-qualified snippet path for the given filename."""
|
||||||
|
return os.path.join('report', 'snippets', os.path.basename(str(filename)))
|
||||||
|
|
||||||
|
def validate_unique(self, exclude=None):
|
||||||
|
"""Validate that this report asset is unique."""
|
||||||
|
proposed_path = self.snippet_path(self.snippet)
|
||||||
|
|
||||||
|
if (
|
||||||
|
ReportSnippet.objects.filter(snippet=proposed_path)
|
||||||
|
.exclude(pk=self.pk)
|
||||||
|
.count()
|
||||||
|
> 0
|
||||||
|
):
|
||||||
|
raise ValidationError({
|
||||||
|
'snippet': _('Snippet file with this name already exists')
|
||||||
|
})
|
||||||
|
|
||||||
|
return super().validate_unique(exclude)
|
||||||
|
|
||||||
snippet = models.FileField(
|
snippet = models.FileField(
|
||||||
upload_to=rename_snippet,
|
upload_to=rename_snippet,
|
||||||
verbose_name=_('Snippet'),
|
verbose_name=_('Snippet'),
|
||||||
@ -626,19 +658,20 @@ class ReportSnippet(models.Model):
|
|||||||
|
|
||||||
def rename_asset(instance, filename):
|
def rename_asset(instance, filename):
|
||||||
"""Function to rename an asset file when uploaded."""
|
"""Function to rename an asset file when uploaded."""
|
||||||
filename = os.path.basename(filename)
|
path = ReportAsset.asset_path(filename)
|
||||||
|
fullpath = settings.MEDIA_ROOT.joinpath(path).resolve()
|
||||||
path = os.path.join('report', 'assets', filename)
|
|
||||||
|
|
||||||
# If the asset file is the *same* filename as the one being uploaded,
|
# If the asset file is the *same* filename as the one being uploaded,
|
||||||
# delete the original one from the media directory
|
# delete the original one from the media directory
|
||||||
if str(filename) == str(instance.asset):
|
if str(filename) == str(instance.asset):
|
||||||
fullpath = settings.MEDIA_ROOT.joinpath(path).resolve()
|
|
||||||
|
|
||||||
if fullpath.exists():
|
if fullpath.exists():
|
||||||
|
# Check for existing asset file with the same name
|
||||||
logger.info("Deleting existing asset file: '%s'", filename)
|
logger.info("Deleting existing asset file: '%s'", filename)
|
||||||
os.remove(fullpath)
|
os.remove(fullpath)
|
||||||
|
|
||||||
|
# Ensure the cache is deleted for this asset
|
||||||
|
cache.delete(fullpath)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
@ -652,7 +685,35 @@ class ReportAsset(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""String representation of a ReportAsset instance."""
|
"""String representation of a ReportAsset instance."""
|
||||||
return os.path.basename(self.asset.name)
|
return f'assets/{self.filename}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filename(self):
|
||||||
|
"""Return the filename of the asset."""
|
||||||
|
path = self.asset.name
|
||||||
|
if path:
|
||||||
|
return os.path.basename(path)
|
||||||
|
else:
|
||||||
|
return '-'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def asset_path(filename):
|
||||||
|
"""Return the fully-qualified asset path for the given filename."""
|
||||||
|
return os.path.join('report', 'assets', os.path.basename(str(filename)))
|
||||||
|
|
||||||
|
def validate_unique(self, exclude=None):
|
||||||
|
"""Validate that this report asset is unique."""
|
||||||
|
proposed_path = self.asset_path(self.asset)
|
||||||
|
|
||||||
|
if (
|
||||||
|
ReportAsset.objects.filter(asset=proposed_path).exclude(pk=self.pk).count()
|
||||||
|
> 0
|
||||||
|
):
|
||||||
|
raise ValidationError({
|
||||||
|
'asset': _('Asset file with this name already exists')
|
||||||
|
})
|
||||||
|
|
||||||
|
return super().validate_unique(exclude)
|
||||||
|
|
||||||
# Asset file
|
# Asset file
|
||||||
asset = models.FileField(
|
asset = models.FileField(
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
"""API serializers for the reporting models."""
|
"""API serializers for the reporting models."""
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
import report.models
|
||||||
from InvenTree.serializers import (
|
from InvenTree.serializers import (
|
||||||
InvenTreeAttachmentSerializerField,
|
InvenTreeAttachmentSerializerField,
|
||||||
InvenTreeModelSerializer,
|
InvenTreeModelSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import (
|
|
||||||
BillOfMaterialsReport,
|
|
||||||
BuildReport,
|
|
||||||
PurchaseOrderReport,
|
|
||||||
ReturnOrderReport,
|
|
||||||
SalesOrderReport,
|
|
||||||
StockLocationReport,
|
|
||||||
TestReport,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ReportSerializerBase(InvenTreeModelSerializer):
|
class ReportSerializerBase(InvenTreeModelSerializer):
|
||||||
"""Base class for report serializer."""
|
"""Base class for report serializer."""
|
||||||
@ -42,7 +35,7 @@ class TestReportSerializer(ReportSerializerBase):
|
|||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = TestReport
|
model = report.models.TestReport
|
||||||
fields = ReportSerializerBase.report_fields()
|
fields = ReportSerializerBase.report_fields()
|
||||||
|
|
||||||
|
|
||||||
@ -52,7 +45,7 @@ class BuildReportSerializer(ReportSerializerBase):
|
|||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = BuildReport
|
model = report.models.BuildReport
|
||||||
fields = ReportSerializerBase.report_fields()
|
fields = ReportSerializerBase.report_fields()
|
||||||
|
|
||||||
|
|
||||||
@ -62,7 +55,7 @@ class BOMReportSerializer(ReportSerializerBase):
|
|||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = BillOfMaterialsReport
|
model = report.models.BillOfMaterialsReport
|
||||||
fields = ReportSerializerBase.report_fields()
|
fields = ReportSerializerBase.report_fields()
|
||||||
|
|
||||||
|
|
||||||
@ -72,7 +65,7 @@ class PurchaseOrderReportSerializer(ReportSerializerBase):
|
|||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = PurchaseOrderReport
|
model = report.models.PurchaseOrderReport
|
||||||
fields = ReportSerializerBase.report_fields()
|
fields = ReportSerializerBase.report_fields()
|
||||||
|
|
||||||
|
|
||||||
@ -82,7 +75,7 @@ class SalesOrderReportSerializer(ReportSerializerBase):
|
|||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = SalesOrderReport
|
model = report.models.SalesOrderReport
|
||||||
fields = ReportSerializerBase.report_fields()
|
fields = ReportSerializerBase.report_fields()
|
||||||
|
|
||||||
|
|
||||||
@ -92,7 +85,7 @@ class ReturnOrderReportSerializer(ReportSerializerBase):
|
|||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = ReturnOrderReport
|
model = report.models.ReturnOrderReport
|
||||||
fields = ReportSerializerBase.report_fields()
|
fields = ReportSerializerBase.report_fields()
|
||||||
|
|
||||||
|
|
||||||
@ -102,5 +95,30 @@ class StockLocationReportSerializer(ReportSerializerBase):
|
|||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = StockLocationReport
|
model = report.models.StockLocationReport
|
||||||
fields = ReportSerializerBase.report_fields()
|
fields = ReportSerializerBase.report_fields()
|
||||||
|
|
||||||
|
|
||||||
|
class ReportSnippetSerializer(InvenTreeModelSerializer):
|
||||||
|
"""Serializer class for the ReportSnippet model."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass options."""
|
||||||
|
|
||||||
|
model = report.models.ReportSnippet
|
||||||
|
|
||||||
|
fields = ['pk', 'snippet', 'description']
|
||||||
|
|
||||||
|
snippet = InvenTreeAttachmentSerializerField()
|
||||||
|
|
||||||
|
|
||||||
|
class ReportAssetSerializer(InvenTreeModelSerializer):
|
||||||
|
"""Serializer class for the ReportAsset model."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class options."""
|
||||||
|
|
||||||
|
model = report.models.ReportAsset
|
||||||
|
fields = ['pk', 'asset', 'description']
|
||||||
|
|
||||||
|
asset = InvenTreeAttachmentSerializerField()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Trans, t } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
import { Box, Group, LoadingOverlay, Stack, Text, Title } from '@mantine/core';
|
import { Box, Group, LoadingOverlay, Stack, Text, Title } from '@mantine/core';
|
||||||
import { IconDots } from '@tabler/icons-react';
|
import { IconDots } from '@tabler/icons-react';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { ReactNode, useCallback, useMemo, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
@ -29,6 +29,7 @@ import { useTable } from '../../hooks/UseTable';
|
|||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { TableColumn } from '../Column';
|
import { TableColumn } from '../Column';
|
||||||
import { BooleanColumn } from '../ColumnRenderers';
|
import { BooleanColumn } from '../ColumnRenderers';
|
||||||
|
import { TableFilter } from '../Filter';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||||
|
|
||||||
@ -257,18 +258,25 @@ export function TemplateTable({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableActions = useMemo(() => {
|
const tableActions: ReactNode[] = useMemo(() => {
|
||||||
let actions = [];
|
return [
|
||||||
|
|
||||||
actions.push(
|
|
||||||
<AddItemButton
|
<AddItemButton
|
||||||
key={`add-${templateType}`}
|
key={`add-${templateType}`}
|
||||||
onClick={() => newTemplate.open()}
|
onClick={() => newTemplate.open()}
|
||||||
tooltip={t`Add` + ' ' + templateTypeTranslation}
|
tooltip={t`Add` + ' ' + templateTypeTranslation}
|
||||||
/>
|
/>
|
||||||
);
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
return actions;
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'enabled',
|
||||||
|
label: t`Enabled`,
|
||||||
|
description: t`Filter by enabled status`,
|
||||||
|
type: 'checkbox'
|
||||||
|
}
|
||||||
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -294,6 +302,7 @@ export function TemplateTable({
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
props={{
|
props={{
|
||||||
rowActions: rowActions,
|
rowActions: rowActions,
|
||||||
|
tableFilters: tableFilters,
|
||||||
tableActions: tableActions,
|
tableActions: tableActions,
|
||||||
onRowClick: (record) => openDetailDrawer(record.pk)
|
onRowClick: (record) => openDetailDrawer(record.pk)
|
||||||
}}
|
}}
|
||||||
|
Loading…
Reference in New Issue
Block a user