feat(b09): 산근 탭 보기 둘(표 · 호표별 카드) · 규격 칸 기계 · 직종 「이름 규격」(157-2 · 157-3)
- 157-2 탭 머리 단추 = 「표로 보기」 ↔ 「호표별 보기」 · 고른 보기 localStorage 기억(못 읽으면 표) - 호표별 카드 = 표 줄 그대로 호표마다 묶음 · 머리 산근 n호표 · 공종 · 단위 · 합계 · 가름줄 없이 간격 · 긴 글 줄바꿈 · 줄 누름 이동 같음 - 157-3 산근 규격 글 = 기계 · 직종 원문번호 → 「이름 규격」(M02 85-1 도우미) · 원문번호는 풍선(두 보기 · 목록표) - 시험 test_157_2_b09_sangeun_views — 두 보기 같은 값 · 단추 바꿈 · 기억 실패 때 표 · 이름 규격 · 풍선 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
"""157-2 B09 단가산출근거 탭 보기 둘 · 157-3 산근 규격 칸 「이름 규격」.
|
||||
|
||||
- 157-2 보기 B(호표별 카드) = 보기 A(표) 줄 그대로 호표마다 묶음 — 같은 줄 객체 · 같은 차례
|
||||
- 단추 = 보기 바꿈(글 「호표별 보기」 ↔ 「표로 보기」) · 기억 못 읽음 · 모르는 값 = 표(A)
|
||||
- 157-3 규격 = 기계 · 직종 원문번호 → 「이름 규격」(M02 85-1 도우미) · 번호는 풍선(tip)
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
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.B09_Estimation_Sangeun import spec_text # noqa: E402
|
||||
|
||||
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
|
||||
B09 = ROOT / "B09_Estimation"
|
||||
|
||||
|
||||
def _node(js_body):
|
||||
src = "".join(
|
||||
IMPORTS.sub("", (B09 / f"B09_Estimation_{n}.ts").read_text("utf-8"))
|
||||
for n in ("UI_Tabs", "UI_Tabs_Sangeun", "UI_Tabs_Sangeun_Cards")
|
||||
)
|
||||
result = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings",
|
||||
"--input-type=module-typescript", "-e", src + js_body],
|
||||
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
||||
) # fmt: skip
|
||||
assert result.returncode == 0, result.stderr
|
||||
return json.loads(result.stdout)
|
||||
|
||||
|
||||
def _block(key, no, spec="", spec_raw=""):
|
||||
return {
|
||||
"key": key, "no": no, "name": f"산근{no}", "unit": "m3", "spec": spec, "spec_raw": spec_raw,
|
||||
"head": [f"산근{no} / m3", "산림품셈 적용"],
|
||||
"lines": [
|
||||
{"kind": "labor", "name": "보통인부", "ref": {"tab": "labor", "key": "LB2"},
|
||||
"why": None, "labor": 2979.8, "material": 0.0, "expense": 0.0,
|
||||
"text": ["보통인부 : 165,545 * 0.018 = 2,979.8 (긴 함수 " +"가" * 200 + ")"]},
|
||||
{"kind": "machine", "name": "굴착기", "ref": {"tab": "machine", "key": "EQ1"},
|
||||
"why": None, "labor": 3899.0, "material": 538.6, "expense": 912.8,
|
||||
"text": ["굴착기 노무비", "굴착기 재료비", "굴착기 경비"]},
|
||||
],
|
||||
"labor": 6878, "material": 538, "expense": 912, "sum": 8328, "why": None,
|
||||
"foot": "계 : 8,328",
|
||||
} # fmt: skip
|
||||
|
||||
|
||||
SHEETS = {"sg": [_block("SG:A", 1), _block("SG:B", 2)]}
|
||||
|
||||
|
||||
def test_157_2_두_보기_같은_값():
|
||||
out = _node(
|
||||
f"\nconst t = buildSg({{ sheets: {json.dumps(SHEETS, ensure_ascii=False)} }});"
|
||||
"\nconst cards = sgCards(t.rows);"
|
||||
"\nconst flat = cards.flatMap((c) => [c.head, ...c.lines.map((l) => l.row)]);"
|
||||
"\nconst ats = cards.flatMap((c) => [c.at, ...c.lines.map((l) => l.at)]);"
|
||||
"\nconsole.log(JSON.stringify({ n: t.rows.length, keys: cards.map((c) => c.key),"
|
||||
" same: flat.length === t.rows.length && flat.every((r, i) => r === t.rows[i]),"
|
||||
" ats, heads: cards.map((c) => [c.head.kind, c.head.name, c.head.unit]),"
|
||||
" links: cards.map((c) => c.lines.map((l) => t.link(l.at, 'kind')?.key ?? null)) }));"
|
||||
)
|
||||
assert out["keys"] == ["SG:A", "SG:B"]
|
||||
assert out["same"] is True # 카드 줄 = 표 줄 객체 그대로 · 같은 차례
|
||||
assert out["ats"] == list(range(out["n"]))
|
||||
assert out["heads"] == [["제 1 호표", "산근1", "m3"], ["제 2 호표", "산근2", "m3"]]
|
||||
# 머리 1 · 노무 1 · 중기 3 · 총계 1 — 줄 누름 이동은 표와 같은 link
|
||||
assert out["links"][0] == [None, "LB2", "EQ1", "EQ1", "EQ1", None]
|
||||
|
||||
|
||||
def test_157_2_단추_바꿈():
|
||||
out = _node(
|
||||
"\nconsole.log(JSON.stringify([sgViewLabel('table'), sgViewLabel('cards'),"
|
||||
" flipSgView('table'), flipSgView(flipSgView('table'))]));"
|
||||
)
|
||||
assert out == ["호표별 보기", "표로 보기", "cards", "table"]
|
||||
|
||||
|
||||
def test_157_2_기억_실패_때_표():
|
||||
out = _node(
|
||||
"\nconst bad = { getItem: () => { throw new Error('막힘'); },"
|
||||
" setItem: () => { throw new Error('막힘'); } };"
|
||||
"\nconst mem = new Map();"
|
||||
"\nconst ok = { getItem: (k) => mem.get(k) ?? null, setItem: (k, v) => mem.set(k, v) };"
|
||||
"\nwriteSgView('cards', bad);"
|
||||
"\nconst junk = { getItem: () => 'x', setItem: () => {} };"
|
||||
"\nconst a = [readSgView(bad), readSgView(null), readSgView(junk), readSgView(ok)];"
|
||||
"\nwriteSgView('cards', ok);"
|
||||
"\nconsole.log(JSON.stringify([...a, readSgView(ok), mem.get(SG_VIEW_KEY)]));"
|
||||
)
|
||||
assert out == ["table", "table", "table", "table", "cards", "cards"]
|
||||
|
||||
|
||||
def test_157_2_화면_잇기():
|
||||
shell = (B09 / "B09_Estimation_UI_Shell.ts").read_text("utf-8")
|
||||
assert 'tab === "sg"' in shell and "renderSgPanel(" in shell
|
||||
cards = (B09 / "B09_Estimation_UI_Tabs_Sangeun_Cards.ts").read_text("utf-8")
|
||||
assert "createCard(" in cards and "createButton(" in cards
|
||||
css = (B09 / "B09_Estimation_UI_Page.css").read_text("utf-8")
|
||||
assert "white-space: pre-wrap" in css # 긴 산출내역 글 = 카드 안 줄바꿈
|
||||
|
||||
|
||||
class _Whole:
|
||||
numbers = {
|
||||
"EQ": {"0201-0070": {"이름": "굴착기(무한궤도)", "규격": "0.6㎥"}},
|
||||
"LB": {"1001": {"이름": "보통인부"}},
|
||||
}
|
||||
|
||||
|
||||
def test_157_3_규격_이름_규격():
|
||||
given = {"기계": "0201-0070", "직종": "1001", "규격": 150, "모름": "9999-9999"}
|
||||
shown, raw = spec_text(_Whole(), given)
|
||||
assert shown == "기계=굴착기(무한궤도) 0.6㎥, 직종=보통인부, 규격=150, 모름=9999-9999"
|
||||
assert raw == "기계=0201-0070, 직종=1001, 규격=150, 모름=9999-9999"
|
||||
|
||||
|
||||
def test_157_3_번호는_풍선():
|
||||
block = _block("SG:A", 1, "기계=굴착기(무한궤도) 0.6㎥", "기계=0201-0070")
|
||||
plain = _block("SG:B", 2, "규격=150", "규격=150")
|
||||
listing = [{k: b[k] for k in ("key", "no", "name", "spec", "spec_raw", "unit")}
|
||||
for b in (block, plain)] # fmt: skip
|
||||
sg_json = json.dumps([block, plain], ensure_ascii=False)
|
||||
ls_json = json.dumps(listing, ensure_ascii=False)
|
||||
out = _node(
|
||||
f"\nconst sg = buildSg({{ sheets: {{ sg: {sg_json} }} }});"
|
||||
f"\nconst ls = buildSgList({{ sheets: {{ sg_list: {ls_json} }} }});"
|
||||
"\nconst heads = sg.rows.map((r, i) => [r, i])"
|
||||
".filter(([r]) => !String(r.key).includes('|'));"
|
||||
"\nconsole.log(JSON.stringify({"
|
||||
" sg: heads.map(([r, i]) => [r.note, sg.tip(i, 'note') ?? null]),"
|
||||
" ls: [0, 1].map((i) => [ls.rows[i].spec, ls.tip(i, 'spec') ?? null]),"
|
||||
" other: ls.tip(0, 'name') ?? null }));"
|
||||
)
|
||||
assert out["sg"] == [
|
||||
["기계=굴착기(무한궤도) 0.6㎥", "원문번호 기계=0201-0070"],
|
||||
["규격=150", None],
|
||||
]
|
||||
assert out["ls"] == [
|
||||
["기계=굴착기(무한궤도) 0.6㎥", "원문번호 기계=0201-0070"],
|
||||
["규격=150", None],
|
||||
]
|
||||
assert out["other"] is None
|
||||
Reference in New Issue
Block a user