mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +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:
		| @@ -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) | ||||||
|         }} |         }} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user