feat(orion): extend report layout settings to version 1.1

This commit is contained in:
Schubert Ferenc 2026-07-12 14:56:15 +02:00
parent 12e1761a5b
commit 613ffdc8b7
591 changed files with 3601 additions and 999 deletions

View file

@ -2,6 +2,8 @@ from __future__ import annotations
from pathlib import Path
import logging
from datetime import UTC, date, datetime
from types import SimpleNamespace
from sqlalchemy.orm import Session
@ -25,8 +27,17 @@ from app.modules.orion.components import (
TocComponent,
)
from app.modules.orion.context import OrionContextBuilder, ReportContext
from app.modules.orion.html import section, table, text
from app.modules.orion.templates.report import render_document
from app.modules.orion.template_service import REPORT_SECTIONS
from app.models.report_settings import (
OrionImageSize,
OrionLayoutProfile,
OrionReportSettings,
OrionReportSettingsScope,
OrionSignatureMode,
)
from app.schemas.domain import OrionReportSettingsUpdate
logger = logging.getLogger(__name__)
@ -52,10 +63,178 @@ class OrionReportService:
self._append_pdf_attachments(context, output_path)
return output_path
def render_settings_preview_pdf(self, settings_override: OrionReportSettingsUpdate | None = None) -> bytes:
from weasyprint import HTML
context = self._preview_context(settings_override)
preview_section = section(
"layout-preview",
"Vorschau Hauptkapitel",
table(
["Bereich", "Darstellung"],
[
["Layoutprofil", context.report_settings.layout_profile],
["Seitenraender", context.report_settings.page_margin],
["Abschnittsabstaende", context.report_settings.section_spacing],
["Tabellenlayout", context.report_settings.table_layout],
["Tabellenschrift", context.report_settings.table_font_size],
["Bildgroesse", context.report_settings.image_size],
["Bildposition", context.report_settings.image_position],
["Bilder pro Seite", context.report_settings.max_images_per_page],
["Kopfzeile", "Ein" if context.report_settings.show_header else "Aus"],
["Fusszeile", "Ein" if context.report_settings.show_footer else "Aus"],
["Logo", context.report_settings.logo_size],
["Kapitelumbruch", "Ein" if context.report_settings.page_break_before_main_chapters else "Aus"],
],
)
+ (
'<div class="figure-grid">'
+ "".join(
'<figure class="report-figure">'
f'<img class="report-image" src="{text((ORION_ASSET_DIR / "schubamed-logo.svg").resolve().as_uri())}" alt="Beispielbild {index}">'
f'<figcaption class="image-caption">Abbildung {index}: Beispielbild fuer die Layoutvorschau</figcaption>'
"</figure>"
for index in range(1, 5)
)
+ "</div>"
),
"1 Vorschau Hauptkapitel",
)
chapters = [
CoverComponent().render(context),
SummaryComponent().render(context),
TocComponent([{"key": "layout-preview", "number": "1", "title": "Vorschau Hauptkapitel", "toc": True}]).render(context),
CustomerComponent().render(context),
preview_section,
StaticTextComponent("results", "Ergebnisse der Validierung", "Die spaetere Ergebnisbox bleibt Bestandteil des Abschlussbereichs.", number="2").render(context),
]
html = render_document(context, chapters)
return HTML(string=html, base_url=str(ORION_ASSET_DIR)).write_pdf()
def _components(self) -> list[ReportComponent]:
chapters = [self._component_for_section(item) for item in REPORT_SECTIONS]
return [CoverComponent(), SummaryComponent(), TocComponent(REPORT_SECTIONS), CustomerComponent(), *chapters]
def _preview_context(self, settings_override: OrionReportSettingsUpdate | None) -> ReportContext:
if settings_override is None:
report_settings = self._settings_clone()
else:
report_settings = OrionReportSettings(
scope=OrionReportSettingsScope.global_.value,
layout_profile=settings_override.layout_profile.value,
show_cover_result_text=settings_override.show_cover_result_text,
signature_mode=settings_override.signature_mode.value,
image_size=settings_override.image_size.value,
page_break_before_main_chapters=settings_override.page_break_before_main_chapters,
page_margin=settings_override.page_margin.value,
section_spacing=settings_override.section_spacing.value,
table_layout=settings_override.table_layout.value,
table_font_size=settings_override.table_font_size.value,
image_position=settings_override.image_position.value,
max_images_per_page=settings_override.max_images_per_page,
show_image_captions=settings_override.show_image_captions,
show_header=settings_override.show_header,
show_footer=settings_override.show_footer,
logo_size=settings_override.logo_size.value,
compact_cover=settings_override.compact_cover,
)
now = datetime.now(UTC)
validation = SimpleNamespace(
id="preview-validation",
report_number="ORION-LAYOUT-PREVIEW",
validation_type="Layoutvorschau",
project="Administrative Orion-Vorschau",
performed_on=date.today(),
test_location="Schubamed Preview",
examiner_name="Technische Validierung",
status="ENTWURF",
result="BESTANDEN_MIT_AUFLAGEN",
participants="Preview Team",
operator_name="Preview Betreiber",
next_validation_on=date(date.today().year + 2, date.today().month, date.today().day),
version=1,
updated_at=now,
equipment_ids=[],
environment_conditions={},
documentation_checklist=[],
performance_checklist=[],
programs=[],
loading_patterns=[],
measurement_data=[],
drying={},
recommendations=[],
attachments=[],
)
customer = SimpleNamespace(
name="Demo-Praxis Orion",
customer_type=SimpleNamespace(value="practice"),
street="Musterstrasse 12",
postal_code="12345",
city="Berlin",
phone="+49 30 123456",
email="preview@schubamed.de",
hygiene_officer="H. Muster",
quality_manager="Q. Beispiel",
)
location = SimpleNamespace(name="Aufbereitungsraum", street="Musterstrasse 12", postal_code="12345", city="Berlin", room="AEMP")
contact = SimpleNamespace(full_name="Dr. Petra Beispiel", function="Praxisleitung", email="kontakt@example.de", phone="+49 30 123456")
device = SimpleNamespace(
manufacturer="MELAG",
model="Vacuklav 44 B+",
serial_number="PREVIEW-12345",
device_type="Klein-Sterilisator",
year_built=2024,
commissioned_on=date(2024, 5, 15),
chamber_volume_liters=22,
steam_generation="integriert",
water_treatment="VE-Wasser",
documentation="vollstaendig",
supplier="Schubamed",
)
return ReportContext(
validation=validation,
customer=customer,
location=location,
contact=contact,
device=device,
equipment=[],
generated_dir=self.generated_dir,
report_sections=[],
text_blocks={
"summary": SimpleNamespace(
content="Diese Vorschau zeigt die aktuell gewaehlten Orion-Berichtseinstellungen ohne Produktivdaten zu veraendern."
)
},
checklist_templates=[],
confirmed_measurements=[],
report_settings=report_settings,
)
def _settings_clone(self) -> OrionReportSettings:
from app.services.report_settings import OrionReportSettingsService
saved = OrionReportSettingsService(self.session).get_global()
return OrionReportSettings(
scope=saved.scope,
layout_profile=saved.layout_profile,
show_cover_result_text=saved.show_cover_result_text,
signature_mode=saved.signature_mode,
image_size=saved.image_size,
page_break_before_main_chapters=saved.page_break_before_main_chapters,
page_margin=saved.page_margin,
section_spacing=saved.section_spacing,
table_layout=saved.table_layout,
table_font_size=saved.table_font_size,
image_position=saved.image_position,
max_images_per_page=saved.max_images_per_page,
show_image_captions=saved.show_image_captions,
show_header=saved.show_header,
show_footer=saved.show_footer,
logo_size=saved.logo_size,
compact_cover=saved.compact_cover,
updated_by_user_id=saved.updated_by_user_id,
)
def _component_for_section(self, item: dict) -> ReportComponent:
component = item.get("component")
key = str(item["key"])