113 lines
4.6 KiB
Python
113 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import logging
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.modules.orion.assets import ORION_ASSET_DIR
|
|
from app.modules.orion.components import (
|
|
AttachmentComponent,
|
|
ChecklistComponent,
|
|
CoverComponent,
|
|
CustomerComponent,
|
|
DeviceComponent,
|
|
DryingComponent,
|
|
EnvironmentComponent,
|
|
EquipmentComponent,
|
|
LoadingComponent,
|
|
MeasurementComponent,
|
|
ProgramComponent,
|
|
RecommendationComponent,
|
|
ReportComponent,
|
|
StaticTextComponent,
|
|
SummaryComponent,
|
|
TocComponent,
|
|
)
|
|
from app.modules.orion.context import OrionContextBuilder, ReportContext
|
|
from app.modules.orion.templates.report import render_document
|
|
from app.modules.orion.template_service import REPORT_SECTIONS
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class OrionReportService:
|
|
def __init__(self, session: Session, generated_dir: Path | None = None) -> None:
|
|
self.session = session
|
|
self.generated_dir = generated_dir or Path("/app/reports")
|
|
|
|
def render_html(self, validation_id: str) -> str:
|
|
context = OrionContextBuilder(self.session, self.generated_dir).build(validation_id)
|
|
components = self._components()
|
|
chapters = [component.render(context) for component in components]
|
|
return render_document(context, chapters)
|
|
|
|
def render_pdf(self, validation_id: str) -> Path:
|
|
from weasyprint import HTML
|
|
|
|
context = OrionContextBuilder(self.session, self.generated_dir).build(validation_id)
|
|
output_path = context.generated_dir / f"{context.validation.report_number}.pdf"
|
|
html = self.render_html(validation_id)
|
|
HTML(string=html, base_url=str(ORION_ASSET_DIR)).write_pdf(output_path)
|
|
self._append_pdf_attachments(context, output_path)
|
|
return output_path
|
|
|
|
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 _component_for_section(self, item: dict) -> ReportComponent:
|
|
component = item.get("component")
|
|
key = str(item["key"])
|
|
title = str(item["title"])
|
|
number = item.get("number")
|
|
if component == "device":
|
|
return DeviceComponent(key, title, number)
|
|
if component == "checklist":
|
|
return ChecklistComponent(key, title, number)
|
|
if component == "environment":
|
|
return EnvironmentComponent(key, title, number)
|
|
if component == "programs":
|
|
return ProgramComponent(key, title, number)
|
|
if component == "loading":
|
|
return LoadingComponent(key, title, number)
|
|
if component == "equipment":
|
|
return EquipmentComponent(key, title, number)
|
|
if component == "measurements":
|
|
return MeasurementComponent(key, title, number)
|
|
if component == "drying":
|
|
return DryingComponent(key, title, number)
|
|
if component == "recommendations":
|
|
return RecommendationComponent(key, title, number)
|
|
if component == "attachments":
|
|
return AttachmentComponent(key, title, number)
|
|
return StaticTextComponent(key, title, block_key=item.get("block_key"), number=number)
|
|
|
|
def _append_pdf_attachments(self, context: ReportContext, output_path: Path) -> None:
|
|
pdf_paths = []
|
|
for item in context.validation.attachments or []:
|
|
filename = str(item.get("filename") or "")
|
|
content_type = str(item.get("content_type") or "")
|
|
if not filename.lower().endswith(".pdf") and content_type != "application/pdf":
|
|
continue
|
|
storage_path = item.get("storage_path")
|
|
if storage_path and Path(str(storage_path)).exists():
|
|
pdf_paths.append(Path(str(storage_path)))
|
|
if not pdf_paths:
|
|
return
|
|
try:
|
|
from pypdf import PdfReader, PdfWriter
|
|
|
|
writer = PdfWriter()
|
|
for page in PdfReader(str(output_path)).pages:
|
|
writer.add_page(page)
|
|
for pdf_path in pdf_paths:
|
|
try:
|
|
for page in PdfReader(str(pdf_path)).pages:
|
|
writer.add_page(page)
|
|
except Exception:
|
|
logger.exception("Could not append attachment PDF %s", pdf_path)
|
|
with output_path.open("wb") as handle:
|
|
writer.write(handle)
|
|
except Exception:
|
|
logger.exception("Could not merge Orion PDF attachments for validation %s", context.validation.id)
|