- 길 = 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
100 lines
3.6 KiB
Python
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),
|
|
},
|
|
)
|