diff --git a/M02_MasterTemplete/M02_Labor_Doc.py b/M02_MasterTemplete/M02_Labor_Doc.py index 890c7155a..655ec4775 100644 --- a/M02_MasterTemplete/M02_Labor_Doc.py +++ b/M02_MasterTemplete/M02_Labor_Doc.py @@ -20,9 +20,11 @@ 고름 = `{호표 차례: MT키}`(엔진 `choosing` 그대로) · 금액은 로직 재료비 안이라 합 · 알약에 안 더함(보기만) · 사용자 자재와 같은 품목 두 번은 [저장] 때 400. 승격(134-1) = 로직 줄 「단가승격」 표시(`true` 만 · 없음 = 단가산출) · 금액 무관 · B09 호표 몫. -자재 세 칸(148-4) = `MARKS` 허용 값만(그 밖 · 빈 것 버림 · 금액 무관) — 사용자 자재 = 줄 덧칸 · +자재 세 칸(148-4) = `MARKS` 허용 값만(그 밖 · 빈 것 버림 · 별도계상만 금액에 닿음) — 사용자 자재 = 줄 덧칸 · 로직 자재 = 로직 줄 `자재표시 {호표 차례: {세 칸}}`(차례가 재료 줄인 것만 · 틀리면 버림) · 빈 칸 = 읽을 때 `MARK_DEFAULT`(`mark_of` · 저장은 비움 · 옛 「일반」 = 버림 → 사급 · 162-1). +별도계상(222-1) = 그 자재 금액은 기준 · 합에서 빠지고 로직 `줄` 에서 지움(`drop_apart` · `settle` · +짝 `Labor_Calc.ts`) · `재료` · `자재`(자재목록 · B08 수량)엔 남음. % 덧줄(149-4) = 식 「X * Y / 100」 덧줄에 덧칸 `율`(Y) · `규격`(「노무비의 %」 등 · `pct_extras`) — 표기만 · 단위 · 수량 · 금액 그대로(「식 · 1」) · 화면이 율 · % 로 그림 · M01 시험계산 표도 이 함수. """ @@ -224,6 +226,35 @@ def mark_of(one: Any, key: str) -> str: return marks(one).get(key) or MARK_DEFAULT[key] +def apart(one: Any) -> bool: + """별도계상(222-1) — 세 칸 「별도계상」(빈 칸 = 기본) 자재는 일위대가 셈에서 빠짐(본 프로그램에서 + 따로 계상 · 자재목록은 상세 보기라 따로 안 더함).""" + return mark_of(one, "별도계상") == "별도계상" + + +def drop_apart(got: dict[str, Any], shown: Any) -> dict[str, Any]: + """로직 줄 답(`run_line`)에서 별도계상 로직 자재 빼기(222-1) — 그 재료 줄 금액을 기준에서 빼고 + `줄`(일위대가 상세)에서 지움 · `재료`(자재목록 · 세 칸 고침)는 그대로. + ponytail: 「재료비의 %」 덧줄은 엔진 값 그대로(뺀 재료 몫 안 고침) — 그런 로직이 별도계상을 쓰면 엔진에 넘김.""" + shown = shown if isinstance(shown, dict) else {} + lines = got.get("줄") or [] + out = { + n + for m in got.get("재료") or [] + if (n := m["차례"]) < len(lines) and apart(shown.get(str(n))) + } + if not out or not got.get("기준"): + return got + base = {k: _number(v) or Decimal(0) for k, v in got["기준"].items()} + for n in out: + for k, v in (lines[n].get("비목") or {}).items(): + cut = _number(v) or Decimal(0) + base[k] = base.get(k, Decimal(0)) - cut + base["계"] -= cut + kept = [x for i, x in enumerate(lines) if i not in out] + return {**got, "기준": _json(base), "줄": kept} + + def _mat_marks(value: Any, logic: dict) -> dict[str, dict[str, str]] | None: """로직 자재 세 칸(148-4) `{호표 차례: {세 칸}}` — 맨 위 층 재료 줄 차례 · 허용 값만 · 빈 것 = None.""" @@ -386,6 +417,7 @@ def settle(result: dict, doc: dict, given: dict[str, Any]) -> dict[str, Any]: have = {str(k): v for k, raw in given.items() if (v := _number(raw)) is not None} hands = {line_id(one): one.get("수량") for one in doc.get("담은로직") or []} mhands = {one.get("코드"): one.get("수량") for one in doc.get("자재") or []} + aside = {one.get("코드") for one in doc.get("자재") or [] if apart(one)} # 222-1 합에서 뺌 zero = dict.fromkeys((*COSTS, "계"), Decimal(0)) lines, mats, bases, reflects = [], [], [], [] for line in result.get("줄") or []: @@ -399,9 +431,11 @@ def settle(result: dict, doc: dict, given: dict[str, Any]) -> dict[str, Any]: qty, source = _pick(mhands.get(one.get("코드")), one.get("알약"), have) price = _number(one.get("기준단가")) cost = price * qty if price is not None and qty is not None else None + mats.append({**one, "수량": qty, "수량출처": source, "반영": cost}) + if one.get("코드") in aside: + continue bases.append({**zero, "재료비": price, "계": price} if price is not None else None) reflects.append({**zero, "재료비": cost, "계": cost} if cost is not None else None) - mats.append({**one, "수량": qty, "수량출처": source, "반영": cost}) return _json({"줄": lines, "자재": mats, "기준합": _sum(bases), "반영합": _sum(reflects)}) @@ -468,7 +502,7 @@ def calc( "배": pill.get("배") or 1, } got = run_line(whole, code, one.get("입력값"), one.get("켜쌓기율"), one.get("고름")) - lines.append({**line, **got}) + lines.append({**line, **drop_apart(got, one.get("자재표시"))}) mats = [ { "코드": one["코드"], diff --git a/M02_MasterTemplete/M02_Labor_Inputs.py b/M02_MasterTemplete/M02_Labor_Inputs.py index c782955ac..204102bfe 100644 --- a/M02_MasterTemplete/M02_Labor_Inputs.py +++ b/M02_MasterTemplete/M02_Labor_Inputs.py @@ -8,6 +8,7 @@ from __future__ import annotations +import re from decimal import Decimal from typing import Any @@ -16,6 +17,7 @@ from M01_MasterData import M01_MasterData_Store_Combo as combo_store _SPEC = ("이름", "설명", "단위", "고르기", "범위") _LAYERED = "골쌓기 기준" # 85-2 — 표 주문에 이 글이 있으면 켜쌓기율 대상 로직 +_NUMBER = re.compile(r"(? Any: @@ -63,6 +65,26 @@ def _labels(whole: Any, spec: dict) -> dict[str, str]: return out +def sources(logic: dict) -> Any: + """원문 출처(222-1) — 로직 `출처` + 호표 · 입력 · 중간 · 덧줄 `출처` 가운데 같은 책(번호 앞 글이 + 같음)의 절 번호를 덧붙임(「산림품셈 12-1-2 · 12-1-3」 · 원문 패널이 절마다 찾음 · M01 + `make.source`) · 다른 책 절은 안 붙임 · 번호 없는 출처는 그대로.""" + main = str(logic.get("출처") or "") + head = _NUMBER.search(main) + if not head: + return logic.get("출처") + book, seen = main[: head.start()], set(_NUMBER.findall(main)) + extra = [] + for part in ("호표", "입력", "중간", "덧줄"): + for row in logic.get(part) or []: + src = str(row.get("출처") or "") if isinstance(row, dict) else "" + hit = _NUMBER.search(src) + if hit and src[: hit.start()] == book and hit.group() not in seen: + seen.add(hit.group()) + extra.append(hit.group()) + return " · ".join([main, *extra]) + + def _listed(logics: list[dict]) -> list[dict[str, Any]]: """로직 줄마다 `{로직, 이름, 결과단위, 출처, 켜쌓기, 입력: [{이름, 설명, 단위, 고르기?, 고르기이름?, 범위?, 자동값}]}`.""" whole = m01.loaded()[1] @@ -84,7 +106,7 @@ def _listed(logics: list[dict]) -> list[dict[str, Any]]: "로직": logic.get("키"), "이름": logic.get("이름"), "결과단위": logic.get("결과단위"), - "출처": logic.get("출처"), + "출처": sources(logic), "켜쌓기": logic.get("키") in layers, "입력": specs, } diff --git a/M02_MasterTemplete/M02_Labor_Ref.py b/M02_MasterTemplete/M02_Labor_Ref.py index bc06b9ac0..6e0a14bfb 100644 --- a/M02_MasterTemplete/M02_Labor_Ref.py +++ b/M02_MasterTemplete/M02_Labor_Ref.py @@ -423,6 +423,7 @@ class Expand: got = labor.run_line( self.whole, one["로직"], one.get("입력값"), one.get("켜쌓기율"), one.get("고름") ) + got = labor.drop_apart(got, one.get("자재표시")) # 별도계상 로직 자재 뺌(222-1) times = labor._number(pill.get("배")) or Decimal(1) base = got.get("기준") return { @@ -463,7 +464,7 @@ class Expand: rows.append({k: v * qty for k, v in sub["기준"].items()}) for mat in doc.get("자재") or []: # 사용자 자재(175-2) = 기준단가 × 손 · 빈칸 = 안 셈 qty = labor._number((mat.get("수량") or {}).get("손")) - if qty is None: + if qty is None or labor.apart(mat): # 별도계상 = 안 셈 · 안 줄에도 없음(222-1) continue got = labor.material_price(self.whole, mat["코드"], mat.get("단위")) price = labor._number(got["기준단가"]) diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Calc.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Calc.ts index 529f6bba4..cea35ce81 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Calc.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Calc.ts @@ -4,6 +4,7 @@ * 거울 시험 `resources/tester/test_m02_labor_calc_mirror.py`. * 기준(결과단위 그대로)은 서버 calc 길이 셈 · 여기는 받은 기준에 * 수량(손이 칸보다 앞섬 · 칸 = 알약 키 값) · 반영 = 기준 × 수량 ÷ 배 · 자재 = 재료비만. + * 별도계상 자재(222-1 · 빈 칸 = 기본 별도계상) = 반영은 보이되 합에 안 더함(짝 `apart`). * 참조 줄(단가승격 · 사전조합 · 50-3)은 `lineId` = 「참조|층|주인|키」(짝 `M02_Labor_Ref.ref_id`). */ import type { CalcLine as LogicLine } from "../M01_MasterData/M01_MasterData_UI_Logic_Api"; @@ -79,7 +80,7 @@ export interface LaborCalc { /** 수량만 보는 문서 조각 — 담은 로직 · 자재의 손 값 */ export interface LaborQtyDoc { 담은로직?: { 로직?: string; 참조?: LaborRef; 수량?: Qty }[]; - 자재?: { 코드: string; 수량?: Qty }[]; + 자재?: { 코드: string; 수량?: Qty; 별도계상?: string }[]; } function num(raw: unknown): number | null { @@ -130,6 +131,10 @@ export function settle( } const hands = new Map((doc.담은로직 ?? []).map((one) => [lineId(one), one.수량 ?? null])); const mhands = new Map((doc.자재 ?? []).map((one) => [one.코드, one.수량 ?? null])); + // 별도계상(222-1) — 「직접계산」 아니면(빈 · 옛 값 = 기본 별도계상) 합에서 뺌(짝 `M02_Labor_Doc.apart`) + const aside = new Set( + (doc.자재 ?? []).filter((m) => m.별도계상 !== "직접계산").map((m) => m.코드), + ); const base = zero(); const done = zero(); const add = (sum: Money, m: Money | null): void => { @@ -146,8 +151,8 @@ export function settle( const [qty, source] = pick(mhands.get(one.코드), one.알약, have); const price = num(one.기준단가); const cost = price !== null && qty !== null ? price * qty : null; - if (price !== null) add(base, { ...zero(), 재료비: price, 계: price }); - if (cost !== null) add(done, { ...zero(), 재료비: cost, 계: cost }); + if (price !== null && !aside.has(one.코드)) add(base, { ...zero(), 재료비: price, 계: price }); + if (cost !== null && !aside.has(one.코드)) add(done, { ...zero(), 재료비: cost, 계: cost }); return { ...one, 수량: qty, 수량출처: source, 반영: cost }; }); return { 줄, 자재, 기준합: base, 반영합: done }; diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts index 71a5e79ce..f0b65b1a6 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts @@ -383,14 +383,16 @@ export function mountLaborDetail( else delete cur.담은로직[i].승격; opt.onChanged(dirty()); } - // 자재 세 칸(148-4) — 화면 캐시만 · 금액 무관이라 다시 셈 없음 + // 자재 세 칸(148-4) — 화면 캐시만 · 별도계상만 금액에 닿음(222-1 · 사용자 자재 = 짝 셈 · 로직 자재 = 서버 기준) function markMat(i: number, key: MatMarkKey, v: string): void { setMark(cur.자재[i], key, v); - opt.onChanged(dirty()); + if (key === "별도계상") changed(true); + else opt.onChanged(dirty()); } function markLogicMat(i: number, at: number, key: MatMarkKey, v: string): void { setLogicMark(cur.담은로직[i], at, key, v); - opt.onChanged(dirty()); + if (key === "별도계상") changed(); + else opt.onChanged(dirty()); } function removeMat(i: number): void { cur.자재.splice(i, 1); diff --git a/resources/tester/test_175_precombo_mats.py b/resources/tester/test_175_precombo_mats.py index 9b98c4cb1..248695cfd 100644 --- a/resources/tester/test_175_precombo_mats.py +++ b/resources/tester/test_175_precombo_mats.py @@ -79,7 +79,8 @@ def _world_combo(world): # noqa: F811 {"로직": STONE, "수량": {"손": 1}}, {"로직": NET, "수량": {"손": 0}, "고름": {"4": NET_ITEM}}, ] - mats = [{"코드": MAT, "수량": {"손": 2}}, {"코드": WIRE, "수량": None}] + # MAT = 직접계산(222-1 · 기본 별도계상이면 셈에서 빠짐) + mats = [{"코드": MAT, "수량": {"손": 2}, "별도계상": "직접계산"}, {"코드": WIRE, "수량": None}] with store.at(world["system"]): key = refs.create("precombo", {"이름": "자재 조합", "담은로직": lines, "자재": mats})[ "이름" diff --git a/resources/tester/test_222_1_m02_apart.py b/resources/tester/test_222_1_m02_apart.py new file mode 100644 index 000000000..9d13b45f1 --- /dev/null +++ b/resources/tester/test_222_1_m02_apart.py @@ -0,0 +1,148 @@ +"""M02 구조물조합 일위대가 기계비빔타설(PLAN 222-1) — 단위 까닭 · 별도계상 빼기 · 원문 여럿. + +① 단위 — GF000156 모래 · 자갈 = 원문 kg(QF000353) ÷ 단위중량(㎏/㎥) · 칸에 t/㎥ 값(2 · 2.1)을 넣으면 + 838 ÷ 2 = 419 ㎥ 처럼 1000 배 · 0 = 표 위끝(1,700 · 1,800 ㎏/㎥) → 0.49 · 0.57 ㎥ · 칸을 비우면 + 자동값(master_auto 「나누는 자리 = 1」)이 1 이라 838 ㎥(자료 · 기본값 몫 — 223 보고). +② 별도계상 — 로직 자재(`자재표시` · 빈 칸 = 기본 별도계상) = 기준에서 빠지고 `줄` 에서 지움 · `재료` 는 + 남음 · 사용자 자재 = 합에서 뺌(짝 TS `settle`) · 사전조합 안 줄 · 사본 셈 같은 길. +③ 원문 — 로직 출처 + 호표 · 입력 출처 가운데 같은 책 절 번호(「산림품셈 12-1-2 · 12-1-3」). +""" + +import os +from decimal import Decimal + +import pytest + +from M01_MasterData import M01_MasterData_Store as m01 +from M01_MasterData import M01_MasterData_Store_Make as make +from M02_MasterTemplete import M02_Labor_Doc as labor +from M02_MasterTemplete import M02_Labor_Inputs as inputs +from M02_MasterTemplete import M02_Labor_Ref as refs +from resources.tester.test_m02_labor_calc_mirror import _same, _ts + +HERE = os.path.dirname(os.path.abspath(__file__)) +SRC = os.path.join(HERE, "..", "..", "M02_MasterTemplete") +MIX = "GF000156" # 기계비빔타설 — 호표 3 시멘트 · 4 모래 · 5 자갈 +GIVEN = {"재료배합": "(A)", "골재최대치수": 40} +MAT = "MT000008" +DIRECT = {"별도계상": "직접계산"} + + +def _whole(): + return m01.loaded()[1] + + +def _rows(got): + return {x["이름"]: {**x, "수량": float(x["수량"])} for x in got["줄"]} + + +# ── ① 단위 ────────────────────────────────────────────────────────── +def test_단위중량_칸에_t_값이면_1000배_0이면_표_위끝(): + wrong = labor.run_line(_whole(), MIX, {**GIVEN, "모래단위중량": 2, "자갈단위중량": 2.1}) + rows = _rows(wrong) + assert rows["시멘트"]["단위"] == "kg" and rows["시멘트"]["수량"] == pytest.approx(335) + assert rows["모래(건조상태)"]["수량"] == pytest.approx(838 / 2) # 419 ㎥ + assert rows["자갈 또는 부순돌"]["수량"] == pytest.approx(1032 / 2.1) # 491.4 ㎥ + zero = {**GIVEN, "모래단위중량": 0, "자갈단위중량": 0} + right = _rows(labor.run_line(_whole(), MIX, zero)) + assert right["모래(건조상태)"]["수량"] == pytest.approx(838 / 1700) + assert right["자갈 또는 부순돌"]["수량"] == pytest.approx(1032 / 1800) + + +# ── ② 별도계상 ─────────────────────────────────────────────────────── +def _calc(shown=None, mats=()): + line = {"로직": MIX, "입력값": GIVEN, "수량": {"손": 1}} + if shown: + line["자재표시"] = shown + doc = labor.clean({"열": "rv", "담은로직": [line], "자재": list(mats)}) + return labor.calc(doc, {}) + + +def test_로직_자재_기본_별도계상은_기준_줄에서_빠지고_재료엔_남음(): + raw = labor.run_line(_whole(), MIX, GIVEN) + cut = sum(Decimal(str(m["금액"])) for m in raw["재료"]) + one = _calc()["줄"][0] + names = {x["이름"] for x in one["줄"]} + assert not names & {"시멘트", "모래(건조상태)", "자갈 또는 부순돌"} + assert {"콘크리트공", "보통인부"} <= names + assert len(one["재료"]) == 3 # 자재목록(세 칸 고침)엔 남음 + want = Decimal(str(raw["기준"]["재료비"])) - cut + assert one["기준"]["재료비"] == pytest.approx(float(want)) + assert one["기준"]["계"] == pytest.approx(float(Decimal(str(raw["기준"]["계"])) - cut)) + assert one["기준"]["노무비"] == pytest.approx(float(raw["기준"]["노무비"])) + + +def test_직접계산은_그대로_섞이면_그_자재만_빠짐(): + raw = labor.run_line(_whole(), MIX, GIVEN) + full = _calc({str(n): DIRECT for n in (3, 4, 5)})["줄"][0] + assert full["기준"]["계"] == pytest.approx(float(raw["기준"]["계"])) + assert len(full["줄"]) == len(raw["줄"]) + part = _calc({"3": DIRECT})["줄"][0] # 시멘트만 셈 + names = {x["이름"] for x in part["줄"]} + assert "시멘트" in names and "모래(건조상태)" not in names + cement = next(m for m in raw["재료"] if m["이름"] == "시멘트") + others = sum(Decimal(str(m["금액"])) for m in raw["재료"]) - Decimal(str(cement["금액"])) + assert part["기준"]["계"] == pytest.approx(float(Decimal(str(raw["기준"]["계"])) - others)) + + +def test_사용자_자재_별도계상은_합에서_빠지고_반영은_보임(): + aside = _calc(None, [{"코드": MAT, "수량": {"손": 2}}]) + direct = _calc(None, [{"코드": MAT, "수량": {"손": 2}, **DIRECT}]) + price = direct["자재"][0]["기준단가"] + assert aside["자재"][0]["반영"] == pytest.approx(price * 2) # 목록엔 금액 보임 + assert direct["반영합"]["계"] - aside["반영합"]["계"] == pytest.approx(price * 2) + assert direct["기준합"]["재료비"] - aside["기준합"]["재료비"] == pytest.approx(price) + + +@pytest.mark.parametrize("mark", [None, "직접계산", "별도계상", "옛값"]) +def test_사용자_자재_별도계상_파이썬과_TS_같은_답(mark): + one = {"코드": MAT, "수량": {"손": 3}, **({"별도계상": mark} if mark else {})} + doc = labor.clean({"열": "rv", "담은로직": [{"로직": MIX, "입력값": GIVEN}], "자재": [one]}) + doc["자재"][0].update({"별도계상": mark} if mark else {}) # 옛 값도 그대로 넘겨 봄 + answer = labor.calc(doc, {}) + values = {"기계비빔타설|㎥": 2} + _same(labor.settle(answer, doc, values), _ts(answer, doc, values)) + + +def test_사전조합_안_줄도_별도계상_뺌(): + raw = labor.run_line(_whole(), MIX, GIVEN) + cut = sum(Decimal(str(m["금액"])) for m in raw["재료"]) + doc = { + "이름": "비빔 조합", + "단위": "㎥", + "담은로직": [{"로직": MIX, "입력값": GIVEN, "수량": {"손": 1}}], + "자재": [ + {"코드": MAT, "수량": {"손": 2}}, # 기본 별도계상 = 안 셈 · 안 줄에도 없음 + ], + } + ref = {"층": "system", "주인": None, "키": "SC999999"} + got = refs.Expand(_whole(), lambda r: doc)(ref) + assert [x.get("자재") for x in got["안"]] == [None] + pill = got["안"][0] + times = Decimal(1) # 기계비빔타설 알약 배 1 + assert pill["기준"]["계"] * times == pytest.approx(Decimal(str(raw["기준"]["계"])) - cut) + assert got["기준"]["계"] == pytest.approx(Decimal(str(raw["기준"]["계"])) - cut) + + +def test_화면_별도계상_바꾸면_다시_셈(): + detail = open(os.path.join(SRC, "M02_MasterTemplete_Labor_Detail.ts"), encoding="utf-8").read() + assert 'if (key === "별도계상") changed(true);' in detail # 사용자 자재 = 짝 셈 + assert 'if (key === "별도계상") changed();' in detail # 로직 자재 = 서버 기준 + + +# ── ③ 원문 여럿 ────────────────────────────────────────────────────── +def test_원문_출처는_같은_책_절_모두(): + src = inputs.sources(_whole().logic(MIX)) + assert src == "산림품셈 12-1-2 · 12-1-3" + assert inputs.logic_inputs([MIX])["로직"][0]["출처"] == src + assert [f["number"] for f in make.source(src)["found"]] == ["12-1-2", "12-1-3"] + + +def test_원문_출처_번호_없거나_더할_것_없으면_그대로(): + assert inputs.sources({"출처": "자체"}) == "자체" + assert inputs.sources({"출처": None}) is None + one = { + "출처": "산림품셈 3-1", + "호표": [{"출처": "건설품셈 공통 1-2-4 3"}, {"출처": "산림품셈 3-1"}], + } + assert inputs.sources(one) == "산림품셈 3-1" # 다른 책 · 같은 절은 안 붙임 diff --git a/resources/tester/test_item_copy_complete.py b/resources/tester/test_item_copy_complete.py index 693c02fe5..66f789fa8 100644 --- a/resources/tester/test_item_copy_complete.py +++ b/resources/tester/test_item_copy_complete.py @@ -184,7 +184,10 @@ def test_입력이_없으면_빠진_입력_목록_셈은_자동값(world): assert _stuck(got) == [] and got["기준합"]["계"] > 0 -def test_마스터_단가를_바꿔도_계산_그대로_새로_가져오면_바뀜(world): +def test_마스터_단가를_바꿔도_계산_그대로_새로_가져오면_바뀜(world, monkeypatch): + from M02_MasterTemplete import M02_Labor_Doc as labor_doc + + monkeypatch.setattr(labor_doc, "apart", lambda one: False) # M01 조합 셈과 대조(222-1 안 뺌) root, master_dir = world["root"], world["master"] _save(root, [_pipe()]) values = _fill_values(root) diff --git a/resources/tester/test_m02_item_labor.py b/resources/tester/test_m02_item_labor.py index eb480e157..76cf3c1ac 100644 --- a/resources/tester/test_m02_item_labor.py +++ b/resources/tester/test_m02_item_labor.py @@ -87,7 +87,7 @@ def world(tmp_path: Path, monkeypatch, master_copy: Path) -> dict[str, Any]: {"로직": LOGICS[1], "입력값": {name: value}, "수량": {"손": HAND}}, {"로직": LOGICS[2]}, ], - "자재": [{"코드": MATERIAL}], + "자재": [{"코드": MATERIAL, "별도계상": "직접계산"}], # 222-1 합에 셈 } ) _write(system / "labor" / f"{COLUMN}.json", doc)