"""145-5 B09 집계표 넷 — 역전개(겹 호표) · 엑셀 끝수 · 검산 대조(서버 단독 · 거울 없음). 견본 = 기번3 공통 엑셀 집계표: 중기시간금액집계표 4줄 불도우저19ton 42.95 시간 → 2,392,315 · 1,584,340 · 1,435,045 = 5,411,700 노무비수량금액집계표 4줄 건축목공 8.29 인 × 268,058 = 2,222,201 재료비수량금액집계표 각재 0.56 × 500,661 = 280,370 · 못 3.54 × 1,503 = 5,321 경비수량금액집계표 구역화물자동차 11.03 회 × 61,500 = 678,345 """ import asyncio import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from B09_Estimation import B09_Estimation_Router as router_mod # noqa: E402 from B09_Estimation.B09_Estimation_Summary import compute_summary # noqa: E402 SHEETS = { "labor": [{"key": "L1", "name": "건축목공", "spec": "", "unit": "인", "price": 268058}], "material": [ {"key": "M3", "name": "각재", "spec": "외송", "unit": "M3", "price": 500661}, {"key": "M4", "name": "못", "spec": "N 50", "unit": "KG", "price": 1503}, ], "expense": [ {"key": "E16", "name": "구역화물자동차", "spec": "5톤", "unit": "회", "price": 61500} ], "machine": [ { "key": "X1", "name": "불도우저19ton", "spec": "(토사)", "unit": "시간", "sum": 126000, "labor": 55700, "material": 36888, "expense": 33412, }, ], # fmt: skip "bill": { "rows": [ {"kind": "title", "key": "sec:1", "qty": None}, {"kind": "item", "key": "a", "qty": 10, "ref": {"tab": "sg_list", "key": "A"}}, {"kind": "item", "key": "m", "qty": 0.56, "ref": {"tab": "material", "key": "M3"}}, {"kind": "item", "key": "none", "qty": None, "ref": {"tab": "sg_list", "key": "A"}}, {"kind": "item", "key": "loop", "qty": 1, "ref": {"tab": "dg_list", "key": "C"}}, ] }, } LINES = { "A": {"lines": [ {"kind": "machine", "key": "X1", "qty": 4.295}, {"kind": "hopyo", "key": "B", "qty": 0.5}, {"kind": "material", "key": "M4", "qty": 0.354}, {"kind": "expense", "key": "E16", "qty": 1.103}, ]}, "B": {"lines": [{"kind": "labor", "key": "L1", "qty": 1.658}, {"kind": "labor", "key": "L9", "qty": 1}]}, "C": {"lines": [{"kind": "hopyo", "key": "C", "qty": 1}]}, # 스스로 부름 — 깊이 한도에서 멈춤 } # fmt: skip def test_역전개_엑셀_금액(): s = compute_summary(SHEETS, LINES) machine = s["sum_machine"][0] assert (machine["qty"], machine["labor"], machine["material"], machine["expense"]) == ( 42.95, 2392315, 1584340, 1435045, ) # fmt: skip assert machine["sum"] == 5411700 labor = {r["key"]: r for r in s["sum_labor"]} assert (labor["L1"]["qty"], labor["L1"]["amount"]) == ( 8.29, 2222201, ) # 겹 호표 10 × 0.5 × 1.658 assert labor["L9"]["amount"] is None and labor["L9"]["note"] == "목록표에 없음" material = {r["key"]: (r["qty"], r["amount"]) for r in s["sum_material"]} assert material == {"M4": (3.54, 5321), "M3": (0.56, 280370)} # 내역 자재 줄 그대로 assert [(r["qty"], r["amount"]) for r in s["sum_expense"]] == [(11.03, 678345)] def test_호표_구성_없으면_자재_줄만(): s = compute_summary(SHEETS, {}) assert s["sum_labor"] == [] and s["sum_machine"] == [] and s["sum_expense"] == [] assert [r["key"] for r in s["sum_material"]] == ["M3"] def test_GET_응답에_집계표(tmp_path, monkeypatch): async def fake_materials(_project_id): return {"material": {"rows": [{"key": "sand", "group": "골재", "spec": "모래", "unit": "㎥", "amount": 16}]}} # fmt: skip monkeypatch.setattr(router_mod, "material_sheets", fake_materials) body = asyncio.run(router_mod.payload("p", tmp_path)) assert set(body["summary"]) == { "sum_material", "sum_labor", "sum_expense", "sum_machine", "unlinked", } # fmt: skip assert body["summary"]["sum_material"] == [] # 「별도」 · 「관급」 표시 전엔 내역 자재 줄 없음 # ── 산근 블록(145-1 `sheets['sg']` 꼴)으로 역전개 — 산근 → 하위 산근 → 중기 · 엑셀 반올림 줄 ── from B09_Estimation.B09_Estimation_Summary import hopyo_lines # noqa: E402 def _ln(kind, tab, key, qty): return {"kind": kind, "ref": {"tab": tab, "key": key}, "qty": qty, "name": key} SG = [ {"key": "S1", "lines": [ _ln("labor", "labor", "L3", 1.3701), _ln("machine", "machine", "X7", 14.3614), _ln("sangeun", "sg", "S2", 0.5), {"kind": "extra", "ref": None, "qty": 1.0, "name": "잡재료 2%"}, # % 줄 = 자원 아님 {"kind": "blank", "ref": None, "qty": None, "name": "빈칸"}, ]}, {"key": "S2", "lines": [ _ln("labor", "labor", "L6", 4.4008), _ln("material", "compare", "M7", 80.5194), ]}, ] # fmt: skip SG_SHEETS = { "labor": [ {"key": "L3", "name": "석공", "price": 258935}, {"key": "L6", "name": "보통인부", "price": 165545}, ], "material": [{"key": "M7", "name": "폭약", "spec": "28m/m", "unit": "KG", "price": 12712}], "expense": [], "machine": [{"key": "X7", "name": "굴삭기+브레카(0.7m3)", "labor": 55700, "material": None, "expense": None}], "sg": SG, "bill": {"rows": [ {"kind": "item", "key": "rock", "qty": 100, "ref": {"tab": "sg_list", "key": "S1"}}, {"kind": "item", "key": "earth", "qty": 5, "ref": None}, # 조합 밖 토공 {"kind": "item", "key": "st", "qty": 2, "ref": {"tab": "dg_list", "key": "st:x"}}, {"kind": "item", "key": "none", "qty": None, "ref": {"tab": "sg_list", "key": "S1"}}, ]}, } # fmt: skip def test_산근_블록_역전개_엑셀_반올림(): lines = hopyo_lines(SG_SHEETS) assert lines["S1"]["lines"] == [ {"kind": "labor", "key": "L3", "qty": 1.3701}, {"kind": "machine", "key": "X7", "qty": 14.3614}, {"kind": "hopyo", "key": "S2", "qty": 0.5}, ] s = compute_summary(SG_SHEETS, lines) labor = {r["name"]: (r["qty"], r["amount"]) for r in s["sum_labor"]} # 엑셀 노무비수량금액집계표 6 · 9 줄(ROUND — 220.04 × 165,545 = 36,426,521.8 → …522) assert labor == {"석공": (137.01, 35476684), "보통인부": (220.04, 36426522)} # 재료비수량금액집계표 8 줄 폭약 4,025.97 × 12,712 = 51,178,130.6 → 51,178,131 assert [(r["qty"], r["amount"]) for r in s["sum_material"]] == [(4025.97, 51178131)] machine = s["sum_machine"][0] # 중기시간금액집계표 10 줄 노무비 assert (machine["qty"], machine["labor"], machine["sum"]) == (1436.14, 79992998, None) assert s["unlinked"] == 3 # 토공 · 대가 전 구조물 · 수량 빈칸 = 전개 안 함