"""704-2 딸림 — 기준 목록 · 계산서 끝수 = B09 산근 규칙(줄마다 ROUNDDOWN 1 · 비목마다 ROUNDDOWN 0). 거울: 엔진 줄(수량 · 단가)에 B09 `_money` · `block_totals` 를 그대로 씌운 값 = `settle` 값. """ import re import sys from decimal import Decimal 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_Sangeun as sg # noqa: E402 from M01_MasterData import M01_MasterData_Store as m01 # noqa: E402 from M02_MasterTemplete import M02_Logic_Settle as st # noqa: E402 from M02_MasterTemplete import M02_Logic_Standard as ls # noqa: E402 mf = m01.cm.mf PART_OF = {"노무비": "labor", "재료비": "material", "경비": "expense"} def _plain(row: dict) -> bool: """로직 줄 · 덧줄 · 작업량 Q 가 없는 돈 로직 — 엔진 줄만으로 B09 값을 낼 수 있음.""" return ( str(row.get("결과단위") or "").startswith("원") and not row.get("덧줄") and not row.get("끝수") and not any(i.get("종류") == "로직" for i in row.get("호표") or []) and not any(re.fullmatch(r"Q\d*", s["이름"]) for s in row.get("중간") or []) ) def test_줄마다_버림_비목마다_버림_B09와_같음(): files, whole = m01.loaded() checked = 0 for _, data in m01._logic_files(files): for row in data.get("줄") or []: if not _plain(row) or checked >= 60: continue base, _ = ls.axes(whole, row) given = mf.fill_empty(row, base) got = mf.run(whole, row["키"], given) lines = [] for line in got["줄"]: one = dict.fromkeys(PART_OF.values(), 0.0) for part, money in line["비목"].items(): one[PART_OF[part]] = sg._money(1.0, float(money)) if money is not None else None lines.append(one) if any(v is None for one in lines for v in one.values()): continue want = sg.block_totals(lines) mine = st.settle(whole, row["키"], given) for name, part in PART_OF.items(): assert float(mine[name]) == want[part], (row["키"], name) assert float(mine["계"]) == want["sum"], row["키"] checked += 1 assert checked >= 30 def test_계산서_줄_소계_계가_맞물림(): files, whole = m01.loaded() key = "GF000089" got = ls.sheet(key, {}) assert got["묶음"], key for group in got["묶음"]: lines = [Decimal(str(x["금액"] or 0)) for x in group["줄"]] assert all(v == v.quantize(Decimal("0.1")) for v in lines), group["비목"] assert group["소계"] == int(group["소계"]) assert group["소계"] <= sum(lines) < group["소계"] + 1 + Decimal("0.05"), group["비목"] assert got["계"] == sum(g["소계"] for g in got["묶음"]) assert got["계"] == ls._money( whole, whole.logic(key), ls._given(whole, whole.logic(key), {})[0] )