diff --git a/B08_Quantity/B08_Quantity_Haul.py b/B08_Quantity/B08_Quantity_Haul.py index b8ccc551f..93a01af50 100644 --- a/B08_Quantity/B08_Quantity_Haul.py +++ b/B08_Quantity/B08_Quantity_Haul.py @@ -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} diff --git a/B08_Quantity/B08_Quantity_Milestone_Calc.ts b/B08_Quantity/B08_Quantity_Milestone_Calc.ts index 8637bc66e..5c1bac853 100644 --- a/B08_Quantity/B08_Quantity_Milestone_Calc.ts +++ b/B08_Quantity/B08_Quantity_Milestone_Calc.ts @@ -87,6 +87,10 @@ export interface MilestoneRoute { linked?: boolean; /** 수기 입력(155) — 카카오 길찾기 안 함 · km 손 입력만 */ manual?: boolean; + /** 운반 묶음 키(251 · 총괄자재 · B09 운반 줄과 이음) — 없음 = 사용자 [요소 추가] 상자 */ + haul?: string | null; + /** 단가연결(251 · dg · sg · logic 꼴 · 정본) */ + ref?: Record | null; /** 업체 → 기준점(거침) · 시점 구간 */ legs: Leg[]; rows: MilestoneRow[]; diff --git a/B08_Quantity/B08_Quantity_Router_Milestone.py b/B08_Quantity/B08_Quantity_Router_Milestone.py index 8e20c819c..e68cccdbc 100644 --- a/B08_Quantity/B08_Quantity_Router_Milestone.py +++ b/B08_Quantity/B08_Quantity_Router_Milestone.py @@ -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) diff --git a/resources/tester/test_251_1_haul_groups.py b/resources/tester/test_251_1_haul_groups.py index 127f09d49..76d258c26 100644 --- a/resources/tester/test_251_1_haul_groups.py +++ b/resources/tester/test_251_1_haul_groups.py @@ -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