Files
Aislo/B09_Estimation/draft/B09_Draft_Print_Drawings_Router.py
T
eomsangdonandClaude Opus 5.5 f4889352f9 feat(b09-print): 인쇄 미리보기 도면 PDF — B07 도면 + 구조물 조합 도면 · 도각 크기 쪽 · 숨긴 층 뺌 · 벡터 · 한글 (302-2)
- 길 = GET /api/projects/{id}/print/drawings · POST /api/projects/{id}/print/drawings-pdf {ids?} (draft 라우터 · 로그인 · 회사 · 프로젝트 권한 스스로)
- 차례 = B07 도면번호 → 구조물 조합 도면(도번 차례 · 도번마다 한 번 · 더한장)
- B07 = B07 단건 조회 그대로(확정본 · 미확정 조립) · 미확정 횡단 장 구조물 = 화면 TS 를 서버 Node 로(번들 tmp · package.json 안 건드림)
- 쪽 = 도각 양식 실치수(A1 840×594) · 색 = openwebcad 밝은 바탕 규칙 · 글 = 맑은 고딕 심음(subset) · 그림 = data URL 넣음 · 못 넣은 수 = 응답 머리
- 화면 쪽 = fetchDrawingList · fetchDrawingsPdf(약속 꼴) · skippedImagesOf
- requirements = PyMuPDF · ezdxf

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
2026-10-13 08:01:47 +09:00

100 lines
3.6 KiB
Python

"""인쇄 미리보기 도면(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),
},
)