"""인쇄 미리보기 도면(PLAN 302-2) — 목록 · PDF 길. GET /api/projects/{id}/print/drawings → [{id, source, kind, label, number, confirmed, paper, images_skipped}] POST /api/projects/{id}/print/drawings-pdf {ids?} → application/pdf 목록 차례 · 장마다 한 쪽 · 머리 X-Aislo-Pages · X-Aislo-Skipped-Images(못 넣은 그림 {id: 수} JSON) 로그인 · 회사 · 프로젝트 권한은 이 라우터가 스스로 검(B09 라우터가 include 하든 앱이 바로 붙이든). 저장 안 함 — 매번 만듦 · B07 정본 · 확정 상태 그대로. """ from __future__ import annotations import asyncio import json import logging from uuid import UUID import pymupdf from fastapi import APIRouter, Depends, Response from fastapi.responses import JSONResponse from pydantic import BaseModel from B09_Estimation.draft import B09_Draft_Print_Drawings_Source as source from B09_Estimation.draft.B09_Draft_Print_Drawings_Pdf import add_drawing_page from common_util.common_util_auth import require_company, require_project_access, verify_session logger = logging.getLogger(__name__) router = APIRouter( prefix="/api/projects", tags=["B09 Print Drawings (draft)"], dependencies=[ Depends(verify_session), Depends(require_company), Depends(require_project_access), ], ) class DrawingsPdfRequest(BaseModel): ids: list[str] | None = None def _error(status: int, message: str) -> JSONResponse: return JSONResponse(status_code=status, content={"status": "error", "message": message}) @router.get("/{project_id}/print/drawings") async def get_print_drawings(project_id: UUID) -> JSONResponse: try: items, _ = await source.drawing_items(project_id) except Exception: logger.exception("인쇄 도면 목록 실패: %s", project_id) return _error(500, "도면 목록을 읽지 못했습니다.") return JSONResponse(items) def _pdf(drawings: list[dict], ids: list[str]) -> tuple[bytes, dict[str, int]]: doc = pymupdf.open() skipped: dict[str, int] = {} for drawing_id, drawing in zip(ids, drawings, strict=True): _, missing = add_drawing_page(doc, drawing) if missing: skipped[drawing_id] = missing doc.subset_fonts() # 한글 글꼴 = 쓴 글자만 return doc.tobytes(garbage=3, deflate=True), skipped @router.post("/{project_id}/print/drawings-pdf") async def post_print_drawings_pdf( project_id: UUID, request: DrawingsPdfRequest | None = None ) -> Response: try: items, context = await source.drawing_items(project_id) by_id = {item["id"]: item for item in items} ids = request.ids if request and request.ids is not None else list(by_id) unknown = [one for one in ids if one not in by_id] if unknown: return _error(400, f"목록에 없는 도면입니다: {', '.join(unknown)}") if not ids: return _error(404, "인쇄할 도면이 없습니다.") chosen = [by_id[one] for one in ids] drawings = await source.drawings_in_order(project_id, chosen, context) body, skipped = await asyncio.to_thread(_pdf, drawings, ids) except ValueError as exc: return _error(400, str(exc)) except Exception: logger.exception("인쇄 도면 PDF 실패: %s", project_id) return _error(500, "도면 PDF 를 만들지 못했습니다.") return Response( body, media_type="application/pdf", headers={ "Content-Disposition": 'inline; filename="drawings.pdf"', "X-Aislo-Pages": str(len(ids)), "X-Aislo-Skipped-Images": json.dumps(skipped), }, )