diff --git a/M01_MasterData/SheetLogic/M01_SL_Router.py b/M01_MasterData/SheetLogic/M01_SL_Router.py index 4719049ed..f9578f586 100644 --- a/M01_MasterData/SheetLogic/M01_SL_Router.py +++ b/M01_MasterData/SheetLogic/M01_SL_Router.py @@ -161,7 +161,10 @@ def _logic(key: str, inputs: dict, field: str | None) -> dict: def _table(key: str) -> dict: - """품셈 표 통째 → 머리 줄 + 줄(범위 · 범위 값은 「이름·아래」 「이름·위」 두 칸).""" + """품셈 표 통째 → 머리 줄 + 줄 — `M01_SL_Table_Build.layoutOf · blockCells` 와 같은 열 차례. + + 조건 칸(범위 조건은 「이름·아래」 「이름·위」 두 칸) → 값 칸(늘 한 칸 · 범위 값은 「아래,위」). + """ files = store.loaded()[0] table = next( ( @@ -175,24 +178,25 @@ def _table(key: str) -> dict: ) if table is None: return {"ok": False, "reason": f"없는 표 「{key}」"} - names = [*(table.get("조건") or {}), *(table.get("값칸") or {})] - split = { - n - for n in names - if (table.get("조건") or {}).get(n) == "범위" - or any(isinstance(r.get(n), list) for r in table.get("줄") or []) - } + conds = table.get("조건") or {} + names = [*conds, *(table.get("값칸") or {})] + split = {n for n in conds if conds[n] == "범위"} head = [h for n in names for h in ((n + RANGE_LOW, n + RANGE_HIGH) if n in split else (n,))] + + def plain(v: Any) -> Any: # JS String(배열) 과 같게 — 「80,100」 · 빈 끝은 빈 글 + if isinstance(v, list): + return ",".join("" if x is None else str(x) for x in v) + return None if v == "" else v + rows = [] for r in table.get("줄") or []: line = [] for n in names: v = r.get(n) - if n in split: - low, high = v if isinstance(v, list) else (v, v) - line += [low, high] + if n in split: # 범위 칸 — 목록이 아니면 둘 다 빈 칸 + line += list(v) if isinstance(v, list) else [None, None] else: - line.append(v if not isinstance(v, (list, dict)) else str(v)) + line.append(plain(v)) rows.append(line) return {"ok": True, "rows": [head, *rows], "name": table.get("이름"), "unit": table.get("기준")} diff --git a/resources/tester/test_m01_sl_router.py b/resources/tester/test_m01_sl_router.py index b8d0a7265..6b1819830 100644 --- a/resources/tester/test_m01_sl_router.py +++ b/resources/tester/test_m01_sl_router.py @@ -36,10 +36,10 @@ TABLE = { "출처": "자체", "조건": {"돌": "고르기", "높이": "범위"}, "범위규칙": "초과·이하", - "값칸": {"석공": "인"}, + "값칸": {"석공": "인", "증가율": "%"}, "줄": [ - {"돌": "깬돌", "높이": [None, 3], "석공": 0.5}, - {"돌": "깬돌", "높이": [3, None], "석공": 0.7}, + {"돌": "깬돌", "높이": [None, 3], "석공": 0.5, "증가율": [80, 100]}, + {"돌": "견치돌", "높이": 4, "석공": 0.7}, ], "주": [], } @@ -158,15 +158,10 @@ def test_저장_열기_목록_지우기_서버가_다시_셈(client: TestClient, assert Decimal(book["계산값"]["s1"]["B1"]["수"]) == 2 * price # 서버 다시 셈(999 는 버림) assert "C1" not in cells # 출처 없는 바깥 알약은 안 넣음 table = book["시트"][1]["칸"] - assert [table[c]["값"] for c in ("A1", "B1", "C1", "D1")] == [ - "돌", - "높이·아래", - "높이·위", - "석공", - ] - assert ( - "B2" not in table and table["C2"]["값"] == 3 and table["D3"]["값"] == 0.7 - ) # 빈 끝은 빈 칸 + head = [table[c]["값"] for c in ("A1", "B1", "C1", "D1", "E1")] + assert head == ["돌", "높이·아래", "높이·위", "석공", "증가율"] # 머리 줄 · A1 부터 + assert "B2" not in table and table["C2"]["값"] == 3 and table["E2"]["값"] == "80,100" + assert "B3" not in table and "C3" not in table and table["D3"]["값"] == 0.7 # 범위 아님 → 빈 칸 assert saved["logic"]["edges"] == _logic(labor)["edges"] answers = {v["id"]: v for v in saved["values"]} assert set(answers) == {"n1!A1", "n2!A1", "n1:노무비"} and all( @@ -221,8 +216,17 @@ def test_바깥_값_표_노임_로직_틀린_종류(client: TestClient) -> None: {"id": "z", "kind": "모름"}, ] got = {v["id"]: v for v in _post(client, "/api/m01/sl/outside", {"items": items})["values"]} - assert got["t"]["rows"][0] == ["돌", "높이·아래", "높이·위", "석공"] - assert got["t"]["rows"][1:] == [["깬돌", None, 3, 0.5], ["깬돌", 3, None, 0.7]] + assert got["t"]["rows"][0] == [ + "돌", + "높이·아래", + "높이·위", + "석공", + "증가율", + ] # sub3 layoutOf 차례 + assert got["t"]["rows"][1:] == [ + ["깬돌", None, 3, 0.5, "80,100"], + ["견치돌", None, None, 0.7, None], + ] assert Decimal(str(got["l"]["value"])) == price and got["l"]["unit"] assert Decimal(str(got["g"]["value"])) == Decimal("0.5") * price assert got["g2"]["value"] == 0