- 운반 단가 창 몸 두 칸 비율 — 왼쪽(옵션 값) 약 0.8 배 - 단가산출근거 [박스보기] 머리 줄 = 운반 창 ③ 칸과 같은 꼴(이름 · 값 오른쪽 굵게 · 식 작게) · 출처 = 칸 맨 위 - 줄 나눔 · 그리기 한 벌 — Haul_Steps headSteps 내보내 같이 씀 · 공용 묶음 카드 = 선택 몸 위 칸(top) - 호표 줄 · 총계 · 표 보기 · 다른 탭 카드 그대로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
121 lines
5.8 KiB
Python
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
|