style(orion): align report header footer and page layout

This commit is contained in:
Schubert Ferenc 2026-07-11 12:19:10 +02:00
parent 302e542fda
commit 503b343070
40 changed files with 2010 additions and 148 deletions

View file

@ -1,6 +1,7 @@
from __future__ import annotations
from datetime import date
from pathlib import Path
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
@ -14,6 +15,9 @@ from app.models.validation import Validation, ValidationStatus
from app.services.validation_workflow import ValidationWorkflowService
from app.schemas.domain import ValidationCreate
from app.modules.orion.service import OrionReportService
from app.modules.orion.assets import SCHUBAMED_LOGO_PATH, schubamed_logo_uri
from app.modules.orion.template_service import ReportTemplateService
from app.modules.helios.service import HeliosImportService
def session() -> Session:
@ -219,7 +223,217 @@ def test_orion_renders_reference_main_chapters(tmp_path):
html = OrionReportService(db, tmp_path).render_html(validation.id)
assert "1. Funktionsqualifikation" in html
assert "2.2 Pruefmittel" in html
assert "4. Ergebnisse der Validierung" in html
assert "9. Kalibrierzertifikate" in html
assert "1 Funktionsqualifikation" in html
assert "2.2 Prüfmittel" in html
assert "4 Ergebnisse der Validierung" in html
assert "9. Werkskalibrierzertifikate Sensoren" in html
def test_orion_renders_uploaded_images_as_real_images(tmp_path):
db = session()
customer, location, device = seed(db)
validation = valid_validation(customer, location, device)
validation.attachments = [
{
"category": "Beladung",
"filename": "beladung.png",
"content_type": "image/png",
"description": "Beladungsmuster Testlauf 1",
"order": 1,
"url": "/uploads/validations/example/beladung.png",
}
]
db.add(validation)
db.flush()
html = OrionReportService(db, tmp_path).render_html(validation.id)
assert '<img class="report-image"' in html
assert "http://localhost:8000/uploads/validations/example/beladung.png" in html
assert "Abbildung 1: Beladungsmuster Testlauf 1" in html
def test_weasyprint_renders_minimal_pdf_bytes():
from weasyprint import HTML
pdf_bytes = HTML(string="<html><body><h1>Orion PDF Test</h1></body></html>").write_pdf()
assert pdf_bytes.startswith(b"%PDF")
assert len(pdf_bytes) > 1024
def test_orion_renders_real_pdf_with_logo(tmp_path):
db = session()
customer, location, device = seed(db)
validation = valid_validation(customer, location, device)
db.add(validation)
db.flush()
assert SCHUBAMED_LOGO_PATH.exists()
html = OrionReportService(db, tmp_path).render_html(validation.id)
pdf_path = OrionReportService(db, tmp_path).render_pdf(validation.id)
assert schubamed_logo_uri() in html
assert "Neutraler Logo-Platzhalter" not in html
assert pdf_path.read_bytes().startswith(b"%PDF")
assert pdf_path.stat().st_size > 1024
def test_orion_header_footer_layout_markers_are_rendered(tmp_path):
db = session()
customer, location, device = seed(db)
validation = valid_validation(customer, location, device)
validation.report_number = "SV-2026-00005"
db.add(validation)
db.flush()
html = OrionReportService(db, tmp_path).render_html(validation.id)
assert 'class="report-header"' in html
assert 'class="report-header-brand"' in html
assert "SCHUBAMED®" in html
assert "Aufbereitung mit System" in html
assert "SV-2026-00005" in html
assert "Version" in html
assert "Datum" in html
assert 'class="report-footer"' in html
assert "Validation Suite" in html
assert "Seite" in html
assert "page-count" in html
def test_orion_layout_css_reserves_header_footer_space():
css = Path("app/modules/orion/templates/report.css").read_text(encoding="utf-8")
assert "margin: 34mm 18mm 24mm 18mm" in css
assert "position: running(report-header)" in css
assert "position: running(report-footer)" in css
assert "grid-template-columns: 1fr 54mm" in css
assert "height: 24mm" in css
assert "height: 20mm" in css
assert "font-size: 22pt" in css
assert "counter(pages)" in css
def test_reference_template_textblocks_and_checklists_are_loaded():
db = session()
bundle = ReportTemplateService(db).ensure_default_template()
assert bundle.template.template_key == "small_steam_sterilizer_initial_validation"
assert ReportTemplateService(db).reference_path.exists()
assert {"summary", "bq_goal", "legal", "performance"} <= set(bundle.text_blocks)
assert len(bundle.checklists) >= 6
assert any(item.title == "Beschreibung Sterilisator" for item in bundle.checklists)
def test_orion_contains_required_reference_sections_and_three_runs(tmp_path):
db = session()
customer, location, device = seed(db)
validation = valid_validation(customer, location, device)
db.add(validation)
db.flush()
html = OrionReportService(db, tmp_path).render_html(validation.id)
for title in [
"Zusammenfassendes Ergebnis der Validierung",
"1.1 Anlass und Ziel der Prüfung",
"1.2 Gesetzliche Grundlagen",
"3.2 Standardbeladung Testlauf 1",
"3.3 Standardbeladung Testlauf 2",
"3.4 Standardbeladung Testlauf 3",
]:
assert title in html
assert "Neutraler Logo-Platzhalter" not in html
assert "report-header-brand" in html
def test_orion_appends_pdf_attachments(tmp_path):
from pypdf import PdfReader, PdfWriter
db = session()
customer, location, device = seed(db)
validation = valid_validation(customer, location, device)
attachment_path = tmp_path / "anlage.pdf"
writer = PdfWriter()
writer.add_blank_page(width=200, height=200)
with attachment_path.open("wb") as handle:
writer.write(handle)
validation.attachments = [
{
"category": "Kalibrierschein",
"filename": "anlage.pdf",
"content_type": "application/pdf",
"description": "Anlage",
"order": 1,
"storage_path": str(attachment_path),
}
]
db.add(validation)
db.flush()
pdf_path = OrionReportService(db, tmp_path).render_pdf(validation.id)
assert len(PdfReader(str(pdf_path)).pages) >= 2
def test_winlog_pdf_import_preview_and_confirmation(tmp_path):
from weasyprint import HTML
db = session()
customer, location, device = seed(db)
validation = valid_validation(customer, location, device)
db.add(validation)
db.flush()
pdf_bytes = HTML(
string="<p>Vakuumtest</p><p>Programm: Vakuum</p><p>Leckrate: 0,1</p><p>Ergebnis: bestanden</p>"
).write_pdf()
preview = HeliosImportService(db, tmp_path).save_winlog_pdf(
validation.id, "winlog.pdf", pdf_bytes
)
assert preview["status"] == "VORSCHAU_BEREIT"
assert preview["sha256"]
assert any(value["field_name"] == "leak_rate" for value in preview["values"])
first = preview["values"][0]
confirmed = HeliosImportService(db, tmp_path).confirm_values(
preview["id"], [{"id": first["id"], "confirmed": True, "corrected_value": "korrigiert"}]
)
assert confirmed["status"] == "BESTAETIGT"
def test_unreadable_winlog_pdf_does_not_crash(tmp_path):
db = session()
customer, location, device = seed(db)
validation = valid_validation(customer, location, device)
db.add(validation)
db.flush()
preview = HeliosImportService(db, tmp_path).save_winlog_pdf(
validation.id, "broken.pdf", b"not a pdf"
)
assert preview["status"] in {"FEHLER", "NUR_ANLAGE"}
assert preview["values"] == []
def test_confirmed_measurements_appear_in_orion_report(tmp_path):
from weasyprint import HTML
db = session()
customer, location, device = seed(db)
validation = valid_validation(customer, location, device)
db.add(validation)
db.flush()
pdf_bytes = HTML(string="<p>Testlauf 1</p><p>Leckrate: 0,2</p>").write_pdf()
preview = HeliosImportService(db, tmp_path).save_winlog_pdf(validation.id, "winlog.pdf", pdf_bytes)
leak_rate = next(value for value in preview["values"] if value["field_name"] == "leak_rate")
HeliosImportService(db, tmp_path).confirm_values(
preview["id"], [{"id": leak_rate["id"], "confirmed": True}]
)
html = OrionReportService(db, tmp_path).render_html(validation.id)
assert "leak_rate" in html
assert "0,2" in html