Files
Aislo/resources/tester/test_278_1_sg_box_steps.py
T
eomsangdonandClaude Opus 5.5 04659a028f feat(b09): 운반 단가 창 왼쪽 폭 줄임 · 단가산출근거 박스보기 머리 줄 = 입력값 · 중간값 칸 (278-1·2)
- 운반 단가 창 몸 두 칸 비율 — 왼쪽(옵션 값) 약 0.8 배
- 단가산출근거 [박스보기] 머리 줄 = 운반 창 ③ 칸과 같은 꼴(이름 · 값 오른쪽 굵게 · 식 작게) · 출처 = 칸 맨 위
- 줄 나눔 · 그리기 한 벌 — Haul_Steps headSteps 내보내 같이 씀 · 공용 묶음 카드 = 선택 몸 위 칸(top)
- 호표 줄 · 총계 · 표 보기 · 다른 탭 카드 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
2026-10-12 18:44:11 +09:00

121 lines
5.8 KiB
Python

"""278 B09 단가산출근거 [박스보기] 머리 줄 = 운반 단가 창 ③ 칸 꼴(입력값 · 중간값 · 이름 · 값 · 식) ·
줄 나눔 · 그리기 한 벌(`Haul_Steps.headSteps`) · 호표 줄 · 총계 · 다른 탭 카드 그대로 ·
운반 단가 창 왼쪽 폭(278-1)."""
import json
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 resources.tester.helper_b09_group_cards import COMMON, GROUPS, RAW, TABS, run # noqa: E402
B09 = "B09_Estimation/B09_Estimation_UI_"
STEPS = f"{B09}Haul_Steps.ts"
# 공용 el 짝(가짜 DOM) — data-* = dataset
EL = r"""
const el = (tag, o = {}) => {
const n = document.createElement(tag);
if (o.className) n.className = o.className;
if (o.text !== undefined) n.textContent = o.text;
for (const [k, v] of Object.entries(o.attrs ?? {})) {
n.setAttribute(k, v);
if (k.startsWith("data-")) n.dataset[k.slice(5)] = v;
}
for (const c of o.children ?? []) n.append(c);
return n;
};
"""
SG = {
"key": "SG:A", "no": 1, "name": "골재운반", "unit": "㎥", "spec": "Cm=100", "spec_raw": "Cm=100",
"source": "산림품셈 10-3-1",
"head": ["골재운반 / ㎥", "산림품셈 10-3-1 적용", "qt : 15 ㎥", "Cm : 운반 사이클 = 100 분",
"작업량 = 골재운반 덤프트럭 시간당 작업량 = 8.1", "└ 결과 = 반올림(60 * 15 / 100, 3) = 8.1",
"덤프트럭 15ton 수량 = 1 / 작업량 = 1 / 8.1 = 0.1235 시간"],
"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"]},
],
"labor": 2979, "material": 0, "expense": 0, "sum": 2979, "why": None, "foot": "계 : 2,979",
} # fmt: skip
PANEL = """
const gone = [];
const o = { tab: TAB, spec, hint: null, goto: (l) => gone.push(l), redraw: () => {},
setTools: () => {}, makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }) };
const [view] = renderGroupPanel(o);
const top = view.querySelector('.b09-sgsteps');
const rows = top ? top.querySelectorAll('.b09-haulstep__row').map((r) =>
[r.dataset.at ?? null, ...r.children.map((c) => [c.className, c.textContent])]) : null;
const groups = top ? top.querySelectorAll('.b09-haulstep__group').map((g) => g.textContent) : null;
const body = view.querySelectorAll('tr').filter((tr) => tr.dataset.at !== undefined).map((tr) => [tr.dataset.at, tr.children[0].text]);
const foot = view.querySelector('.ui-group-card__foot')?.dataset.at ?? null;
if (top) top.querySelectorAll('.b09-haulstep__row')[2].click();
const lit = view.querySelectorAll('.is-focus').map((e) => e.dataset.at);
console.log(JSON.stringify({ rows, groups, body, foot, gone, lit }));
"""
def _panel(tab, files, build, sheets, prelude=""):
js = (
f"\nconst TAB = '{tab}';"
f"\nconst spec = {build}({{ sheets: {json.dumps(sheets, ensure_ascii=False)} }});" + PANEL
)
return run([COMMON, TABS, *files, STEPS, GROUPS], js, EL + prelude)
def test_박스보기_머리_줄_나눔():
out = _panel("sg", [f"{B09}Tabs_Sangeun.ts"], "buildSg", {"sg": [SG]})
assert out["groups"] == ["산림품셈 10-3-1", "입력값", "중간값"] # 출처 = 칸 맨 위
v, f = "b09-haulstep__value", "b09-haulstep__formula"
assert out["rows"] == [
["2", ["", "qt"], [v, "15 ㎥"]], # 표 줄 번호 그대로(머리 첫 줄 = 카드 머리 · 출처 줄 = 위)
["3", ["", "Cm"], [v, "100 분"], [f, "운반 사이클"]],
["4", ["", "작업량"], [v, "8.1"],
[f, "골재운반 덤프트럭 시간당 작업량\n└ 결과 = 반올림(60 * 15 / 100, 3) = 8.1"]],
["6", ["", "덤프트럭 15ton 수량"], [v, "0.1235 시간"], [f, "1 / 작업량 = 1 / 8.1"]],
] # fmt: skip
# 호표 줄 · 총계 그대로(머리 줄은 표 몸에서 빠짐)
assert out["body"] == [
["7", "보통인부 : 165,545 * 0.018 = 2,979.8노무"]
] # 가짜 DOM 글 = 이름 + 꼬리표
assert out["foot"] == "8"
# 머리 줄 누름 = 고름(is-focus) · 이동 없음(표와 같음)
assert out["lit"] == ["4"] and out["gone"] == [None]
def test_다른_탭_카드_그대로():
dg = {
"id": "DG1", "no": 1, "name": "찰쌓기", "spec": "", "spec_raw": "", "unit": "m2",
"kind": "", "cite": "",
"lines": [{"kind": "labor", "key": "L1", "name": "석공", "qty": 0.072, "unit": "인",
"price": 1, "amount": 1, "ref_no": "",
"prices": {"labor": 1, "material": None, "expense": None},
"amounts": {"labor": 1, "material": None, "expense": None}}],
"sum": 1, "labor": 1, "material": 0, "expense": 0, "blank": 0, "blank_names": [],
} # fmt: skip
out = _panel("dg", [f"{B09}Tabs_Labor.ts"], "buildDaegaBlocks", {"daega_blocks": [dg]}, RAW)
assert out["rows"] is None and out["body"] == [["1", "석공"]]
def test_한_벌_잇기():
groups = (ROOT / GROUPS).read_text("utf-8")
assert 'import { headSteps } from "./B09_Estimation_UI_Haul_Steps";' in groups
assert 'indexOf(" : ")' not in groups # 줄 나눔 복사 없음
assert "sgHeadCards(tabGroups(o.tab, o.spec), o.spec.rows)" in groups
steps = (ROOT / STEPS).read_text("utf-8")
assert "export function headSteps" in steps and "[...headSteps(b), ...group(" in steps
common = (ROOT / COMMON).read_text("utf-8")
assert "top?: HTMLElement | null;" in common and "if (g.top) body.append(g.top);" in common
sangeun = (ROOT / f"{B09}Tabs_Sangeun.ts").read_text("utf-8")
assert 'source: b.source ?? ""' in sangeun
def test_운반_창_왼쪽_폭():
modal = (ROOT / f"{B09}Haul_Modal.ts").read_text("utf-8")
assert "grid-template-columns:minmax(260px,0.7fr) minmax(340px,1.4fr)" in modal