- 723-1 「안 줄 수량 null」 걷음 — SC 줄 · 자재 수량 {손} 저장(새 줄 1 · 빈 칸 = 「수량 입력」 · 금액 빠짐)
- 셈 차례: 층 = 알약 값 → 저장 수량 · 공사 굳힌 사본 = 굳힌 손 → 알약 → 저장(with_pills 가 {저장} 칸으로 옮김)
- 불러오기로 만든 일위대가 = 엑셀 수량을 저장 수량으로(본뜬.실무수량 같은 값 남김)
- 단가산출 안 일위대가 = 저장 수량으로 금액(수량 없음 없어짐) · B08 자재 · B09 내 자료집 저장도 같은 차례
- 일위대가표 수량 칸 = 문서에 저장(휘발 임시 칸 걷음) · 합계 열 「기준단위 단가」 · 「수량 반영 금액」
- 낡은 시험(723 · 175 · 204-2 · 702-3 · 703 · 717-4 · 719-1 · 719-2 · hopyo build · flow · labor ref)의 null 단언을
저장 수량 단언으로 고침 — 723-1 결정이 742-1(요구 584-1)로 바뀜 · 새 지킴 시험 test_742_1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
187 lines
9.5 KiB
Python
187 lines
9.5 KiB
Python
"""M02 일위대가 사전조합 자재목록(PLAN 175) — 구조물 일위대가와 같은 자재목록.
|
|
|
|
175-1 화면 · 저장 — 서고 모드 자재목록 · 로직 자재 세 칸 막힘 풂 · 저장 `_combo_lines` 가
|
|
고름 · 자재표시 남김 · 문서 `자재`(일위대가 자재 검사 · 로직 자재와 같은 품목 400).
|
|
175-2 셈 — 담은 자재(수량 = 이은 알약 값 · 723-1 · 손은 저장 안 함)가 사전조합 1 단위 값(`Expand._combo` · 안 줄) · B08 참조 줄
|
|
재료(`_ref_mats`) · B09 겹 대가 자재 줄 · 프로젝트 사본 단가 묶음(`frozen_materials`)에 들어감.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from decimal import Decimal
|
|
|
|
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_Ref as refs
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from resources.tester.test_m02_labor_ref import _area, world # noqa: F401 — 임시 층 fixture
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
SRC = os.path.join(HERE, "..", "..", "M02_MasterTemplete")
|
|
NET = "GC000337" # 낙석방지망 — 재료 줄 3 · 4 · 5(철망 · 결속선 · 에폭시)
|
|
NET_ITEM = "MT006697"
|
|
WIRE = "MT000194"
|
|
MAT = "MT000008" # 자재품목 · 물가정보 83000
|
|
STONE = "GF000219" # 원/㎡
|
|
SYS = {"층": "system", "주인": None}
|
|
|
|
|
|
def _save(folder, doc):
|
|
with store.at(folder):
|
|
key = refs.create("precombo", {"이름": "자재 조합", "단위": "㎡", **doc})["이름"]
|
|
return key, json.loads((folder / "precombo" / f"{key}.json").read_bytes())
|
|
|
|
|
|
def test_화면_서고_모드도_자재목록_세_칸_자재_저장(world): # noqa: F811
|
|
"""723-2 — 서고 = 일위대가표만(`Labor_Table`) · [항목 추가] 머리(736-1 · 자재 추가 단추 걷음) · 세 칸 = 줄 모달(`Labor_RowModal`)."""
|
|
read = lambda name: open(os.path.join(SRC, name), encoding="utf-8").read() # noqa: E731
|
|
detail, lib = (
|
|
read("M02_MasterTemplete_Labor_Detail.ts"),
|
|
read("M02_MasterTemplete_Labor_Table.ts"),
|
|
)
|
|
modal = read("M02_MasterTemplete_Labor_RowModal.ts")
|
|
assert 'if (opt.mode === "precombo") return mountLaborTable(host, doc, opt);' in detail
|
|
assert 'className: "ui-btn-group", children: [addLogic]' in lib and "addMat" not in lib
|
|
assert "setLogicMark(row, at, key, v);" in modal and "setMark(o.mat, key, v);" in modal
|
|
assert "delete out.자재" not in lib and "delete out.열" in lib
|
|
|
|
|
|
def test_저장_왕복_고름_자재표시_자재_남음(world): # noqa: F811
|
|
line = {"로직": NET, "고름": {"4": NET_ITEM}, "자재표시": {"3": {"자재구분": "관급"}}}
|
|
mats = [
|
|
{"코드": MAT, "수량": {"손": 2}, "운반비": "미포함", "이름": "틀린 이름"},
|
|
{"코드": WIRE, "수량": None},
|
|
]
|
|
_, got = _save(world["system"], {"담은로직": [line], "자재": mats})
|
|
one = got["담은로직"][0]
|
|
assert one["고름"] == {"4": NET_ITEM} and one["자재표시"] == {"3": {"자재구분": "관급"}}
|
|
assert [(m["코드"], m["수량"]) for m in got["자재"]] == [
|
|
(MAT, {"손": 2}),
|
|
(WIRE, None),
|
|
] # 742-1
|
|
assert got["자재"][0]["운반비"] == "미포함" and got["자재"][0]["이름"] != "틀린 이름"
|
|
# 자재 없음 = 빈 목록(옛 문서 꼴 그대로 읽힘)
|
|
_, empty = _save(world["system"], {"담은로직": [{"로직": NET}]})
|
|
assert empty["자재"] == []
|
|
|
|
|
|
def test_로직_자재와_같은_품목_400_없는_자재_400(world): # noqa: F811
|
|
with pytest.raises(store.StoreError) as e:
|
|
_save(world["system"], {"담은로직": [{"로직": NET}], "자재": [{"코드": WIRE}]})
|
|
assert e.value.status == 400 and WIRE in str(e.value.detail)
|
|
with pytest.raises(store.StoreError) as e:
|
|
_save(world["system"], {"담은로직": [], "자재": [{"코드": "MT999999"}]})
|
|
assert e.value.status == 400
|
|
|
|
|
|
# ── 175-2 셈 ──────────────────────────────────────────────────────────
|
|
def _price(code):
|
|
whole = m01.loaded()[1]
|
|
return Decimal(str(labor.material_price(whole, code, None)["기준단가"]))
|
|
|
|
|
|
def _given(key):
|
|
"""안 줄 알약 값(723-1) — STONE 1 · NET 0 · MAT 2 · WIRE 0."""
|
|
rid = refs.ref_id({**SYS, "키": key})
|
|
return {f"{rid}#0": 1, f"{rid}#1": 0, f"{rid}#자재|{MAT}": 2, f"{rid}#자재|{WIRE}": 0}
|
|
|
|
|
|
def _legacy(world, key): # noqa: F811
|
|
"""옛 손 수량 문서(723-1 앞 · 굳힌 사본 꼴) — B08 · B09 셈 길 시험용 · 파일은 안 바꿈."""
|
|
doc = json.loads((world["system"] / "precombo" / f"{key}.json").read_bytes())
|
|
hands = ({"손": 1}, {"손": 0})
|
|
lines = [{**one, "수량": q} for one, q in zip(doc["담은로직"], hands)]
|
|
mats = [{**m, "수량": {"손": 2} if m["코드"] == MAT else None} for m in doc["자재"]]
|
|
return {**doc, "담은로직": lines, "자재": mats}
|
|
|
|
|
|
def _world_combo(world): # noqa: F811
|
|
"""STONE 1 + MAT 2 + WIRE 0 · NET 고름 4 = NET_ITEM — (키, 1 단위 값) · 수량은 `_given`."""
|
|
lines = [
|
|
{"로직": STONE, "수량": {"손": 1}},
|
|
{"로직": NET, "수량": {"손": 0}, "고름": {"4": NET_ITEM}},
|
|
]
|
|
# MAT = 직접계산(222-1 · 기본 별도계상이면 셈에서 빠짐)
|
|
mats = [{"코드": MAT, "수량": {"손": 2}, "별도계상": "직접계산"}, {"코드": WIRE, "수량": None}]
|
|
with store.at(world["system"]):
|
|
key = refs.create(
|
|
"precombo", {"이름": "자재 조합", "단위": "㎡", "담은로직": lines, "자재": mats}
|
|
)["이름"]
|
|
stone = Decimal(str(labor.run_line(m01.loaded()[1], STONE, {})["기준"]["계"]))
|
|
return key, stone + _price(MAT) * 2
|
|
|
|
|
|
def test_사전조합_값에_자재_금액_빈칸은_안_셈_안_줄에_자재(world): # noqa: F811
|
|
key, want = _world_combo(world)
|
|
with store.at(world["system"]):
|
|
doc = labor.clean(
|
|
{"열": "rv", "담은로직": [{"참조": {**SYS, "키": key}, "수량": {"손": 3}}]}
|
|
)
|
|
bare = labor.calc(doc, {})["줄"][0]
|
|
one = labor.calc(doc, _given(key))["줄"][0]
|
|
assert bare["기준"]["계"] == pytest.approx(float(want)) # 742-1 — 안 이으면 저장 수량
|
|
assert one["기준"]["계"] == pytest.approx(float(want))
|
|
assert one["반영"]["계"] == pytest.approx(float(want * 3))
|
|
mats = [x for x in one["안"] if x.get("자재")]
|
|
assert [(x["자재"], x["수량"]) for x in mats] == [(MAT, 2)] # WIRE = 기본 별도계상(안 셈)
|
|
assert mats[0]["기준"]["재료비"] == pytest.approx(float(_price(MAT)))
|
|
# 목록 기준금액(51-4) — 저장 수량으로(742-1)
|
|
rows = [{"종류": "precombo", "이름": key}]
|
|
refs.summaries(world["system"], rows)
|
|
assert rows[0]["기준금액"] == pytest.approx(float(want))
|
|
|
|
|
|
def test_B08_참조_줄_재료에_사전조합_자재_손_곱(world): # noqa: F811
|
|
from B08_Quantity.B08_Quantity_Router_Material import _ref_mats
|
|
|
|
key, _ = _world_combo(world)
|
|
outer = {
|
|
"이름": "겹",
|
|
"단위": "㎡",
|
|
"담은로직": [{"참조": {**SYS, "키": key}, "수량": {"손": 3}}],
|
|
}
|
|
docs = {"SC-겹": outer, key: _legacy(world, key)} # 옛 손 문서(723-1 앞 꼴 · B08 길만 잼)
|
|
got = _ref_mats(m01.loaded()[1], lambda r: docs.get(r["키"]), {**SYS, "키": "SC-겹"})
|
|
mats = [(m["품목"]["키"], q) for m, q in got if m.get("자재")]
|
|
assert mats == [(MAT, pytest.approx(6.0))] # 2 × 겹 손 3 · WIRE 빈칸 없음
|
|
# 742-1 — 층 문서 = 저장 수량 · 면적 알약 값(given)도 같음
|
|
live = {"SC-겹": outer, key: refs.read_ref({**SYS, "키": key})}
|
|
bare = _ref_mats(m01.loaded()[1], lambda r: live.get(r["키"]), {**SYS, "키": "SC-겹"})
|
|
assert [(m["품목"]["키"], q) for m, q in bare if m.get("자재")] == [(MAT, pytest.approx(6.0))]
|
|
got = _ref_mats(
|
|
m01.loaded()[1], lambda r: live.get(r["키"]), {**SYS, "키": "SC-겹"}, given=_given(key)
|
|
)
|
|
mats = [(m["품목"]["키"], q) for m, q in got if m.get("자재")]
|
|
assert mats == [(MAT, pytest.approx(6.0))]
|
|
|
|
|
|
def test_B09_겹_대가에_자재_줄(world): # noqa: F811
|
|
from B09_Estimation.B09_Estimation_Labor_Source import Builder
|
|
|
|
key, _ = _world_combo(world)
|
|
ref = {**SYS, "키": key}
|
|
build = Builder()
|
|
bid = build.precombo(m01.loaded()[1], ref, _legacy(world, key), {}) # 옛 손 문서(723-1 앞)
|
|
mats = [x for x in build.blocks[bid]["lines"] if x["kind"] == "material"]
|
|
assert [(x["key"], x["qty"]) for x in mats] == [(MAT, 2.0), (WIRE, None)]
|
|
assert mats[0]["name"] and mats[0]["unit"]
|
|
|
|
|
|
def test_사본_단가_묶음에_사전조합_자재_고름_사본_값_같음(world): # noqa: F811
|
|
from M02_MasterTemplete import M02_Item_Prices as prices
|
|
from M02_MasterTemplete import M02_Item_Source as source
|
|
|
|
key, want = _world_combo(world)
|
|
doc = {"열": "rv", "담은로직": [{"참조": {**SYS, "키": key}, "수량": {"손": 1}}], "자재": []}
|
|
with store.at(world["system"]):
|
|
rows = source.labor_doc_rows(labor.clean(doc))
|
|
mats = refs.frozen_materials(rows["참조사본"])
|
|
assert {MAT, WIRE, NET_ITEM} <= set(mats)
|
|
assert "refs.frozen_materials(" in open(source.__file__, encoding="utf-8").read()
|
|
bundle = prices.to_json(prices.collect(refs.frozen_logics(rows["참조사본"]), mats))
|
|
(world["system"] / "precombo" / f"{key}.json").unlink() # 원본이 사라져도 굳힌 사본
|
|
got = prices.calc(rows, bundle, {}, {}, _area(_given(key)))["줄"][0] # 사본 면적 칸 = 안 줄 값
|
|
assert got["기준"]["계"] == pytest.approx(float(want), rel=1e-9)
|