feat: FASE 6.5 - Report PDF (WeasyPrint + Kaleido)
Add PDF report generation for Metrologist dashboard with SPC and measurement reports including SVG charts, capability indices, and company logo embedding. New files: - server/services/report_service.py (Jinja2 + Plotly/Kaleido + WeasyPrint) - server/routers/reports.py (2 GET endpoints with auth) - server/templates/reports/ (base, spc, measurement HTML templates) Modified: - server/main.py (register reports router) - client dashboard (download buttons + proxy routes) - i18n strings IT/EN Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""Reports router — PDF report generation endpoints for Metrologist."""
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from fastapi.responses import Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database import get_db
|
||||
from middleware.api_key import require_metrologist
|
||||
from models.user import User
|
||||
from services.report_service import generate_measurement_report, generate_spc_report
|
||||
|
||||
router = APIRouter(prefix="/api/reports", tags=["reports"])
|
||||
|
||||
|
||||
@router.get("/spc")
|
||||
async def download_spc_report(
|
||||
recipe_id: int = Query(...),
|
||||
subtask_id: int = Query(...),
|
||||
version_id: int | None = Query(None),
|
||||
date_from: datetime | None = Query(None),
|
||||
date_to: datetime | None = Query(None),
|
||||
operator_id: int | None = Query(None),
|
||||
lot_number: str | None = Query(None),
|
||||
serial_number: str | None = Query(None),
|
||||
user: User = Depends(require_metrologist),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> Response:
|
||||
"""Generate and download SPC PDF report."""
|
||||
try:
|
||||
pdf_bytes = await generate_spc_report(
|
||||
db, recipe_id, subtask_id,
|
||||
version_id, date_from, date_to,
|
||||
operator_id, lot_number, serial_number,
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Report generation failed: {str(e)}",
|
||||
)
|
||||
|
||||
filename = f"spc_report_recipe{recipe_id}_st{subtask_id}.pdf"
|
||||
return Response(
|
||||
content=pdf_bytes,
|
||||
media_type="application/pdf",
|
||||
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/measurements")
|
||||
async def download_measurement_report(
|
||||
recipe_id: int = Query(...),
|
||||
subtask_id: int | None = Query(None),
|
||||
version_id: int | None = Query(None),
|
||||
date_from: datetime | None = Query(None),
|
||||
date_to: datetime | None = Query(None),
|
||||
operator_id: int | None = Query(None),
|
||||
lot_number: str | None = Query(None),
|
||||
serial_number: str | None = Query(None),
|
||||
user: User = Depends(require_metrologist),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> Response:
|
||||
"""Generate and download measurement table PDF report."""
|
||||
try:
|
||||
pdf_bytes = await generate_measurement_report(
|
||||
db, recipe_id, subtask_id,
|
||||
version_id, date_from, date_to,
|
||||
operator_id, lot_number, serial_number,
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Report generation failed: {str(e)}",
|
||||
)
|
||||
|
||||
filename = f"measurements_report_recipe{recipe_id}.pdf"
|
||||
return Response(
|
||||
content=pdf_bytes,
|
||||
media_type="application/pdf",
|
||||
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||
)
|
||||
Reference in New Issue
Block a user