Files
Aislo/resources/tester/test_m02_labor_logic_mats.py
T
eomsangdonandClaude Opus 5.5 f79078f9c9 feat(m02): 일위대가 자재목록 로직 자재 기본 줄 · [바꾸기] 고름 · 사용자 추가분만 삭제(103-1)
- 문서 담은로직[].고름 {호표 차례: MT키} 정리 · 검사(재료 줄 · M01 자재품목만)
- run_line 을 엔진 choosing(고름)으로 감쌈 · 로직 자재 줄(차례 · 품목 · 단가 · 금액 · 가격 · 걸림) 내보냄
- 로직 자재 금액은 합 · 알약에 안 더함 · 사용자 자재와 같은 품목은 저장 때 400
- 단가승격 · 사전조합 안 로직도 고름 따름(없으면 지금 값)
- 프로젝트 사본 고른자재 씨앗 = 문서 고름 · 단가 묶음에 고른 품목 줄
- 화면: 자재목록 표 앞에 로직 자재 줄(삭제 없음 · [바꾸기] · 바꾼 줄 [기본]) · 입력 바뀌면 걸린 고름 풂 · 로직 자재와 같은 품목 추가 막음
- 시험 test_m02_labor_logic_mats.py(서버 · Node 짝)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
2026-10-08 16:18:21 +09:00

163 lines
7.1 KiB
Python

