- B07 POST /design-drawings/reset — 저장본 확정 풂(번호 · json 남김) · B07 이 올린 측점 확정 → 잠정 · 축척 · 횡단 배치 · 평면도 종류 · 표식 파일 지움 · 새 _Router_Reset.py(main.py 물림) - B08 POST /quantity/reset — quantity 설정 구획 기본 · hydro_inputs 지움 · milestone 기본(사용자 그림 붙은 거래처 경로 줄만 남김 · to = 시점) · 1본 길이 · 그림 남김 - B09 POST /estimation/reset — 사용자 값 · 단가 책 · estimation 설정 구획 지움 · 안 쓰는 서고 사본 정리 · 견적서 남김 · 답 = GET 꼴 - 공용 reset_section(구획 하나 기본으로) · 새 시험 test_298_2_page_reset Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
161 lines
7.5 KiB
Python
161 lines
7.5 KiB
Python
"""B08 좌측 패널 옵션 길(PLAN 122 · 124) — 배관 1본 길이 · 연암 / 경암 비율 · 할증 모래 · 자갈 · 시멘트.
|
||
|
||
GET /api/projects/{id}/quantity/options 지금 값
|
||
PUT /api/projects/{id}/quantity/options [저장] — 받은 칸만 정본 자리에 씀
|
||
POST /api/projects/{id}/quantity/complete [>] — 단계 5 완료 · 6 진행 중(요구 300 B1)
|
||
POST /api/projects/{id}/quantity/reset [초기화](요구 298-2 C1 · C3 · C4) — 사용자 값 ·
|
||
설정 지움 → 다음 GET = 지금 B05 · B06 저장본으로 셈한 기준 · 단계 = [저장]과 같음
|
||
|
||
[초기화] 지움 = `project_settings.json › quantity` 구획 통째(연암 · 할증 · 재료 칸 · 그 밖 → 기본)
|
||
· `hydro_inputs.json` · `milestone.json`(사용자 그림이 붙은 거래처 경로 줄만 남김 — 다음 [저장]이
|
||
그림을 안 지우게 · 그 줄 `to` = 시점) · 남김 = 1본 길이 · 조합 연결(M02 층) · 올린 그림 파일 ·
|
||
초안 넷 = 화면 몫.
|
||
|
||
1본 길이 = 프로젝트 층 M02 구조물집계표 변수 `파형강관_1본_m` 한 벌(98 `one_pipe_length` 이 읽는 자리 ·
|
||
M02 표 식 `[$파형강관_1본_m]` 도 같은 값) · 프로젝트 사본이 없으면 시스템 층 문서를 바탕으로 깔고 씀 ·
|
||
M02 [초기화]면 같이 되돌아감.
|
||
연암 % = `project_settings.json › quantity › rock_ratios_pct`(연암 · 경암 = 100 − 연암) · 기본 90 은
|
||
파일에 안 쓰고 읽을 때 채움(`soft_rock_pct`).
|
||
할증 % = `quantity › material_surcharge_pct`(모래 · 자갈 · 시멘트 · 받은 칸만 · 기본 12 · 5 · 3 은 읽을 때 채움
|
||
`panel_surcharge`) — 총괄자재수량집계표 그 세 줄이 씀.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import asyncio
|
||
from pathlib import Path
|
||
from typing import Annotated, Any, Literal
|
||
from uuid import UUID
|
||
|
||
from fastapi import APIRouter, HTTPException
|
||
from pydantic import BaseModel, Field
|
||
|
||
from B08_Quantity.B08_Quantity_Hydro_Site import SAVED_FILE as HYDRO_FILE
|
||
from B08_Quantity.B08_Quantity_Material_Calc import PANEL_KEY, panel_surcharge
|
||
from B08_Quantity.B08_Quantity_Milestone_Calc import SITE
|
||
from B08_Quantity.B08_Quantity_Router_Hydro import _project_root
|
||
from B08_Quantity.B08_Quantity_Router_Milestone import DATA_FILE as MILESTONE_FILE
|
||
from B08_Quantity.B08_Quantity_Router_Milestone import Milestone, images, read_data
|
||
from B08_Quantity.B08_Quantity_Router_Milestone import folder as milestone_folder
|
||
from B08_Quantity.B08_Quantity_Router_Summary import ONE_LENGTH_VAR, TABLE_NAME, one_pipe_length
|
||
from common_util.common_util_json import atomic_write_json
|
||
from common_util.common_util_project_settings import (
|
||
quantity_settings,
|
||
reset_section,
|
||
save_section,
|
||
soft_rock_pct,
|
||
)
|
||
from common_util.common_util_workflow_state import mark_stage
|
||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||
|
||
router = APIRouter(prefix="/api/projects", tags=["B08 Options"])
|
||
|
||
|
||
class OptionsSave(BaseModel):
|
||
"""빈칸(None) = 그 칸은 그대로."""
|
||
|
||
one_pipe_length_m: float | None = Field(default=None, gt=0, le=100)
|
||
soft_rock_pct: float | None = Field(default=None, ge=0, le=100)
|
||
material_surcharge_pct: (
|
||
dict[Literal["sand", "gravel", "cement"], Annotated[float, Field(ge=0, le=100)]] | None
|
||
) = None
|
||
|
||
|
||
def read_options(root: str | Path) -> dict[str, Any]:
|
||
settings = quantity_settings(root)
|
||
soft = soft_rock_pct(settings)
|
||
return {
|
||
"one_pipe_length_m": one_pipe_length(root),
|
||
"rock_ratios_pct": {"연암": soft, "경암": 100.0 - soft},
|
||
PANEL_KEY: panel_surcharge(settings.get(PANEL_KEY)),
|
||
}
|
||
|
||
|
||
def _write_one_pipe(root: str | Path, value: float) -> None:
|
||
"""프로젝트 층 구조물집계표 변수 한 칸 — 판이 그새 바뀌었으면 `StaleTemplate`."""
|
||
folder = layers.project_dir(root)
|
||
found = layers.read_template(folder, "table", TABLE_NAME)
|
||
version = found["판"] if found else None
|
||
found = found or layers.read_template(layers.system_dir(), "table", TABLE_NAME)
|
||
if found is None:
|
||
raise HTTPException(status_code=404, detail=f"{TABLE_NAME} 양식이 없습니다 — M02 표부터.")
|
||
document = found["문서"]
|
||
document["변수"] = {**(document.get("변수") or {}), ONE_LENGTH_VAR: value}
|
||
layers.write_template(
|
||
folder, "table", TABLE_NAME, document, version=version, check_version=True
|
||
)
|
||
|
||
|
||
def save_options(root: str | Path, body: OptionsSave) -> dict[str, Any]:
|
||
if body.one_pipe_length_m is not None:
|
||
_write_one_pipe(root, body.one_pipe_length_m)
|
||
if body.soft_rock_pct is not None:
|
||
soft = float(body.soft_rock_pct)
|
||
ratios = {
|
||
**(quantity_settings(root).get("rock_ratios_pct") or {}),
|
||
"연암": soft,
|
||
"경암": 100.0 - soft,
|
||
}
|
||
save_section(
|
||
root, "quantity", {"rock_ratios_pct": ratios}, replace_keys=("rock_ratios_pct",)
|
||
)
|
||
if body.material_surcharge_pct:
|
||
stored = quantity_settings(root).get(PANEL_KEY)
|
||
merged = {**(stored if isinstance(stored, dict) else {}), **body.material_surcharge_pct}
|
||
save_section(root, "quantity", {PANEL_KEY: merged}, replace_keys=(PANEL_KEY,))
|
||
return read_options(root)
|
||
|
||
|
||
@router.get("/{project_id}/quantity/options")
|
||
async def options_read(project_id: UUID) -> dict[str, Any]:
|
||
root = await _project_root(project_id)
|
||
return await asyncio.to_thread(read_options, root)
|
||
|
||
|
||
@router.put("/{project_id}/quantity/options")
|
||
async def options_save(project_id: UUID, body: OptionsSave) -> dict[str, Any]:
|
||
root = await _project_root(project_id)
|
||
try:
|
||
out = await asyncio.to_thread(save_options, root, body)
|
||
except layers.StaleTemplate as error:
|
||
raise HTTPException(
|
||
status_code=409, detail="구조물집계표가 그새 바뀌었습니다 — 다시 열고 저장하세요."
|
||
) from error
|
||
await mark_stage(str(project_id), 5, "save") # [저장](B1)
|
||
return out
|
||
|
||
|
||
@router.post("/{project_id}/quantity/complete")
|
||
async def quantity_complete(project_id: UUID) -> dict[str, Any]:
|
||
"""B08 [>] — 단계 5 완료 · 6 진행 중(요구 300 B1 · 본문 없음)."""
|
||
await _project_root(project_id) # 없는 프로젝트는 404
|
||
await mark_stage(str(project_id), 5, "next")
|
||
return {"status": "success", "project_id": str(project_id)}
|
||
|
||
|
||
def reset_quantity(root: str | Path) -> dict[str, Any]:
|
||
"""[초기화] 파일 몫 — 지운 것 · 남긴 거래처 경로 줄 수를 옵션 꼴에 얹어 돌려줌."""
|
||
root = Path(root)
|
||
reset_section(root, "quantity")
|
||
hydro = root / HYDRO_FILE
|
||
cleared_hydro = hydro.is_file()
|
||
hydro.unlink(missing_ok=True)
|
||
kept_routes = 0
|
||
if (milestone_folder(root) / MILESTONE_FILE).is_file():
|
||
pictured = set(images(root)) # 사용자 그림 = 하위 폴더 밖 `<경로 id>.<확장자>`
|
||
routes = [
|
||
{**route, "to": SITE} for route in read_data(root)["routes"] if route["id"] in pictured
|
||
]
|
||
kept_routes = len(routes)
|
||
data = Milestone.model_validate({"routes": routes}).model_dump()
|
||
atomic_write_json(milestone_folder(root) / MILESTONE_FILE, data)
|
||
return {**read_options(root), "cleared_hydro": cleared_hydro, "kept_routes": kept_routes}
|
||
|
||
|
||
@router.post("/{project_id}/quantity/reset")
|
||
async def quantity_reset(project_id: UUID) -> dict[str, Any]:
|
||
root = await _project_root(project_id)
|
||
out = await asyncio.to_thread(reset_quantity, root)
|
||
await mark_stage(str(project_id), 5, "save") # [초기화] = 작업본 바꿈 = [저장]과 같음
|
||
return out
|