Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SE9MxeXTu2uaDZ55BJowAY
165 lines
7.1 KiB
Python
165 lines
7.1 KiB
Python
"""일위대가 자재목록 세 칸(PLAN 148-4) — 자재구분 · 별도계상 · 운반비 · 금액 무관.
|
|
|
|
- 서버 `clean` = 허용 값만(그 밖 · 빈 것 버림) · 사용자 자재 = 줄 덧칸 ·
|
|
로직 자재 = 로직 줄 `자재표시 {호표 차례: 세 칸}`(차례가 재료 줄인 것만 · 틀려도 400 아님)
|
|
- 기준합 · 반영합 그대로 · 저장 왕복에 남음 · 화면 `normalizeLabor` 도 같은 규칙(Node 헬퍼)
|
|
- 화면 칸 부품 = 새 파일 · 사용자 줄 · 로직 줄 같은 칸 · 사전조합 줄엔 안 넘김(소스 검사)
|
|
- 162-1 = 「일반」 지움(관급 · 사급) · 빈 칸 = 읽을 때 기본(사급 · 별도계상 · 포함 · 저장은 비움) ·
|
|
옛 「일반」 = 사급으로 읽음 · 별도계상이면 운반비 드롭다운 꺼짐(값 그대로)
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
from M02_MasterTemplete import M02_Labor_Doc as labor
|
|
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__))
|
|
SRC = os.path.join(HERE, "..", "..", "M02_MasterTemplete")
|
|
NET = "GC000337" # 낙석방지망 — 재료 줄 3 · 4 · 5
|
|
WIRE = "MT000194"
|
|
MARKS = {"자재구분": "관급", "별도계상": "별도계상", "운반비": "미포함"}
|
|
|
|
|
|
def _doc(**extra):
|
|
return {"열": "rv", "담은로직": [], "자재": [], **extra}
|
|
|
|
|
|
def _first_non_material(code):
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
|
|
rows = m01.loaded()[1].logic(code).get("호표") or []
|
|
return next(n for n, r in enumerate(rows) if r.get("종류") != "재료")
|
|
|
|
|
|
def test_사용자_자재_허용_값만():
|
|
got = labor.clean(
|
|
_doc(자재=[{"코드": WIRE, **MARKS}, {"코드": "MT000008", "자재구분": "아무", "운반비": ""}])
|
|
)["자재"]
|
|
assert {k: got[0][k] for k in MARKS} == MARKS
|
|
assert not set(MARKS) & set(got[1])
|
|
|
|
|
|
def test_로직_자재_자재표시_재료_줄_차례만_허용_값만():
|
|
other = _first_non_material(NET)
|
|
shown = {"3": MARKS, "4": {"자재구분": "엉뚱"}, str(other): MARKS, "99": MARKS, "x": MARKS}
|
|
line = labor.clean(_doc(담은로직=[{"로직": NET, "자재표시": shown}]))["담은로직"][0]
|
|
assert line["자재표시"] == {"3": MARKS}
|
|
bare = labor.clean(_doc(담은로직=[{"로직": NET, "자재표시": {"4": {"운반비": "?"}}}]))
|
|
assert "자재표시" not in bare["담은로직"][0]
|
|
|
|
|
|
def test_금액_그대로():
|
|
plain = _doc(
|
|
담은로직=[{"로직": NET, "수량": {"손": 2}}], 자재=[{"코드": WIRE, "수량": {"손": 3}}]
|
|
)
|
|
marked = _doc(
|
|
담은로직=[{"로직": NET, "수량": {"손": 2}, "자재표시": {"3": MARKS}}],
|
|
자재=[{"코드": WIRE, "수량": {"손": 3}, **MARKS}],
|
|
)
|
|
a, b = (labor.calc(labor.clean(d), {}) for d in (plain, marked))
|
|
assert a["기준합"] == b["기준합"] and a["반영합"] == b["반영합"]
|
|
|
|
|
|
@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, "수량": None, "자재표시": {"3": MARKS}}],
|
|
"자재": [{"코드": "MT000008", "수량": None, "자재구분": "사급"}],
|
|
}
|
|
store.write("labor", "rv", have["판"], sent)
|
|
doc = store.read("labor", "rv")["문서"]
|
|
assert doc["담은로직"][0]["자재표시"] == {"3": MARKS}
|
|
assert doc["자재"][0]["자재구분"] == "사급" and "운반비" not in doc["자재"][0]
|
|
|
|
|
|
def test_화면_정리도_허용_값만():
|
|
doc = {
|
|
"담은로직": [
|
|
{"로직": NET, "자재표시": {"3": MARKS, "4": {"운반비": "?"}}},
|
|
{
|
|
"로직": "",
|
|
"참조": {"층": "system", "주인": None, "키": "SC-1"},
|
|
"자재표시": {"3": MARKS},
|
|
},
|
|
],
|
|
"자재": [{"코드": WIRE, "별도계상": "직접계산", "자재구분": "?"}],
|
|
}
|
|
proc = subprocess.run(
|
|
["node", os.path.join(HERE, "helper_m02_labor_normalize.cjs")],
|
|
input=json.dumps({"doc": doc, "column": "rv"}, ensure_ascii=False),
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=True,
|
|
)
|
|
got = json.loads(proc.stdout)
|
|
assert [r.get("자재표시") for r in got["담은로직"]] == [{"3": MARKS}, None]
|
|
assert got["자재"][0]["별도계상"] == "직접계산" and "자재구분" not in got["자재"][0]
|
|
|
|
|
|
def _src(name):
|
|
with open(os.path.join(SRC, name), encoding="utf-8") as f:
|
|
return f.read()
|
|
|
|
|
|
def test_화면_칸_부품_사용자_줄_로직_줄_같은_칸():
|
|
marks = _src("M02_MasterTemplete_Labor_MatMarks.ts")
|
|
assert '"—"' not in marks # 「—」 항목 없음(162-1)
|
|
assert "e.stopPropagation()" in marks # 줄 누름과 따로
|
|
rows = _src("M02_MasterTemplete_Labor_Rows.ts")
|
|
assert "...markCells(mat, on?.mark" in rows and "...markHeads()," in rows
|
|
assert "...markCells(" in _src("M02_MasterTemplete_Labor_LogicMats.ts")
|
|
detail = _src("M02_MasterTemplete_Labor_Detail.ts")
|
|
assert "mark: lib ? undefined : markLogicMat" in detail
|
|
assert "mark: markMat" in detail
|
|
# 서버 허용 값 = 화면 허용 값
|
|
api = _src("M02_MasterTemplete_Labor_Api.ts")
|
|
for key, allowed in labor.MARKS.items():
|
|
assert f"{key}: [{', '.join(json.dumps(v, ensure_ascii=False) for v in allowed)}]" in api
|
|
|
|
|
|
def test_빈_칸_기본값_허용_값_그대로():
|
|
assert labor.MARK_DEFAULT == {"자재구분": "사급", "별도계상": "별도계상", "운반비": "포함"}
|
|
assert {k: labor.mark_of({}, k) for k in labor.MARKS} == labor.MARK_DEFAULT
|
|
assert {k: labor.mark_of(MARKS, k) for k in labor.MARKS} == MARKS
|
|
assert labor.mark_of(None, "운반비") == "포함"
|
|
|
|
|
|
def test_옛_일반은_사급으로_읽음():
|
|
assert "일반" not in labor.MARKS["자재구분"]
|
|
got = labor.clean(
|
|
_doc(
|
|
담은로직=[{"로직": NET, "자재표시": {"3": {"자재구분": "일반", "운반비": "미포함"}}}],
|
|
자재=[{"코드": WIRE, "자재구분": "일반"}],
|
|
)
|
|
)
|
|
assert got["담은로직"][0]["자재표시"] == {"3": {"운반비": "미포함"}} # 저장은 채우지 않음
|
|
assert "자재구분" not in got["자재"][0]
|
|
assert labor.mark_of(got["자재"][0], "자재구분") == "사급"
|
|
assert labor.mark_of({"자재구분": "일반"}, "자재구분") == "사급"
|
|
|
|
|
|
def test_화면_기본값_한_벌_운반비_꺼짐():
|
|
api = _src("M02_MasterTemplete_Labor_Api.ts")
|
|
for key, v in labor.MARK_DEFAULT.items():
|
|
assert f'{key}: "{v}",' in api.split("MAT_MARK_DEFAULT")[1].split("};")[0]
|
|
marks = _src("M02_MasterTemplete_Labor_MatMarks.ts")
|
|
assert "value[key] ?? MAT_MARK_DEFAULT[key]" in marks
|
|
assert 'picks.운반비.disabled = sep === "별도계상"' in marks
|
|
assert "haulOff(value.별도계상 ?? MAT_MARK_DEFAULT.별도계상)" in marks
|
|
assert '일반: "Mark_General"' not in marks
|