feat(b08): 거리이정표 읽기 = 운반 행마다 상자 자동 · 모델 haul · ref (251-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
This commit is contained in:
@@ -101,3 +101,29 @@ def haul_groups(materials: dict[str, Any] | None) -> list[dict[str, Any]]:
|
||||
name, unit = NAMES[gkey]
|
||||
out[gkey] = {"key": gkey, "name": name, "unit": unit, "qty": None}
|
||||
return list(out.values())
|
||||
|
||||
|
||||
def with_haul_routes(data: dict[str, Any], groups: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""거리이정표 자료에 운반 행 상자 자동(251-2) — 묶음마다 거래처 상자가 없으면 넣음.
|
||||
|
||||
거래처 · 주소 빈칸 · 선 없음(linked false) · 항목 = 자재 이름 ·
|
||||
저장은 [저장] 때(읽기만 합침)."""
|
||||
have = {r.get("haul") for r in data.get("routes") or []}
|
||||
routes = list(data.get("routes") or [])
|
||||
for g in groups:
|
||||
if g["key"] in have:
|
||||
continue
|
||||
routes.append(
|
||||
{
|
||||
"id": f"h-{g['key']}"[:40],
|
||||
"name": "",
|
||||
"address": "",
|
||||
"via_base": True,
|
||||
"linked": False,
|
||||
"dir": None,
|
||||
"haul": g["key"],
|
||||
"legs": [{"road": None, "km": None}],
|
||||
"rows": [{"item": g["name"], "dest": "start", "note": ""}],
|
||||
}
|
||||
)
|
||||
return {**data, "routes": routes}
|
||||
|
||||
@@ -87,6 +87,10 @@ export interface MilestoneRoute {
|
||||
linked?: boolean;
|
||||
/** 수기 입력(155) — 카카오 길찾기 안 함 · km 손 입력만 */
|
||||
manual?: boolean;
|
||||
/** 운반 묶음 키(251 · 총괄자재 · B09 운반 줄과 이음) — 없음 = 사용자 [요소 추가] 상자 */
|
||||
haul?: string | null;
|
||||
/** 단가연결(251 · dg · sg · logic 꼴 · 정본) */
|
||||
ref?: Record<string, unknown> | null;
|
||||
/** 업체 → 기준점(거침) · 시점 구간 */
|
||||
legs: Leg[];
|
||||
rows: MilestoneRow[];
|
||||
|
||||
@@ -49,9 +49,11 @@ from pydantic import BaseModel, Field, ValidationError, model_validator
|
||||
from B01_Dashboard.B01_Dashboard_Map import reverse_geocode
|
||||
from B02_ProjRegister.B02_ProjRegister_Repository import get_project_by_id
|
||||
from B03_FileInput.B03_FileInput_Engine import save_upload_stream
|
||||
from B08_Quantity.B08_Quantity_Haul import haul_groups, with_haul_routes
|
||||
from B08_Quantity.B08_Quantity_Milestone_Calc import compute_milestone, r3
|
||||
from B08_Quantity.B08_Quantity_Milestone_Capture import make_route_image
|
||||
from B08_Quantity.B08_Quantity_Router_Hydro import _project_root
|
||||
from B08_Quantity.B08_Quantity_Router_Material import material_sheets
|
||||
from common_util.common_util_initial_snapshot import design_route_csv_path
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_route_geometry import (
|
||||
@@ -101,6 +103,9 @@ class Place(BaseModel):
|
||||
class Route(Place):
|
||||
id: str = Field(pattern=ROUTE_ID)
|
||||
via_base: bool = True
|
||||
# 251 운반 묶음 키(총괄자재 · B09 운반 줄과 이음) · 단가연결(dg · sg · logic 꼴 · 정본)
|
||||
haul: str | None = Field(default=None, max_length=60, pattern=r"^[A-Za-z0-9_-]+$")
|
||||
ref: dict[str, Any] | None = None
|
||||
linked: bool = True # 155 False = 아직 선 안 이음(요소 추가 뒤)
|
||||
rows: list[Row] = Field(default_factory=list, max_length=50)
|
||||
|
||||
@@ -239,10 +244,19 @@ def _route_id(route: str) -> str:
|
||||
raise HTTPException(status_code=400, detail="경로 id 가 바르지 않습니다.") from error
|
||||
|
||||
|
||||
async def _haul(project_id: UUID) -> list[dict[str, Any]]:
|
||||
"""운반 묶음(251) — 자재 집계 못 읽으면 빈 목록(거리이정표는 그대로)."""
|
||||
try:
|
||||
return haul_groups(await material_sheets(project_id))
|
||||
except Exception: # noqa: BLE001 — 자재 집계 없음 · 실패 = 자동 상자 없음
|
||||
return []
|
||||
|
||||
|
||||
@router.get(PATH)
|
||||
async def milestone_read(project_id: UUID) -> dict[str, Any]:
|
||||
root = await _project_root(project_id)
|
||||
return await asyncio.to_thread(lambda: payload(root, read_data(root)))
|
||||
groups = await _haul(project_id)
|
||||
return await asyncio.to_thread(lambda: payload(root, with_haul_routes(read_data(root), groups)))
|
||||
|
||||
|
||||
@router.put(PATH)
|
||||
|
||||
@@ -48,3 +48,31 @@ def test_B09_운반_줄_옛_키_그대로_새_묶음_키():
|
||||
assert items["haul:remicon"]["name"] == "레미콘운반" and items["haul:remicon"]["unit"] == "㎥"
|
||||
assert all(i["section"] == "3" for i in items.values())
|
||||
assert {"haul:1", "haul:2", "haul:3", "haul:4", "haul:5", "haul:6"} <= set(items)
|
||||
|
||||
|
||||
def test_251_2_거리이정표_운반_상자_자동():
|
||||
from B08_Quantity.B08_Quantity_Haul import with_haul_routes
|
||||
|
||||
groups = haul_groups(_mats(ROWS))
|
||||
data = {"site": {}, "base": {"enabled": False}, "routes": [
|
||||
{"id": "h-sand", "name": "골재상", "haul": "sand", "rows": [{"item": "모래"}]},
|
||||
{"id": "u1", "name": "내 상자", "rows": []},
|
||||
]} # fmt: skip
|
||||
out = with_haul_routes(data, groups)
|
||||
by = {r["id"]: r for r in out["routes"]}
|
||||
assert by["h-sand"]["name"] == "골재상" # 있는 상자 그대로
|
||||
assert "u1" in by # 사용자 상자 그대로
|
||||
new = by["h-remicon"]
|
||||
assert (new["name"], new["address"], new["linked"], new["haul"]) == ("", "", False, "remicon")
|
||||
assert new["rows"] == [{"item": "레미콘", "dest": "start", "note": ""}]
|
||||
assert len([r for r in out["routes"] if r.get("haul")]) == len(groups)
|
||||
assert data["routes"][0] is out["routes"][0] and len(data["routes"]) == 2 # 원본 안 바뀜
|
||||
|
||||
|
||||
def test_251_2_서버_모델_읽기_길():
|
||||
from B08_Quantity.B08_Quantity_Router_Milestone import Route
|
||||
|
||||
r = Route(id="h-sand", haul="sand", ref={"tab": "logic", "code": "GF000284"})
|
||||
assert r.haul == "sand" and r.ref == {"tab": "logic", "code": "GF000284"}
|
||||
src = (ROOT / "B08_Quantity" / "B08_Quantity_Router_Milestone.py").read_text(encoding="utf-8")
|
||||
assert "with_haul_routes(read_data(root), groups)" in src
|
||||
|
||||
Reference in New Issue
Block a user