Files
Aislo/resources/tester/test_717_3_sangeun_modal.py
T
eomsangdonandClaude Sonnet 5.5 2d9a469fc7 feat(806-1): 구조물 조합 일위대가 보기 = 일위대가 컨테이너 한 부품 · 단가산출 줄 = 그 SK 문서 모달(수식 탭 · 저장 = 그 SK)
- 조합 상세 · 카드 · 서고 모두 Labor_Table 한 부품 — 조합 전용 칸(면적 잇기 · 수량산출 모달 · 규격 열 · 다이어그램)은 새 Labor_Combo
- 단가산출(SK) 참조 줄 누름 = 새 Labor_SkEdit — 그 SK 문서를 단가산출 상세 부품(수식 탭 첫 화면)으로 열고 [저장] = 수식 저장 → SK 문서 저장 → 일위대가 금액 다시 셈 · 위 층 SK = 읽기만 · 두 번 눌러도 모달 하나
- 옛 조합 전용 표 · 단가산출 보기 모달(Labor_SangeunModal) 지움 · 전국 자재 줄은 Labor_Table 이 그림
- 새 시험 test_806_1_labor_view · 소스 지킴 시험 12 개를 새 자리(Labor_Table · Labor_RowModal · Labor_SkEdit)로 고침 — 옛 Detail 자리 문구가 없어져 낡았음

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 16:31:49 +09:00

187 lines
8.3 KiB
Python

# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
"""일위대가 줄 [단가산출 보기] 모달(PLAN 717-3 · 724-1) — 임시 storage · 순수 함수는 Node 로.
화면 문서(`logic/M02_Sangeun_Cover_Model.lineDoc`) = 서버 `M02_Labor_Patch.as_doc` 꼴
(같은 고름 · 자재표시 cid) ·
모달 셈 길(`/sangeun/calc` = `calc_doc`) 값 = 일위대가 셈의 그 줄 값 · 고친 칸 = 덮개 칸 이름 ·
패치 정리 순수 함수 · `normalizeLabor` 가 줄 `패치` 를 지킴 ·
화면 이음(모달 = 줄 모드 SK 상세 · 공용 상세).
"""
import json
import re
import shutil
import subprocess
from pathlib import Path
import pytest
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Labor_Patch as patch
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_Sangeun_Calc as calc
from M02_MasterTemplete import M02_Sangeun_Doc as sd
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
ROOT = Path(__file__).resolve().parents[2]
M02 = ROOT / "M02_MasterTemplete"
PCT = "GC001109" # 원/㎡ · 인력 둘 + 「노무비 * 3 / 100」
TWO_MATS = "GC000565" # 원/m · 재료 줄 둘
COVER = {"덮개": {"labor:LB000036#0": {"qty": 0.5}}}
MODEL = r"""
const fs = require("fs");
const path = require("path");
const root = process.argv[1];
const ts = require(path.join(root, "config", "node_modules", "typescript"));
const f = path.join(root, "M02_MasterTemplete", "logic", "M02_Sangeun_Cover_Model.ts");
const js = ts.transpileModule(fs.readFileSync(f, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
new Function("exports", "module", "require", js)(box.exports, box, () => ({}));
const m = box.exports;
const job = JSON.parse(fs.readFileSync(0, "utf8"));
const out = {};
for (const [k, [fn, ...args]] of Object.entries(job)) out[k] = m[fn](...args);
process.stdout.write(JSON.stringify(out));
"""
def _node(job: dict) -> dict:
node = shutil.which("node")
if not node:
pytest.skip("node 없음")
got = subprocess.run(
[node, "-e", MODEL, str(ROOT)],
input=json.dumps(job, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
)
assert got.returncode == 0, got.stderr
return json.loads(got.stdout)
def _whole():
return m01.loaded()[1]
def test_화면_문서_서버_as_doc_꼴():
"""고름 · 자재표시(호표 차례) → cid · 패치 · 참조 줄 = 서버 `as_doc` 와 같은 몸."""
import master_pick as mp
whole = _whole()
row = whole.logic(TWO_MATS)
item = row["호표"][2]
want = mp.search(whole, item["요소"], {}, item.get("단위"))[1]
drop = {"뺀줄": ["material:" + row["호표"][1]["이름"] + "#0"]}
one = {
"로직": TWO_MATS,
"입력값": {},
"고름": {"2": want},
"자재표시": {"2": {"별도계상": "직접계산"}},
}
ref = {"참조": {"층": "personal", "주인": None, "키": "SK-000001"}, "입력값": {}}
ids = sd.line_ids(row)
got = _node(
{
"logic": ["lineDoc", one, ids, drop],
"ref": ["lineDoc", ref, None, COVER],
"bare": ["lineDoc", one, None, None],
"needs": ["needsIds", one],
"needsRef": ["needsIds", ref],
}
)
assert got["logic"].pop("줄모드") is True and got["ref"].pop("줄모드") is True # 750-1
assert got["logic"] == patch.as_doc({**one, "패치": drop}, whole)
assert got["ref"] == patch.as_doc({**ref, "패치": COVER}, whole)
assert got["bare"]["고름"] == {} and got["bare"]["덮개"] == {}
assert got["needs"] is True and got["needsRef"] is False
def test_모달_셈_길_값_일위대가_줄_값(world):
"""모달 셈(`calc_doc` · 저장 안 함) 기준 = 일위대가 셈 그 줄 기준 · 고친 줄 덮개 칸 = qty · 정체 = 바탕 줄."""
whole = _whole()
line = {"로직": PCT, "입력값": {}, "수량": {"손": 1}}
doc = _node({"d": ["lineDoc", line, None, COVER]})["d"]
bare = _node({"d": ["lineDoc", line, None, None]})["d"]
with store.at(world["personal"]):
got = calc.calc_doc(doc)
was = calc.calc_doc(bare)
mine = labor.calc(labor.clean({"담은로직": [{**line, "패치": COVER}]}), {})["줄"][0]
plain = labor.calc(labor.clean({"담은로직": [line]}), {})["줄"][0]
assert got["기준"] == pytest.approx(mine["기준"])
assert was["기준"] == pytest.approx(plain["기준"])
assert got["기준"]["계"] != was["기준"]["계"] # 품 0.5 로 바뀜
assert was["정체"] == sd.line_ids(whole.logic(PCT))
cover = [x for x in got["줄"] if x["cid"] == "labor:LB000036#0"][0]
assert cover["덮개"] == ["qty"] and cover["수량"] == pytest.approx(0.5)
def test_패치_정리_순수_함수():
"""원값 뺌 · 빈 칸 뺌 · 다 비면 null(줄 칸 안 둠) — 덮개 고침 자체는 717-2b `setLineCover`."""
one = {"덮개": {"labor:LB1#0": {"qty": 0.5, "원값": {"qty": 0.2}}, "x:a#0": {"원값": {}}}}
got = _node(
{
"clean": ["cleanPatch", one],
"empty": ["cleanPatch", {"덮개": {}, "뺀줄": [], "더한줄": []}],
"none": ["cleanPatch", None],
"keep": ["cleanPatch", {"뺀줄": ["a:b#0"]}],
}
)
assert got["clean"] == {"덮개": {"labor:LB1#0": {"qty": 0.5}}} # 원값 = 서버 몫
assert got["empty"] is None and got["none"] is None
assert got["keep"] == {"뺀줄": ["a:b#0"]}
def test_normalizeLabor_줄_패치_지킴():
node = shutil.which("node")
if not node:
pytest.skip("node 없음")
doc = {"담은로직": [{"로직": PCT, "패치": COVER}, {"로직": TWO_MATS}]}
got = subprocess.run(
[node, str(ROOT / "resources/tester/helper_m02_labor_normalize.cjs")],
input=json.dumps({"doc": doc, "column": "c"}, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
)
assert got.returncode == 0, got.stderr
rows = json.loads(got.stdout)["담은로직"]
assert rows[0]["패치"] == COVER and "패치" not in rows[1]
def test_화면_이음():
"""806-1 — 조합 일위대가도 일위대가표 한 부품 · 단가산출(SK) 줄 = 그 SK 문서 모달(`Labor_SkEdit`) · 옛 조합 전용
단가산출 보기 모달 걷음 · 셈 주입(프로젝트 복사본)엔 옛 줄 모달 · 고친 것 있이 닫으면 물음 · 700 줄."""
detail = (M02 / "M02_MasterTemplete_Labor_Detail.ts").read_text("utf-8")
assert "return mountLaborTable(host, doc, opt);" in detail
assert not (M02 / "M02_MasterTemplete_Labor_SangeunModal.ts").exists()
table = (M02 / "M02_MasterTemplete_Labor_Table.ts").read_text("utf-8")
assert "if (isSkRef(row.참조) && !opt.calc)" in table and "openSkEdit({" in table
assert "readOnly: !edit || row.참조.층 !== opt.layer," in table # 위 층 SK = 읽기만
assert "closed: (done) => alive && done && recalc()," in table # 저장했으면 금액 다시 셈
modal = (M02 / "M02_MasterTemplete_Labor_SkEdit.ts").read_text("utf-8")
assert "mountSangeunDetail(host, doc," in modal and "source: true" in modal
assert re.search(
r'saveTemplate\(\s*layer,\s*"sangeun",\s*opt\.ref\.키', modal
) # 저장 = 그 SK 문서(그 층 · 그 주인)
assert "await handle.flush();" in modal # 수식 저장 먼저
assert "dirty && !saved && !(await showConfirmDialog(" in modal # 고친 것 물음
sk = (M02 / "M02_MasterTemplete_Labor_RowSk.ts").read_text("utf-8")
assert '{ layer: "personal", owner: null }' in sk
cover = (M02 / "M02_MasterTemplete_Sangeun_Cover.ts").read_text("utf-8")
assert "/sangeun/calc" in cover and "/sangeun/from-line" in cover
assert "coverTable" not in cover and not (M02 / "M02_Sangeun_Cover_Model.ts").exists()
for name in (
"M02_MasterTemplete_Labor_Rows.ts",
"M02_MasterTemplete_Labor_Table.ts",
"M02_MasterTemplete_Labor_Combo.ts",
"M02_MasterTemplete_Labor_SkEdit.ts",
"M02_MasterTemplete_Sangeun_Cover.ts",
"logic/M02_Sangeun_Cover_Model.ts",
):
assert len((M02 / name).read_text("utf-8").splitlines()) <= 700, name