"""일위대가 자재목록 세 칸(PLAN 148-4) — 자재구분 · 별도계상 · 운반비 · 금액 무관. - 서버 `clean` = 허용 값만(그 밖 · 빈 것 버림) · 사용자 자재 = 줄 덧칸 · 로직 자재 = 로직 줄 `자재표시 {호표 차례: 세 칸}`(차례가 재료 줄인 것만 · 틀려도 400 아님) - 기준합 · 반영합 그대로 · 저장 왕복에 남음 · 화면 `normalizeLabor` 도 같은 규칙(Node 헬퍼) - 화면 칸 부품 = 새 파일 · 사용자 줄 · 로직 줄 같은 칸 · 사전조합 줄엔 안 넘김(소스 검사) - 162-1 = 「일반」 지움(관급 · 사급) · 빈 칸 = 읽을 때 기본(사급 · 별도계상 · 포함 · 저장은 비움) · 옛 「일반」 = 사급으로 읽음 - 168-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: markLogicMat," in detail and "lib ? undefined" not in detail # 사전조합도(175-1) 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 "disabled" not in marks and "haulOff" not in marks # 별도계상과 따로 assert "onChange: (v) => set(key, v)," in marks # 칸마다 제 값만 저장 assert '일반: "Mark_General"' not in marks