2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-01 19:20:55 +00:00

Report merge

This commit is contained in:
Tristan Le
2025-04-18 16:24:36 +00:00
parent 67bdf3162a
commit a92a1e134a
5 changed files with 111 additions and 20 deletions

View File

@ -17,7 +17,7 @@ repos:
- id: check-yaml - id: check-yaml
- id: mixed-line-ending - id: mixed-line-ending
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.0 rev: v0.11.6
hooks: hooks:
- id: ruff-format - id: ruff-format
args: [--preview] args: [--preview]
@ -28,7 +28,7 @@ repos:
--preview --preview
] ]
- repo: https://github.com/astral-sh/uv-pre-commit - repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.6.6 rev: 0.6.14
hooks: hooks:
- id: pip-compile - id: pip-compile
name: pip-compile requirements-dev.in name: pip-compile requirements-dev.in
@ -70,13 +70,13 @@ repos:
src/frontend/vite.config.ts | src/frontend/vite.config.ts |
)$ )$
- repo: https://github.com/biomejs/pre-commit - repo: https://github.com/biomejs/pre-commit
rev: v1.9.4 rev: v2.0.0-beta.1
hooks: hooks:
- id: biome-check - id: biome-check
additional_dependencies: ["@biomejs/biome@1.9.4"] additional_dependencies: ["@biomejs/biome@1.9.4"]
files: ^src/frontend/.*\.(js|ts|tsx)$ files: ^src/frontend/.*\.(js|ts|tsx)$
- repo: https://github.com/gitleaks/gitleaks - repo: https://github.com/gitleaks/gitleaks
rev: v8.24.0 rev: v8.24.3
hooks: hooks:
- id: gitleaks - id: gitleaks
language_version: 1.23.6 language_version: 1.23.6

View File

@ -0,0 +1,22 @@
# Generated by Django 4.2.20 on 2025-04-03 01:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("report", "0029_remove_reportoutput_template_and_more"),
]
operations = [
migrations.AddField(
model_name="reporttemplate",
name="merge",
field=models.BooleanField(
default=False,
help_text="Render a single report against selected items",
verbose_name="Merge",
),
),
]

View File

@ -170,10 +170,12 @@ class ReportContextExtension(TypedDict):
Attributes: Attributes:
page_size: The page size of the report page_size: The page size of the report
landscape: Boolean value, True if the report is in landscape mode landscape: Boolean value, True if the report is in landscape mode
merge: Boolean value, True if the a single report is generated against multiple items
""" """
page_size: str page_size: str
landscape: bool landscape: bool
merge: bool
class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel): class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel):
@ -366,6 +368,12 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
help_text=_('Render report in landscape orientation'), help_text=_('Render report in landscape orientation'),
) )
merge = models.BooleanField(
default=False,
verbose_name=_('Merge'),
help_text=_('Render a single report against selected items'),
)
def get_report_size(self) -> str: def get_report_size(self) -> str:
"""Return the printable page size for this report.""" """Return the printable page size for this report."""
try: try:
@ -382,14 +390,21 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
return page_size return page_size
def get_context(self, instance, request=None, **kwargs): def get_report_context(self):
"""Supply context data to the report template for rendering.""" """Return report template context."""
base_context = super().get_context(instance, request)
report_context: ReportContextExtension = { report_context: ReportContextExtension = {
'page_size': self.get_report_size(), 'page_size': self.get_report_size(),
'landscape': self.landscape, 'landscape': self.landscape,
'merge': self.merge,
} }
return report_context
def get_context(self, instance, request=None, **kwargs):
"""Supply context data to the report template for rendering."""
base_context = super().get_context(instance, request)
report_context: ReportContextExtension = self.get_report_context()
context = {**base_context, **report_context} context = {**base_context, **report_context}
# Pass the context through to the plugin registry for any additional information # Pass the context through to the plugin registry for any additional information
@ -455,21 +470,23 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
output.save() output.save()
try: try:
for instance in items: if self.merge:
context = self.get_context(instance, request) base_context = super().base_context(request)
report_context = self.get_report_context()
item_contexts = []
for instance in items:
item_contexts.append(instance.report_context())
contexts = {
**base_context,
**report_context,
'instances': item_contexts,
}
if report_name is None: if report_name is None:
report_name = self.generate_filename(context) report_name = self.generate_filename(contexts)
# Render the report output html = render_to_string(self.template_name, contexts, request)
try: report = HTML(string=html).write_pdf()
if debug_mode:
report = self.render_as_string(instance, request)
else:
report = self.render(instance, request)
except TemplateDoesNotExist as e:
t_name = str(e) or self.template
raise ValidationError(f'Template file {t_name} does not exist')
outputs.append(report) outputs.append(report)
@ -495,6 +512,47 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
# Update the progress of the report generation # Update the progress of the report generation
output.progress += 1 output.progress += 1
output.save() output.save()
else:
for instance in items:
context = self.get_context(instance, request)
if report_name is None:
report_name = self.generate_filename(context)
# Render the report output
try:
if debug_mode:
report = self.render_as_string(instance, request)
else:
report = self.render(instance, request)
except TemplateDoesNotExist as e:
t_name = str(e) or self.template
raise ValidationError(f'Template file {t_name} does not exist')
outputs.append(report)
# Attach the generated report to the model instance (if required)
if self.attach_to_model and not debug_mode:
instance.create_attachment(
attachment=ContentFile(report, report_name),
comment=_(f'Report generated from template {self.name}'),
upload_user=request.user
if request and request.user.is_authenticated
else None,
)
# Provide generated report to any interested plugins
for plugin in report_plugins:
try:
plugin.report_callback(self, instance, report, request)
except Exception:
InvenTree.exceptions.log_error(
f'plugins.{plugin.slug}.report_callback'
)
# Update the progress of the report generation
output.progress += 1
output.save()
except Exception as exc: except Exception as exc:
# Something went wrong during the report generation process # Something went wrong during the report generation process

View File

@ -65,7 +65,12 @@ class ReportTemplateSerializer(ReportSerializerBase):
"""Metaclass options.""" """Metaclass options."""
model = report.models.ReportTemplate model = report.models.ReportTemplate
fields = [*ReportSerializerBase.base_fields(), 'page_size', 'landscape'] fields = [
*ReportSerializerBase.base_fields(),
'page_size',
'landscape',
'merge',
]
page_size = serializers.ChoiceField( page_size = serializers.ChoiceField(
required=False, required=False,

View File

@ -21,6 +21,12 @@ function ReportTemplateTable() {
<YesNoButton value={instance.landscape} /> <YesNoButton value={instance.landscape} />
) )
}, },
merge: {
label: t`Merge`,
modelRenderer: (instance: any) => (
<YesNoButton value={instance.merge} />
)
},
attach_to_model: { attach_to_model: {
label: t`Attach to Model`, label: t`Attach to Model`,
modelRenderer: (instance: any) => ( modelRenderer: (instance: any) => (