"""일위대가 로직 자재(PLAN 103-1) — 담은 로직 맨 위 층 재료 줄 · 고름 · 합 이중 계산 없음.
- 로직 줄 계산값 `재료` = 호표 재료 줄(차례 · 품목 · 단가 · 금액 · 걸림) · 재료 없는 로직은 빈 목록
- 고름 `{호표 차례: MT키}` → 그 줄 품목 · 단가 · 로직 재료비가 바뀜(엔진 `choosing`) ·
고름 없으면 지금 값
- 합 · 알약에 로직 자재 금액을 다시 안 더함 · 저장 뒤 고름 남음 · 틀린 고름 400
- 사용자 자재와 같은 품목 = [저장] 400 · 입력에 걸린 고름은 입력이 바뀌면 풂(화면 짝 · Node)
- 프로젝트 사본 씨앗 = 문서 고름
"""
import json
import os
import subprocess
import pytest
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Item_Snapshot as snap
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from resources.tester.test_m02_structure import write_sample_table
HERE = os.path.dirname(os.path.abspath(__file__))
NET = "GC000337" # 낙석방지망 — 재료 줄 3 · 4 · 5(철망 MT006697 · 결속선 MT000194 · 에폭시)
BORING = "GC000431" # 보링 천공 — 재료 줄 4~8 조건에 {규격} · 9 = MT000008(입력 안 걸림)
BARE = "GC000994" # 재료 줄 없는 돈 로직
WIRE = "MT000194"
NET_ITEM = "MT006697"
def _whole():
return m01.loaded()[1]
def _doc(**extra):
return {"열": "rv", "담은로직": [], "자재": [], **extra}
def test_로직_자재는_맨_위_층_재료_줄_재료_없는_로직은_빈_목록():
got = labor.run_line(_whole(), NET, {})
assert [(m["차례"], m["품목"]["키"]) for m in got["재료"]] == [
(3, NET_ITEM),
(4, WIRE),
(5, "MT007705"),
]
assert all(m["고름"] is False and m["걸림"] == [] for m in got["재료"])
assert labor.run_line(_whole(), BARE, {})["재료"] == []
def test_조건에_든_입력은_걸림():
mats = {m["차례"]: m for m in labor.run_line(_whole(), BORING, {})["재료"]}
assert all(mats[n]["걸림"] == ["규격"] for n in range(4, 9))
assert mats[9]["걸림"] == []
def test_고름이_단가와_로직_재료비를_바꿈_고름_없으면_지금_값():
plain = labor.run_line(_whole(), NET, {})
again = labor.run_line(_whole(), NET, {}, None, None)
assert again["기준"] == plain["기준"] # 고름 없음 = 지금 길 그대로(회귀)
picked = labor.run_line(_whole(), NET, {}, None, {"3": WIRE})
row = picked["재료"][0]
assert (row["품목"]["키"], row["고름"]) == (WIRE, True)
assert row["단가"] == picked["재료"][1]["단가"] != plain["재료"][0]["단가"]
gap = (plain["재료"][0]["단가"] - row["단가"]) * row["수량"]
assert plain["기준"]["재료비"] - picked["기준"]["재료비"] == pytest.approx(gap)
assert picked["기준"]["노무비"] == plain["기준"]["노무비"]
def test_합_알약에_로직_자재_금액_다시_안_더함():
doc = labor.clean(_doc(담은로직=[{"로직": NET, "수량": {"손": 2}}]))
got = labor.calc(doc, {})
line = got["줄"][0]
assert line["재료"] and got["자재"] == []
assert got["기준합"]["계"] == pytest.approx(line["기준"]["계"])
assert got["반영합"]["계"] == pytest.approx(line["기준"]["계"] * 2)
assert not [p for p in labor.pills(doc) if p["키"].startswith(labor.MATERIAL_HEAD)]
def test_고름_정리_틀리면_400():
got = labor.clean(_doc(담은로직=[{"로직": NET, "고름": {3: WIRE}}, {"로직": BARE, "고름": {}}]))
assert got["담은로직"][0]["고름"] == {"3": WIRE} and "고름" not in got["담은로직"][1]
for bad in ({"0": WIRE}, {"3": "MT999999"}, {"3": "LB000001"}, {"가": WIRE}, ["3"]):
with pytest.raises(store.StoreError) as e:
labor.clean(_doc(담은로직=[{"로직": NET, "고름": bad}]))
assert e.value.status == 400
def test_사용자_자재가_로직_자재와_같으면_저장_400():
with pytest.raises(store.StoreError) as e:
labor.server(_doc(담은로직=[{"로직": NET}], 자재=[{"코드": WIRE}]))
assert e.value.status == 400 and WIRE in str(e.value.detail)
# 고른 품목도 같음 — 결속선 줄을 다른 품목으로 바꾸면 사용자 자재로 담을 수 있음
with pytest.raises(store.StoreError):
labor.server(
_doc(담은로직=[{"로직": NET, "고름": {"3": "MT000008"}}], 자재=[{"코드": "MT000008"}])
)
ok = labor.server(
_doc(담은로직=[{"로직": NET, "고름": {"4": NET_ITEM}}], 자재=[{"코드": WIRE}])
)
assert ok["자재"][0]["코드"] == WIRE
@pytest.fixture()
def system(tmp_path, monkeypatch):
write_sample_table(tmp_path)
monkeypatch.setattr(store, "FOLDER", tmp_path)
store.create_structure("rv")
return tmp_path
def test_저장_뒤_고름_남고_계산값에_반영(system):
have = store.read("labor", "rv")
sent = {**have["문서"], "담은로직": [{"로직": NET, "고름": {"3": WIRE}, "수량": None}]}
store.write("labor", "rv", have["판"], sent)
doc = store.read("labor", "rv")["문서"]
assert doc["담은로직"][0]["고름"] == {"3": WIRE}
assert doc["계산값"]["줄"][0]["재료"][0]["품목"]["키"] == WIRE
def test_단가승격도_고름을_따름_없으면_지금_값():
expand = refs.Expand(_whole())
plain = expand._promote({"담은로직": [{"로직": NET}], "배": 1})
picked = expand._promote({"담은로직": [{"로직": NET, "고름": {"3": WIRE}}], "배": 1})
assert plain["기준"] == labor.run_line(_whole(), NET, {})["기준"]
assert picked["기준"]["재료비"] < plain["기준"]["재료비"]
def test_사본_씨앗은_문서_고름():
labor_file = {
"줄": {
"담은로직": [
{"로직": NET, "고름": {"3": WIRE}},
{"로직": BARE},
{"참조": {"층": "system", "주인": None, "키": "SP-000001"}, "로직": ""},
]
}
}
assert snap.seed_picks(labor_file) == {NET: {"3": WIRE}}
assert snap.seed_picks(None) == {}
def _release(row: dict, before: dict, mats: list) -> dict:
proc = subprocess.run(
["node", os.path.join(HERE, "helper_m02_labor_logic_mats.cjs")],
input=json.dumps({"row": row, "before": before, "mats": mats}, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=True,
)
return json.loads(proc.stdout)
def test_입력이_바뀌면_걸린_고름만_풂_화면_짝():
mats = labor._json(labor.run_line(_whole(), BORING, {})["재료"])
picks = {"4": "MT007895", "9": WIRE}
row = {"로직": BORING, "입력값": {"규격": "NX"}, "고름": picks}
assert _release(row, {"규격": "NX"}, mats)["고름"] == picks # 그대로
assert _release(row, {"규격": "BX"}, mats)["고름"] == {"9": WIRE} # 규격에 걸린 줄만
row = {**row, "고름": {"4": "MT007895"}}
assert "고름" not in _release(row, {}, mats) # 다 풀리면 칸째 지움