refactor(B08,B09): 폴더째 old_code 로 옮기고 빈 화면 둘만 남김 (PLAN 7-3)

B08_Quantity 86 · B09_Estimation 111 파일을 old_code/ 로 옮김(지우지 않음).
화면은 메뉴·주소·단계 막대만 남은 빈 틀 둘 — main.py 라우터 13 개는 끊음.
B07 이 빌려 쓰던 비탈 길이·면적은 필요한 함수만 B07_DesignDetail_Engine_SlopeGeometry
로 옮겨 적음(면적 적분·노면 면적·측점 묶음은 안 옮김) · 시험 하나를 새로 둠.
B07 구조물도 조립(Cad_StandardSheet)은 2026-09-13 에 이미 도면 목록에서 빠져
부르는 곳이 없어 old_code 로 같이 보냄 — 구조물 그림은 되살리지 않음.
B06 구조물 몫 조회는 빈 값으로 두어 화면이 그대로 서게 함.
B08·B09 를 부르던 시험 25 개도 old_code/resources/tester 로 옮김.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
2026-09-22 12:27:45 +09:00
co-authored by Claude Opus 5
parent 185f800d23
commit 8472fc9f40
231 changed files with 1617 additions and 1216 deletions
@@ -0,0 +1,111 @@
"""B09 원가계산 라우터 — **자재 수동 단가** 읽기·저장 (PLAN 1장 Ⓐ · `…_MaterialPrices`).
⚠ 저장은 단가·출처만 — 금액은 다음 조회 때 서버가 그 값으로 **다시 조립**함
(브라우저 값을 받아 적지 않음).
"""
from __future__ import annotations
import logging
from datetime import date
from typing import Any
from uuid import UUID
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from B09_Estimation.B09_Estimation_MaterialPrices import (
MATERIAL_PRICES_KEY,
MaterialPriceError,
listing,
merge,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/projects", tags=["B09 Estimation Material Prices"])
class MaterialPriceChange(BaseModel):
"""단가 칸 하나 — `price_krw` 가 비면 그 줄을 지움(단가 없음으로 돌아감)."""
key: str
price_krw: Any = None
source: str = ""
name: str = ""
spec: str = ""
unit: str = ""
class MaterialPriceRequest(BaseModel):
changes: list[MaterialPriceChange] = Field(default_factory=list)
async def _rows(project_id: UUID) -> dict[str, Any]:
import json
from B08_Quantity.B08_Quantity_Router_Material import get_handoff
from B09_Estimation.B09_Estimation_Router import _build_for, _project_root_of
from common_util.common_util_project_settings import estimation_settings
root = await _project_root_of(project_id)
settings = estimation_settings(root) if root else {}
build = await _build_for(project_id)
# 자재총괄 줄(「이름 규격」 키) — 인계를 못 받으면 코드 줄만 서고 그 사실을 `handoff_note` 로.
handoff = json.loads(bytes((await get_handoff(project_id)).body).decode("utf-8"))
materials = handoff.get("materials")
rows = listing(build, settings.get(MATERIAL_PRICES_KEY), materials)
return {
"status": "success",
"rows": rows,
"handoff_note": ""
if materials is not None
else f"B08 인계를 못 받아 자재총괄 줄이 빠짐 — {handoff.get('message') or ''}",
"unconfirmed_count": sum(1 for row in rows if row.get("price_krw")),
}
@router.get("/{project_id}/estimation/material-prices")
async def get_material_prices(project_id: UUID) -> JSONResponse:
"""단가 칸을 낼 자재 줄 — 자원 축 자재(코드) + 자원 축에서 사라진 저장 줄."""
try:
return JSONResponse(content=await _rows(project_id))
except Exception:
logger.exception("B09 자재 수동 단가 조회 실패: project_id=%s", project_id)
return JSONResponse(
status_code=500,
content={"status": "error", "message": "자재 단가 목록을 못 만들었습니다."},
)
@router.put("/{project_id}/estimation/material-prices")
async def put_material_prices(project_id: UUID, payload: MaterialPriceRequest) -> JSONResponse:
"""단가 저장 — 0 이하·수가 아닌 값은 받지 않음 · 값·출처가 같으면 넣은 날 그대로."""
from B09_Estimation.B09_Estimation_Router import _project_root_of
from common_util.common_util_project_settings import estimation_settings, save_section
root = await _project_root_of(project_id)
if root is None:
return JSONResponse(
status_code=404,
content={"status": "error", "message": "프로젝트 저장 폴더를 찾지 못했습니다."},
)
try:
prices = merge(
estimation_settings(root).get(MATERIAL_PRICES_KEY),
[change.model_dump() for change in payload.changes],
date.today().isoformat(),
)
except MaterialPriceError as error:
return JSONResponse(status_code=422, content={"status": "error", "message": str(error)})
try:
save_section(
root, "estimation", {MATERIAL_PRICES_KEY: prices}, replace_keys=(MATERIAL_PRICES_KEY,)
)
return JSONResponse(content=await _rows(project_id))
except Exception:
logger.exception("B09 자재 수동 단가 저장 실패: project_id=%s", project_id)
return JSONResponse(
status_code=500,
content={"status": "error", "message": "자재 단가를 저장하지 못했습니다."},
)