feat(B09): 일위대가 탭 — 목록표+본표 2단, 원천 표시, 층 파고들기

**API 2종** (`B09_Estimation_Router.py`)
- `GET …/estimation/unit-prices` — **목록표** + **산출 요약**. 요약을 같이 보내는 까닭은
  사용자가 「무엇이 안 선 상태인가」를 화면에서 알아야 하기 때문임(자재 카탈로그
  미확보로 구조물 계열이 안 섬).
- `GET …/estimation/unit-prices/{code}` — **본표**. 줄마다 원천(자재5·노임6·기계경비105·
  일위대가103·단가산출104)과 **파고들기 가능 여부**가 붙음. 없는 코드는 404.
- 품셈 3 MB 를 요청마다 다시 안 읽게 `cached_build()` 로 한 번만 조립.

**화면** (`B09_Estimation_UI_Page.ts`)
- 일위대가 탭 활성화. **목록표(위) + 본표(아래) 2단** — 9-3 「제목+상세 한 쌍」이 화면에도
  그대로 섬.
- 본표 줄마다 `원천(번호)` 표시, **기계 줄을 누르면 그 시간당 사용료 본표로 파고듦**
  (거기서 취득가·연료·조종원까지 보임). 값을 못 믿을 때 사람이 하는 일이 이것임.
- **재료·노무·경비 3분할 + 합계 줄**, `TC = NC + GC + JC` 성립 여부를 화면 문구로 냄.
- **산출 요약을 화면에 표시** — 자재가 없어 구조물 계열이 못 선다는 것을 그 자리에 적음.

locale 은 **B09 키만** 추가(16줄), 공용 파일 다른 줄 무수정.

⚠ 화면 조작 검증은 다음 단계 — `tsc` 는 통과했고(남은 오류 1건은 메인 창 B08 파일),
백엔드 재시작·클릭 검증은 이어서 함.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-07 23:13:20 +09:00
co-authored by Claude Opus 5
parent 856be1f45b
commit 6c4d0251db
4 changed files with 467 additions and 3 deletions
+46
View File
@@ -26,7 +26,14 @@ from B09_Estimation.B09_Estimation_Engine_Cost import (
proposed_profit_adjustment,
)
from B09_Estimation.B09_Estimation_Rates import RateLookupError
from B09_Estimation.B09_Estimation_PriceBook import PriceBookError
from B09_Estimation.B09_Estimation_Statutory import STATUTORY_ITEMS
from B09_Estimation.B09_Estimation_UnitPrice import (
build_summary,
cached_build,
detail_of,
list_unit_prices,
)
from common_util.common_util_workflow_state import complete_stage
from config.config_db import get_db_pool
@@ -154,6 +161,45 @@ async def list_items(project_id: UUID) -> JSONResponse:
)
@router.get("/{project_id}/estimation/unit-prices")
async def list_unit_price_titles(project_id: UUID) -> JSONResponse:
"""일위대가 **목록표** — 「무엇이 있나」 한 줄씩 + 산출 요약.
요약을 같이 보내는 까닭은 사용자가 **「무엇이 안 선 상태인가」를 화면에서**
알아야 하기 때문이다(자재 카탈로그 미확보로 구조물 계열이 안 섬).
"""
try:
build = cached_build()
return JSONResponse(
content={
"status": "success",
"summary": build_summary(build),
"rows": list_unit_prices(build),
}
)
except Exception:
logger.exception("B09 일위대가 목록 실패: project_id=%s", project_id)
return JSONResponse(
status_code=500,
content={"status": "error", "message": "일위대가 목록을 못 만들었습니다."},
)
@router.get("/{project_id}/estimation/unit-prices/{code}")
async def get_unit_price_detail(project_id: UUID, code: str) -> JSONResponse:
"""일위대가 **본표** — 「무엇으로 이루어졌나」. 줄마다 원천·파고들기 표시가 붙는다."""
try:
return JSONResponse(content={"status": "success", **detail_of(cached_build(), code)})
except PriceBookError as error:
return JSONResponse(status_code=404, content={"status": "error", "message": str(error)})
except Exception:
logger.exception("B09 일위대가 본표 실패: project_id=%s, code=%s", project_id, code)
return JSONResponse(
status_code=500,
content={"status": "error", "message": "일위대가 본표를 못 만들었습니다."},
)
@router.post("/{project_id}/estimation/confirm")
async def confirm_estimation(project_id: UUID) -> JSONResponse:
"""원가계산 단계 확정 — 워크플로 stage 6(ESTIMATION)을 COMPLETE 로 전이한다."""