fix(b09-print): 도면 영역 이어 붙임 — 초안 router 가 스스로 단 /api/projects 머리를 사본 길에서 떼고 끼움(앞머리 두 번 겹침 없앰) · 못 넣은 그림 수 알림 (302-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
import importlib
|
||||
import logging
|
||||
from pathlib import Path
|
||||
@@ -347,11 +348,20 @@ router.include_router(quotes_router)
|
||||
|
||||
# 302 초안 길(draft/*_Router.py 의 router · 폴더째 지우면 길도 없음) — 같은 머리 · 같은 권한
|
||||
def include_drafts(target: APIRouter, folder: Path, package: str) -> list[str]:
|
||||
"""folder 의 *_Router.py 마다 router 를 끼움 → 끼운 파일 이름(못 읽은 초안은 건너뜀)."""
|
||||
"""folder 의 *_Router.py 마다 router 를 끼움 → 끼운 파일 이름(못 읽은 초안은 건너뜀).
|
||||
초안이 같은 머리(/api/projects)를 스스로 달았으면 떼고 끼움 — 머리가 두 번 붙지 않게."""
|
||||
done = []
|
||||
for path in sorted(folder.glob("*_Router.py")):
|
||||
try:
|
||||
target.include_router(importlib.import_module(f"{package}.{path.stem}").router)
|
||||
draft = importlib.import_module(f"{package}.{path.stem}").router
|
||||
if target.prefix and draft.prefix.startswith(target.prefix):
|
||||
# 길은 만들 때 머리가 박힘 — 사본 길에서 떼어 냄(원본은 앱에 바로 붙일 때 그대로)
|
||||
shim, cut = copy.copy(draft), len(target.prefix)
|
||||
shim.routes = [copy.copy(route) for route in draft.routes]
|
||||
for route in shim.routes:
|
||||
route.path = route.path[cut:]
|
||||
draft = shim
|
||||
target.include_router(draft)
|
||||
done.append(path.stem)
|
||||
except (ImportError, AttributeError) as error: # 한 초안이 막혀도 B09 는 뜸
|
||||
logging.getLogger(__name__).warning("초안 길 %s 못 읽음 — %s", path.name, error)
|
||||
|
||||
@@ -49,6 +49,8 @@ interface Area {
|
||||
items: DrawingItem[];
|
||||
pdf: (ids: string[]) => Promise<Blob>;
|
||||
xlsx?: (ids: string[]) => Promise<Blob>;
|
||||
/** 그 PDF 곁 알림(도면 = 못 넣은 그림 수) */
|
||||
note?: (pdf: Blob) => string;
|
||||
}
|
||||
|
||||
async function post(projectId: string, path: string, body: unknown): Promise<Blob> {
|
||||
@@ -88,7 +90,12 @@ async function loadArea(projectId: string, id: AreaId): Promise<Area | null> {
|
||||
const list = mod.fetchDrawingList as FetchDrawingList | undefined;
|
||||
const pdf = mod.fetchDrawingsPdf as FetchDrawingsPdf | undefined;
|
||||
if (!list || !pdf) return null;
|
||||
return { items: await list(projectId), pdf: (ids) => pdf(projectId, ids) };
|
||||
const skipped = mod.skippedImagesOf as ((b: Blob) => Record<string, number>) | undefined;
|
||||
const note = (b: Blob) => {
|
||||
const n = Object.values(skipped?.(b) ?? {}).reduce((a, v) => a + v, 0);
|
||||
return n ? ` · 못 넣은 그림 ${n}개` : "";
|
||||
};
|
||||
return { items: await list(projectId), pdf: (ids) => pdf(projectId, ids), note };
|
||||
}
|
||||
const collect = mod[id === "quantity" ? "collectQuantitySheets" : "collectCostSheets"] as
|
||||
CollectSheets | undefined;
|
||||
@@ -160,7 +167,7 @@ export function renderB09Print(root: HTMLElement): MountedPage {
|
||||
if (my !== seq) return;
|
||||
shown!.url = URL.createObjectURL(b);
|
||||
frame.src = shown!.url;
|
||||
say(`${label()} · ${ids.length}쪽 묶음`);
|
||||
say(`${label()} · ${ids.length}장 묶음${area?.note?.(b) ?? ""}`);
|
||||
},
|
||||
(error: unknown) => my === seq && say(`PDF 를 못 만들었습니다 — ${errText(error)}`, true),
|
||||
);
|
||||
|
||||
@@ -132,6 +132,21 @@ def test_서버_초안_길_끼우기_폴더_없으면_길_없음(tmp_path, monke
|
||||
got = APIRouter()
|
||||
assert b09.include_drafts(got, pkg / "draft", "fakepkg.draft") == ["X_Router"]
|
||||
assert [r.path for r in got.routes] == ["/x"]
|
||||
# 초안이 같은 머리를 스스로 달았으면 한 번만(도면 302-2 꼴)
|
||||
(pkg / "draft/Z_Router.py").write_text(
|
||||
"from fastapi import APIRouter\nrouter = APIRouter(prefix='/api/projects')\n"
|
||||
"@router.get('/{project_id}/z')\ndef z():\n return 1\n"
|
||||
)
|
||||
head = APIRouter(prefix="/api/projects")
|
||||
assert b09.include_drafts(head, pkg / "draft", "fakepkg.draft") == ["X_Router", "Z_Router"]
|
||||
assert {r.path for r in head.routes} == {"/api/projects/x", "/api/projects/{project_id}/z"}
|
||||
assert [r.path for r in sys.modules["fakepkg.draft.Z_Router"].router.routes] == [
|
||||
"/api/projects/{project_id}/z"
|
||||
] # 원본 그대로(앱에 바로 붙이는 시험)
|
||||
assert (
|
||||
"/api/projects/{project_id}/print/drawings-pdf" in paths
|
||||
or not (DRAFT / "B09_Draft_Print_Drawings_Router.py").exists()
|
||||
)
|
||||
|
||||
|
||||
def test_로그인_회사_권한은_B09_길과_같음():
|
||||
@@ -313,3 +328,12 @@ def test_원가_영역_PDF_시트_이름_가로(cost_bodies):
|
||||
text = "".join(p.get_text() for p in doc)
|
||||
assert all(s["name"] in text for s in body["sheets"])
|
||||
assert all(p.rect.width > p.rect.height for p in doc)
|
||||
|
||||
|
||||
def test_앱_전체_길_목록에_겹친_앞머리_없음():
|
||||
import main
|
||||
|
||||
paths = [getattr(r, "path", "") for r in main.app.routes]
|
||||
assert not [p for p in paths if "/api/projects/api/projects" in p]
|
||||
for tail in ("drawings", "drawings-pdf", "table-pdf", "table-xlsx"):
|
||||
assert paths.count(f"/api/projects/{{project_id}}/print/{tail}") == 1, tail
|
||||
|
||||
Reference in New Issue
Block a user