diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index d278abde37..179bbd692c 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,13 +1,16 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 250 +INVENTREE_API_VERSION = 251 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v251 - 2024-09-06 : https://github.com/inventree/InvenTree/pull/8018 + - Adds "attach_to_model" field to the ReporTemplate model + v250 - 2024-09-04 : https://github.com/inventree/InvenTree/pull/8069 - Fixes 'revision' field definition in Part serializer diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index 24e718bd5e..2b8dc5aca3 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -1704,20 +1704,6 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'default': 'A4', 'choices': report.helpers.report_page_size_options, }, - 'REPORT_ENABLE_TEST_REPORT': { - 'name': _('Enable Test Reports'), - 'description': _('Enable generation of test reports'), - 'default': True, - 'validator': bool, - }, - 'REPORT_ATTACH_TEST_REPORT': { - 'name': _('Attach Test Reports'), - 'description': _( - 'When printing a Test Report, attach a copy of the Test Report to the associated Stock Item' - ), - 'default': False, - 'validator': bool, - }, 'SERIAL_NUMBER_GLOBALLY_UNIQUE': { 'name': _('Globally Unique Serials'), 'description': _('Serial numbers for stock items must be globally unique'), diff --git a/src/backend/InvenTree/common/tests.py b/src/backend/InvenTree/common/tests.py index 7acd3b9c9e..826bce15b7 100644 --- a/src/backend/InvenTree/common/tests.py +++ b/src/backend/InvenTree/common/tests.py @@ -228,9 +228,6 @@ class SettingsTest(InvenTreeTestCase): report_size_obj = InvenTreeSetting.get_setting_object( 'REPORT_DEFAULT_PAGE_SIZE' ) - report_test_obj = InvenTreeSetting.get_setting_object( - 'REPORT_ENABLE_TEST_REPORT' - ) # check settings base fields self.assertEqual(instance_obj.name, 'Server Instance Name') @@ -260,7 +257,6 @@ class SettingsTest(InvenTreeTestCase): # check setting_type self.assertEqual(instance_obj.setting_type(), 'string') - self.assertEqual(report_test_obj.setting_type(), 'boolean') self.assertEqual(stale_days.setting_type(), 'integer') # check as_int @@ -269,9 +265,6 @@ class SettingsTest(InvenTreeTestCase): instance_obj.as_int(), 'InvenTree' ) # not an int -> return default - # check as_bool - self.assertEqual(report_test_obj.as_bool(), True) - # check to_native_value self.assertEqual(stale_days.to_native_value(), 0) diff --git a/src/backend/InvenTree/report/api.py b/src/backend/InvenTree/report/api.py index 75959557f7..f32e7f7d8b 100644 --- a/src/backend/InvenTree/report/api.py +++ b/src/backend/InvenTree/report/api.py @@ -350,6 +350,15 @@ class ReportPrint(GenericAPIView): output = template.render(instance, request) + if template.attach_to_model: + # Attach the generated report to the model instance + data = output.get_document().write_pdf() + instance.create_attachment( + attachment=ContentFile(data, report_name), + comment=_('Report saved at time of printing'), + upload_user=request.user, + ) + # Provide generated report to any interested plugins for plugin in registry.with_mixin('report'): try: diff --git a/src/backend/InvenTree/report/migrations/0028_labeltemplate_attach_to_model_and_more.py b/src/backend/InvenTree/report/migrations/0028_labeltemplate_attach_to_model_and_more.py new file mode 100644 index 0000000000..01fa44c8df --- /dev/null +++ b/src/backend/InvenTree/report/migrations/0028_labeltemplate_attach_to_model_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.15 on 2024-09-05 23:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('report', '0027_alter_labeltemplate_model_type_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='labeltemplate', + name='attach_to_model', + field=models.BooleanField(default=False, help_text='Save report output as an attachment against linked model instance when printing', verbose_name='Attach to Model on Print'), + ), + migrations.AddField( + model_name='reporttemplate', + name='attach_to_model', + field=models.BooleanField(default=False, help_text='Save report output as an attachment against linked model instance when printing', verbose_name='Attach to Model on Print'), + ), + ] diff --git a/src/backend/InvenTree/report/models.py b/src/backend/InvenTree/report/models.py index 58cb2a0998..0972eb8703 100644 --- a/src/backend/InvenTree/report/models.py +++ b/src/backend/InvenTree/report/models.py @@ -163,6 +163,14 @@ class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel): editable=False, ) + attach_to_model = models.BooleanField( + default=False, + verbose_name=_('Attach to Model on Print'), + help_text=_( + 'Save report output as an attachment against linked model instance when printing' + ), + ) + def generate_filename(self, context, **kwargs): """Generate a filename for this report.""" template_string = Template(self.filename_pattern) diff --git a/src/backend/InvenTree/report/serializers.py b/src/backend/InvenTree/report/serializers.py index 1dadc7114f..1bf2986db6 100644 --- a/src/backend/InvenTree/report/serializers.py +++ b/src/backend/InvenTree/report/serializers.py @@ -42,6 +42,7 @@ class ReportSerializerBase(InvenTreeModelSerializer): 'filename_pattern', 'enabled', 'revision', + 'attach_to_model', ] template = InvenTreeAttachmentSerializerField(required=True) diff --git a/src/backend/InvenTree/report/tests.py b/src/backend/InvenTree/report/tests.py index 429f7a3166..a4036f0551 100644 --- a/src/backend/InvenTree/report/tests.py +++ b/src/backend/InvenTree/report/tests.py @@ -555,8 +555,16 @@ class TestReportTest(PrintTestMixins, ReportTest): template = ReportTemplate.objects.filter( enabled=True, model_type='stockitem' ).first() + self.assertIsNotNone(template) + # Ensure that the 'attach_to_model' attribute is initially False + template.attach_to_model = False + template.save() + template.refresh_from_db() + + self.assertFalse(template.attach_to_model) + url = reverse(self.print_url) # Try to print without providing a valid StockItem @@ -568,18 +576,37 @@ class TestReportTest(PrintTestMixins, ReportTest): # Now print with a valid StockItem item = StockItem.objects.first() + n = item.attachments.count() + response = self.post( url, {'template': template.pk, 'items': [item.pk]}, expected_code=201 ) # There should be a link to the generated PDF - self.assertEqual(response.data['output'].startswith('/media/report/'), True) + self.assertTrue(response.data['output'].startswith('/media/report/')) + self.assertTrue(response.data['output'].endswith('.pdf')) # By default, this should *not* have created an attachment against this stockitem + self.assertEqual(n, item.attachments.count()) self.assertFalse( Attachment.objects.filter(model_id=item.pk, model_type='stockitem').exists() ) + # Now try again, but attach the generated PDF to the StockItem + template.attach_to_model = True + template.save() + + response = self.post( + url, {'template': template.pk, 'items': [item.pk]}, expected_code=201 + ) + + # A new attachment should have been created + self.assertEqual(n + 1, item.attachments.count()) + attachment = item.attachments.order_by('-pk').first() + + # The attachment should be a PDF + self.assertTrue(attachment.attachment.name.endswith('.pdf')) + def test_mdl_build(self): """Test the Build model.""" self.run_print_test(Build, 'build', label=False) diff --git a/src/backend/InvenTree/stock/templates/stock/item_base.html b/src/backend/InvenTree/stock/templates/stock/item_base.html index 8550bc9419..05544a9ebb 100644 --- a/src/backend/InvenTree/stock/templates/stock/item_base.html +++ b/src/backend/InvenTree/stock/templates/stock/item_base.html @@ -54,19 +54,15 @@ {% endif %} -{% if test_report_enabled or labels_enabled %}