feat(B09 설계내역서 펼침): 우클릭 복사해 새 일위대가 · 내 자료집에 저장 · 어디 쓰였나 (PLAN 703-2)

- 복사 = 덮개 K:… 의 @copy {from · name · spec} + 원본 덮개째(B09 사용자 값 · 브레인 정함) — 셈 짝 Calc_Labor py·ts 가 원본 블록을 본뜬 새 호표(번호 끝 · 기본값 = 원본) · 이 줄 단가연결을 새 호표로 바꿀지 물음 · 복사본 지우기(잇는 줄 있으면 막음)
- 원본 없음 = 짝 잃음 알림 · 저장 다듬기 @copy 글만 · 서고 사본 원본 = prune 에서 지킴
- 내 자료집 = POST …/estimation/daega/personal → 개인 층 사전조합(B09_Estimation_Daega_Personal) — 넣을 값 · 고른 자재 · 줄 수량 덮개(손) 담음 · 품 · 단가 덮개 = 안 담은 수 알림 · 같은 이름 409 → 덮어쓰기 · 새 이름
- 어디 쓰였나 = 누른 줄 노임 · 자재 · 중기(그 밖 = 호표)를 쓰는 내역 줄 목록 → 누르면 그 줄로 · 펼침
- 새 파일 B09_Estimation_UI_Tabs_Bill_Fold_Menu.ts · 시험 test_703_b09_daega_copy(거울 · 임시 storage 길)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 13:35:44 +09:00
co-authored by Claude Opus 5.5
parent 595d6415eb
commit 1c1dc9c523
9 changed files with 693 additions and 14 deletions
+37
View File
@@ -24,6 +24,9 @@
운반공 줄 = {given, own, dist} 같이 → 거리 값 사본(263-2 · 폴더 `~값 해시`)
POST /api/projects/{id}/estimation/library-inputs {kind, layer, owner, key, lib?}
「운반 단가」 모달(263-2) — 담은 로직 줄 입력 · 값(읽기만 · 권한 = library 길과 같음)
POST /api/projects/{id}/estimation/daega/personal {cover, name?, overwrite?, user?}
「내 자료집에 저장」(703-2) — 펼친 일위대가 → 개인 층 사전조합
(`B09_Estimation_Daega_Personal`) · user = 화면 캐시 덮개 · 같은 이름 = 409 {같은이름} → 덮어쓰기(overwrite = 키) · 새 이름
운반 사본 연결 = 셈 때마다 거리로 사본 다시 고름(`Bill_Library.resolve_hauls` · `fix_keys`).
저장 자리 = 프로젝트 안 `B09_Estimation/v1/`(`B09_Estimation_Store`) ·
@@ -53,6 +56,7 @@ from B08_Quantity.B08_Quantity_Router_Material import material_sheets
from B08_Quantity.B08_Quantity_Router_Milestone import read_data as read_milestone
from B08_Quantity.B08_Quantity_Router_Milestone import site_info
from B09_Estimation import B09_Estimation_Bill_Library as library
from B09_Estimation import B09_Estimation_Daega_Personal as personal
from B09_Estimation import B09_Estimation_Prices as prices
from B09_Estimation import B09_Estimation_Sangeun as sangeun
from B09_Estimation import B09_Estimation_Store as store
@@ -71,6 +75,7 @@ from common_util.common_util_project_settings import reset_section
from common_util.common_util_workflow_state import mark_stage
from M02_MasterTemplete import M02_Layer_Access as access
from M02_MasterTemplete.M02_Item_Snapshot import importer
from M02_MasterTemplete.M02_MasterTemplete_Router_Items import _call, _root
try: # 252 공기 초안(draft 폴더 · 지우면 공기 탭 원천 없이)
from B09_Estimation.draft import B09_Draft_Schedule_Source as schedule_draft
@@ -381,6 +386,38 @@ async def library_inputs(
return {"lines": lines}
class PersonalBody(BaseModel):
cover: str = Field(min_length=1, max_length=200)
name: str | None = Field(None, max_length=200)
overwrite: str | None = Field(None, max_length=20)
user: dict[str, Any] | None = None
@router.post("/{project_id}/estimation/daega/personal")
async def save_personal(
project_id: UUID, body: PersonalBody, session: dict[str, Any] = Depends(verify_session)
) -> dict[str, Any]:
root = await _project_root(project_id)
raw = (
body.user if isinstance(body.user, dict) else await asyncio.to_thread(store.read_user, root)
)
cover = store.clean_user(raw).get("daega_cover") or {}
try:
made = await asyncio.to_thread(personal.build, root, body.cover, cover, body.name)
except LookupError as error:
raise HTTPException(status_code=404, detail=str(error)) from error
if not made["문서"]["이름"]:
raise HTTPException(status_code=400, detail="이름이 빔")
folder = await asyncio.to_thread(_root, session, "personal", None, True)
got = await asyncio.to_thread(_call, folder, personal.write, made["문서"], body.overwrite)
return {
"키": got["이름"],
"이름": made["문서"]["이름"],
"담음": made["담음"],
"안담음": made["안담음"],
}
# 166-4 견적서 파일 길 — 같은 머리 · 같은 권한
router.include_router(quotes_router)