Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SE9MxeXTu2uaDZ55BJowAY
118 lines
5.6 KiB
Python
118 lines
5.6 KiB
Python
"""239-1 B09 총괄설계내역서 맨 위 = 설계내역서 레벨 1 · 2 집계 · 누름 = 설계내역서 그 줄 빨강.
|
|
|
|
- 맨 위 내역 줄 = 설계내역서 레벨 1 · 2 폴더 줄(관급 빼고 · 레벨 1 항목도) · 설계내역서 차례 · 레벨 그대로
|
|
- 금액(합계 · 노무 · 재료 · 경비) = 설계내역서 그 줄 금액(Calc_Bill) · 끝 「순공사비 계」 = 총괄 가. 순공사비계
|
|
- 줄 누름 = link {tab: bill, key} → Shell 이 설계내역서 그 줄에 is-pick(빨강 외곽선 · 고른 줄 상자와 따로)
|
|
- 아래 원가계산 줄 = 그대로(요율 입력 줄부터 같은 차례)
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from test_182_b09_bill_level import BOOKS, HAND, INPUTS, USER # noqa: E402
|
|
from test_184_3_b09_bill_menu import _node, _ts # noqa: E402
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from B09_Estimation.B09_Estimation_Calc_Bill import compute_estimation # noqa: E402
|
|
from B09_Estimation.B09_Estimation_Calc_Total import compute_total # noqa: E402
|
|
|
|
SRC = ["Calc_Base", "Calc_Total", "UI_Tabs", "UI_Tabs_Total"]
|
|
#: Shell 이 부르는 줄 class(목록표 → 탭 길 · 181-2) — 묶음 탭 파일에서 그 함수만
|
|
GROUPS = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Tabs_Groups.ts").read_text("utf-8")
|
|
PICK = re.search(r"export function pickClass\(.*?\n}\n", GROUPS, re.DOTALL).group(0)
|
|
PARTS = ["sum", "labor", "material", "expense"]
|
|
#: 빈 줄 없는 판(연결 없는 earth:5 · hand:c 뺌) — 총괄 순공사비계가 값을 받음
|
|
FULL_INPUTS = {**INPUTS, "bill": {"items": [i for i in INPUTS["bill"]["items"] if i["ref"]]}}
|
|
FULL_USER = {"bill": {"rows": [h for h in HAND if h["key"] != "hand:c"]}}
|
|
|
|
JS = """
|
|
const sheets = __SHEETS__;
|
|
const ctx = { sheets, payload: {}, user: { estimate: {} }, setUser: () => {} };
|
|
const spec = buildTotal(ctx);
|
|
const plain = buildTotal({ ...ctx, sheets: { total: sheets.total } });
|
|
console.log(JSON.stringify({
|
|
rows: spec.rows.map((r, i) => ({ ...r, link: spec.link(i, "name"), cls: spec.rowClass(i),
|
|
edit: ["amount", "rate", "hand"].some((k) => spec.editable(i, k)) })),
|
|
plain: plain.rows.map((r) => r.key),
|
|
pick: [pickClass([3, 3], 2), pickClass([3, 3], 3), pickClass(null, 3)],
|
|
}));
|
|
"""
|
|
|
|
|
|
def _run(inputs, user):
|
|
bill = compute_estimation(inputs, user, None, BOOKS)["bill"]
|
|
total = compute_total(bill["sums"], {})
|
|
sheets = {"bill": bill, "total": total}
|
|
out = json.loads(_node(_ts(SRC) + PICK + JS.replace("__SHEETS__", json.dumps(sheets))))
|
|
return bill, total, out
|
|
|
|
|
|
def test_맨_위_줄_레벨_1_2_폴더_차례_금액_같음():
|
|
bill, _, out = _run(INPUTS, USER)
|
|
want = [
|
|
r
|
|
for r in bill["rows"]
|
|
if r["cost"] == "direct" and (r["level"] == 1 or (r["level"] == 2 and r["kind"] != "item"))
|
|
]
|
|
top = [r for r in out["rows"] if r["key"].startswith("bill:")]
|
|
assert [r["key"] for r in top] == [f"bill:{r['key']}" for r in want]
|
|
assert {r["level"] for r in top} == {1, 2}
|
|
assert "bill:sec:G" not in {r["key"] for r in top} # 관급 = 순공사비 밖
|
|
assert "bill:haul:1" not in {r["key"] for r in top} # 레벨 2 항목(파일) 빠짐
|
|
for got, src in zip(top, want):
|
|
assert got["name"] == f"{src['no']} {src['name']}"
|
|
assert [got["amount"], got["labor"], got["material"], got["expense"]] == [
|
|
src[p] for p in PARTS
|
|
]
|
|
assert got["cls"] == ("is-title" if src["level"] == 1 else None)
|
|
assert got["edit"] is False
|
|
# 맨 위부터 내역 줄 → 순공사비 계 → 요율 입력 줄
|
|
keys = [r["key"] for r in out["rows"]]
|
|
assert keys[: len(top) + 2] == [r["key"] for r in top] + ["bill_sum", "in_price"]
|
|
|
|
|
|
def test_합_순공사비_레벨1_합_총괄_같음():
|
|
bill, total, out = _run(FULL_INPUTS, FULL_USER)
|
|
assert bill["totals"]["direct"]["blank"] == 0
|
|
rows = {r["key"]: r for r in out["rows"]}
|
|
s = rows["bill_sum"]
|
|
lv1 = [r for r in out["rows"] if r["key"].startswith("bill:") and r["level"] == 1]
|
|
for col, p in zip(["amount", "labor", "material", "expense"], PARTS):
|
|
assert s[col] == bill["totals"]["direct"][p]
|
|
assert s[col] == sum(r[col] for r in lv1)
|
|
assert s["amount"] == rows["direct"]["amount"] == total["sums"]["direct"] > 0
|
|
assert s["level"] == 1 and s["link"] is None
|
|
|
|
|
|
def test_빈_줄_있으면_계_안내():
|
|
bill, _, out = _run(INPUTS, USER)
|
|
s = next(r for r in out["rows"] if r["key"] == "bill_sum")
|
|
assert bill["totals"]["direct"]["blank"] == 2
|
|
assert s["amount"] == bill["totals"]["direct"]["sum"] and "빈 줄 2" in s["note"]
|
|
|
|
|
|
def test_누름_설계내역서_그_줄_빨강():
|
|
bill, _, out = _run(INPUTS, USER)
|
|
keys = {r["key"] for r in bill["rows"]}
|
|
for r in out["rows"]:
|
|
if r["key"].startswith("bill:"):
|
|
assert r["link"] == {"tab": "bill", "key": r["key"][5:]} and r["link"]["key"] in keys
|
|
else:
|
|
assert r["link"] is None # 원가계산 줄 = 이동 없음(그대로)
|
|
assert out["pick"] == [None, "is-pick is-pick-top is-pick-end", None]
|
|
shell = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Shell.ts").read_text("utf-8")
|
|
assert "if (bill) jumpTo(bill.rows, at);" in shell # 242-1 범위 = 그 줄 + 자손
|
|
assert "pickedClass(bill.rows[i]) ?? pickClass(jumpRange(), i)" in shell
|
|
|
|
|
|
def test_설계내역서_없으면_원가계산_줄_그대로():
|
|
_, _, out = _run(INPUTS, USER)
|
|
assert out["plain"][0] == "in_price"
|
|
assert out["plain"] == [r["key"] for r in out["rows"] if not r["key"].startswith("bill")]
|