- 새 Labor_Detail.ts(mountLaborDetail · 차례 = 정보 → 담은 로직 → 자재목록 → 다이어그램 · 고칠 때마다 calc 를 뒤에서 · 최신 답만 · 같은 로직 · 자재 두 번 막음) - 새 Labor_Input.ts(담은 로직 줄 입력 모달 · 빈 칸 = 자동값 · 메모 · 수량 = 이은 칸 / 손 입력 / 면적산출에서 잇기 · 사라진 입력값 알림 · 자재 수량 모달) - 새 Labor_Rows.ts(작은 글 「지역 서울 · 수량 B6 = 12.5㎡」 · 빈 값 n 붉게 · 금액 칸 넷 두 줄(위 기준 · 아래 반영) · 합계 줄 · 보기 갈래는 손잡이 없이) - Labor_Api.ts: labor 문서 · calc 답 모양 · calcLabor · fetchLogicInputs · previewCombo 지움 - Labor_Diagram.ts: buildDiagramData → buildLaborDiagram(줄 입력값 + calc · 수량 있으면 반영 · 없으면 기준 표기 · 자재목록 카드 하나 → 재료비 · 알약 = 입력 모달) - 미리 보기 지움: Labor_Preview.ts · M02 preview 길 · Combo_Store.preview · 옛 조합 상세의 미리 보기 · 다이어그램 칸 - 시험: test_m02_labor_diagram 새 모양 · test_m01_combo 미리 보기 시험은 같은 계산(combo_preview)을 바로 부름 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
133 lines
5.2 KiB
Python
133 lines
5.2 KiB
Python
"""일위대가 다이어그램 값 만들기(`Labor_Diagram.ts` 의 `buildLaborDiagram` · 39-3) — calc 줄 → 카드 출력.
|
|
|
|
화면 TS 를 그 자리에서 트랜스파일해 node 로 돌림 — 상대 경로 TS 는 이어 트랜스파일 · 화면 부품(@ui)은 빈 흉내 ·
|
|
금액 모양(Logic_Money)도 빈 흉내(이 시험은 값 만들기만 봄). 견본 줄은 이 시험 안에 둠.
|
|
"""
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SOURCE = ROOT / "M02_MasterTemplete" / "M02_MasterTemplete_Labor_Diagram.ts"
|
|
|
|
RUNNER = r"""
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const [root, file] = process.argv.slice(1);
|
|
const ts = require(path.join(root, "config", "node_modules", "typescript"));
|
|
const stub = new Proxy({}, { get: (_t, k) => (k === "currentLanguageIndex" ? 0 : () => undefined) });
|
|
const cache = {};
|
|
function load(f) {
|
|
if (cache[f]) return cache[f].exports;
|
|
const js = ts.transpileModule(fs.readFileSync(f, "utf8"), {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
}).outputText;
|
|
const box = (cache[f] = { exports: {} });
|
|
const req = (id) => {
|
|
if (id.startsWith(".") && !id.includes("M01_MasterData")) {
|
|
const p = path.resolve(path.dirname(f), id);
|
|
return fs.existsSync(p + ".ts") ? load(p + ".ts") : stub;
|
|
}
|
|
return stub;
|
|
};
|
|
new Function("exports", "module", "require", js)(box.exports, box, req);
|
|
return box.exports;
|
|
}
|
|
const m = load(file);
|
|
const jobs = JSON.parse(fs.readFileSync(0, "utf8"));
|
|
process.stdout.write(JSON.stringify(jobs.map((j) => m.buildLaborDiagram(j))));
|
|
"""
|
|
|
|
|
|
def run(jobs: list[dict]) -> list:
|
|
if shutil.which("node") is None:
|
|
pytest.skip("node 가 없음")
|
|
done = subprocess.run(
|
|
["node", "-e", RUNNER, str(ROOT), str(SOURCE)],
|
|
input=json.dumps(jobs, ensure_ascii=False),
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=True,
|
|
)
|
|
return json.loads(done.stdout)
|
|
|
|
|
|
LOGICS = [
|
|
{
|
|
"로직": "GC000001",
|
|
"이름": "돌쌓기",
|
|
"결과단위": "원/㎡",
|
|
"입력": [{"이름": "높이", "단위": "m"}, {"이름": "지역", "자동값": "서울"}],
|
|
},
|
|
{"로직": "GF000002", "이름": "작업량", "결과단위": "㎥/시간", "입력": [{"이름": "지역"}]},
|
|
{"로직": "GC000003", "이름": "막힌 로직", "결과단위": "원/m", "입력": []},
|
|
]
|
|
ROWS = [
|
|
{"로직": "GC000001", "입력값": {"높이": 4}},
|
|
{"로직": "GF000002", "입력값": {"지역": "부산"}},
|
|
{"로직": "GC000003", "입력값": {}},
|
|
]
|
|
MONEY = {"노무비": 100, "재료비": 50, "경비": 0, "계": 150}
|
|
APPLIED = {"노무비": 300, "재료비": 150, "경비": 0, "계": 450}
|
|
|
|
|
|
def _line(key: str, **more) -> dict:
|
|
return {"로직": key, "기준": None, "반영": None, "까닭": None, **more}
|
|
|
|
|
|
def test_돈_값_멈춤_세_갈래_반영이_있으면_반영():
|
|
calc = {
|
|
"줄": [
|
|
_line("GC000001", 기준=MONEY, 반영=APPLIED),
|
|
_line("GF000002", 결과=8.946),
|
|
_line("GC000003", 까닭="표에 맞는 줄이 없음"),
|
|
],
|
|
"자재": [],
|
|
}
|
|
(got,) = run([{"rows": ROWS, "logics": LOGICS, "calc": calc, "mats": 0}])
|
|
outs = [x["out"] for x in got["logics"]]
|
|
assert outs[0] == APPLIED and got["logics"][0]["basis"] == "반영"
|
|
assert outs[1] == {"값": 8.946}
|
|
assert outs[2] == {"멈춤": "표에 맞는 줄이 없음"}
|
|
|
|
|
|
def test_수량_없으면_기준():
|
|
calc = {"줄": [_line("GC000001", 기준=MONEY)], "자재": []}
|
|
(got,) = run([{"rows": ROWS[:1], "logics": LOGICS, "calc": calc, "mats": 0}])
|
|
assert got["logics"][0]["out"] == MONEY and got["logics"][0]["basis"] == "기준"
|
|
|
|
|
|
def test_넣은_값은_로직별_빈_값은_자동값_또는_빈_글():
|
|
(got,) = run([{"rows": ROWS, "logics": LOGICS, "calc": None, "mats": 0}])
|
|
first, second, third = got["logics"]
|
|
assert [(i["name"], i["value"]) for i in first["inputs"]] == [("높이", "4"), ("지역", "서울")]
|
|
assert first["inputs"][1].get("auto") is True and "auto" not in first["inputs"][0]
|
|
assert first["inputs"][0]["unit"] == "m"
|
|
assert [(i["name"], i["value"]) for i in second["inputs"]] == [("지역", "부산")]
|
|
assert third["inputs"] == []
|
|
|
|
|
|
def test_아직_안_셌으면_출력_없음():
|
|
(got,) = run([{"rows": ROWS, "logics": LOGICS, "calc": None, "mats": 0}])
|
|
assert [x["out"] for x in got["logics"]] == [None, None, None]
|
|
assert [x["key"] for x in got["logics"]] == ["GC000001", "GF000002", "GC000003"]
|
|
|
|
|
|
def test_자재목록은_카드_하나_재료비로():
|
|
calc = {
|
|
"줄": [_line("GC000001", 기준=MONEY)],
|
|
"자재": [{"기준단가": 1000, "반영": 5000}, {"기준단가": 200, "반영": None}],
|
|
}
|
|
(got,) = run([{"rows": ROWS[:1], "logics": LOGICS, "calc": calc, "mats": 2}])
|
|
card = got["logics"][-1]
|
|
assert card["material"] is True and card["inputs"] == []
|
|
assert card["out"] == {"노무비": 0, "재료비": 5200, "경비": 0, "계": 5200}
|
|
assert card["basis"] == "기준" # 반영 없는 자재가 있음
|
|
(none,) = run([{"rows": ROWS[:1], "logics": LOGICS, "calc": calc, "mats": 0}])
|
|
assert not any(x.get("material") for x in none["logics"])
|