"""719-2 공사 쪽 M02 단가산출(SK) — B09 대가 블록 · 산근 · [업데이트] · 내 자료집 SK(임시 storage · 실제 M01 읽기만). - 굳힌 SK = 품셈 꼴 대가 블록(`Labor_Source.sangeun`) · 금액 = M02 SK 셈 · 단가산출 속 일위대가 줄 = 사전조합 블록을 부르는 줄 · 로직 더한줄의 하위 로직 = 산근 목록(`Sangeun.rows_of`) - [업데이트] = SK 고친 뒤 다시 굳힘 — 굳힌 키 · cover · cid · 입력값 · 고른자재 · B09 덮개 그대로 · 덮개 밑 값(base) = 새 단가산출 값 · 서고 사본(`sangeun`)도 원본판 다르면 다시 굳힘 · 미리 보기 SK 줄 - 내 자료집 = SK 블록 · 품셈 블록 → 개인 층 SK(B09 덮개 = SK 덮개) → 서고로 다시 불러와 같은 금액 """ # ruff: noqa: F811 — 임시 층 fixture `world` 를 들여 씀 import json import pytest from B09_Estimation import B09_Estimation_Bill_Library as library from B09_Estimation import B09_Estimation_Daega_Personal as personal from B09_Estimation import B09_Estimation_Prices as prices from B09_Estimation import B09_Estimation_Refresh as refresh from B09_Estimation import B09_Estimation_Sangeun as sangeun from B09_Estimation.B09_Estimation_Calc_Base import compute_base from B09_Estimation.B09_Estimation_Calc_Labor import compute_labor from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs from B09_Estimation.B09_Estimation_Linked import linked_resource_keys from M02_MasterTemplete import M02_Item_Copy as item_copy from M02_MasterTemplete import M02_Item_Snapshot as snapshots from M02_MasterTemplete import M02_Item_Source as source from M02_MasterTemplete import M02_Labor_Ref as refs from M02_MasterTemplete import M02_MasterTemplete_Store as store from resources.tester.test_145_2_b09_labor_calc import STONE, _item, _project, _snap from resources.tester.test_715_sangeun_store import PCT, _calc, _sk from resources.tester.test_m02_labor_ref import MINE, world # noqa: F401 — 같은 임시 층 TWO_MATS = "GC000565" # 재료 줄 둘(고른자재 열쇠) COVER = {"labor:LB000036#0": {"qty": 0.5}} PRICE = {"labor:LB000002#0": {"labor": 200000}} OWNER = "7/42" def _json(value): return json.loads(json.dumps(value, ensure_ascii=False, default=str)) def _sc(world, lines): with store.at(world["personal"]): doc = {"이름": "속 일위대가", "단위": "㎡", "담은로직": lines} return refs.create("precombo", doc)["이름"] def _mine(world, full=False): """SK = 품 덮개 · 인력 더한줄 · 로직 더한줄(하위 산근) · full = 단가 덮개 + SC 더한줄.""" added = [ {"종류": "인력", "요소": "LB000002", "수량": 0.1}, {"종류": "로직", "로직": PCT, "입력값": {}, "수량": 0.5}, ] cover = COVER if full: sc = _sc(world, [{"로직": PCT, "수량": {"손": 1}}]) added.append({"참조": {**MINE, "키": sc}, "수량": 2}) # 단가산출 속 일위대가 cover = {**COVER, **PRICE} return _sk(world["personal"], 바탕={"로직": PCT}, 덮개=cover, 더한줄=added) def _engine_ready(world): """엔진 입력 덧칸(715-1b `단가` · `값묶음`)이 붙었나 — 아니면 M02 도 못 셈.""" key = _sk( world["personal"], 이름="엔진", 바탕={"로직": PCT}, 덮개={"labor:LB000002#0": {"labor": 1}} ) got = _calc(world["personal"], key) return not got.get("까닭") and got["줄"][1]["단가"] == 1 def _b09(root, cover=None): keys = linked_resource_keys(root) inputs = prices.build_inputs(None, keys) inputs["daega"] = daega_inputs(root) base = compute_base(inputs, {}) sg = sangeun.build(root, base) rows = {r["key"]: r for r in sg["sg_list"]} out = compute_labor(inputs["daega"], base, rows, cover) return inputs["daega"], {b["id"]: b for b in out["daega_blocks"]}, rows def _lib(world, tmp_path, key, kind="sangeun"): root = _project(tmp_path / "proj", []) made = library.import_copy(root, kind, "personal", OWNER, key) return root, made["폴더"] def _set_cover(world, key, cover): with store.at(world["personal"]): now = store.read("sangeun", key) store.write("sangeun", key, now["판"], {**now["문서"], "덮개": cover}) def _same_money(block, live): assert block["sum"] == pytest.approx(float(live["1단위"]["계"]), abs=5) # 끝수(내림)만 다름 for part, name in (("labor", "노무비"), ("material", "재료비"), ("expense", "경비")): assert block[part] == pytest.approx(float(live["1단위"][name]), abs=3) def test_SK_대가_블록_금액_M02_같음_하위_산근(world, tmp_path): key = _mine(world) live = _calc(world["personal"], key) assert not live.get("까닭") and live["1단위"]["계"] > 0 root, folder = _lib(world, tmp_path, key) daega, done, sg_rows = _b09(root) block = done[daega["libs"][folder]] assert block["cover"].startswith("P:") and block["kind"] == "품셈" lines = block["lines"] assert all(ln.get("sk") for ln in lines) assert lines[0]["kind"] == "labor" and lines[0]["qty"] == 0.5 assert lines[0]["poom"] == {"qty": 0.018, "단가": 233250} # M02 에서 고친 줄 = 품셈 값 곁 assert "poom" not in lines[1] sub = next(ln for ln in lines if ln["kind"] == "sangeun") assert sub["key"] in sg_rows and sub["key"].startswith(f"SG:{PCT}") and sub["qty"] == 0.5 _same_money(block, live) def test_SK_단가_덮개_속_일위대가_펼침(world, tmp_path): if not _engine_ready(world): pytest.skip("715-1b 엔진 입력 덧칸(단가 · 값묶음) 뒤 — 지금은 M02 SK 셈도 못 함") key = _mine(world, full=True) # 740-1 — 속 일위대가 = 넣을 때 펼쳐 담음(SC 로직 줄 = 로직 더한줄 · 본뜬) → 빠진 줄 없음 live = _calc(world["personal"], key) added = _added(world, key) assert live["빠진줄"] == [] and not any("참조" in x for x in added) assert added[-1]["로직"] == PCT and added[-1]["본뜬"]["키"].startswith("SC-") root, folder = _lib(world, tmp_path, key) daega, done, _ = _b09(root) block = done[daega["libs"][folder]] lines = block["lines"] assert lines[1]["kind"] == "fixed" and lines[1]["prices"]["labor"] == pytest.approx(200000) assert block["blank"] == 0 _same_money(block, live) def _added(world, key): with store.at(world["personal"]): return store.read("sangeun", key)["문서"]["더한줄"] def test_구조물_사본_SK_줄_업데이트_덮개_입력값_고른자재_굳힌키_그대로( world, tmp_path, monkeypatch ): key = _mine(world) ref = {**MINE, "키": key} with store.at(world["personal"]): from M02_MasterTemplete import M02_Labor_Doc as labor doc = labor.clean( { "열": "rv", "담은로직": [ {"참조": ref, "수량": {"손": 2}}, {"로직": "GF000219", "입력값": {}, "수량": {"손": 1}}, {"로직": TWO_MATS, "수량": {"손": 1}}, ], } ) def build(_item_): # 원본 층 → 사본 파일(구조물 원본 대신 이 일위대가 문서) rows = source.labor_doc_rows(doc) return _json( { source.LABOR_FILE: rows, source.PRICE_FILE: source.price_bundle(rows, world["personal"]), snapshots.VALUES_FILE: {}, } ) monkeypatch.setattr(item_copy, "build_snapshot", build) root = _project(tmp_path / "proj", [(100, "SS000901")]) folder = snapshots.structures_dir(root) / snapshots.folder_name("pipe@100.0") folder.mkdir(parents=True) values = {"GF000219": {k: {"값": v} for k, v in STONE.items()}} picks = {TWO_MATS: {"1": "MT000001"}} for name, data in { **build(None), snapshots.VALUES_FILE: values, snapshots.PICKS_FILE: picks, snapshots.MANIFEST_NAME: {"원본": {**_item("SS000901"), "경로": ["x"]}}, }.items(): (folder / name).write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8") rid = refs.ref_id(ref) frozen_key = json.loads((folder / source.LABOR_FILE).read_text("utf-8"))["참조사본"][rid] frozen_key = frozen_key["바탕"]["로직"] daega, _, _ = _b09(root) combo = next(b for b in daega["blocks"] if b["id"] == daega["owners"]["pipe@100.0"]) sk_block = next(b for b in daega["blocks"] if b["id"] == combo["lines"][0]["key"]) cid = sk_block["lines"][0]["cid"] cover = {sk_block["cover"]: {cid: {"qty": 0.9}}} # SK 고침(품 0.5 → 0.7) → [재적용](업데이트) = 다시 풀어 굳힘 _set_cover(world, key, {**COVER, "labor:LB000036#0": {"qty": 0.7}}) item_copy.reapply(root, folder.name) after = json.loads((folder / source.LABOR_FILE).read_text("utf-8"))["참조사본"][rid] assert after["바탕"]["로직"] == frozen_key # 굳힌 키 그대로 assert ( json.loads((folder / snapshots.VALUES_FILE).read_text("utf-8"))["GF000219"] == (values["GF000219"]) ) assert json.loads((folder / snapshots.PICKS_FILE).read_text("utf-8")) == picks daega2, done, _ = _b09(root, cover) block = next(b for b in done.values() if b["cover"] == sk_block["cover"]) line = block["lines"][0] assert line["cid"] == cid and line["covered"] == ["qty"] and line["qty"] == 0.9 assert line["base"]["qty"] == 0.7 # 덮개 밑 = 새 단가산출 값(「단가산출 값으로」 되돌림 값) assert daega2["owners"]["pipe@100.0"] in done def test_서고_SK_업데이트_미리보기_다시_굳힘(world, tmp_path): key = _mine(world) root, folder = _lib(world, tmp_path, key) assert library.changed(root) == [] before = daega_inputs(root) cover_key = next(b for b in before["blocks"] if b["id"] == before["libs"][folder])["cover"] _set_cover(world, key, {**COVER, "labor:LB000036#0": {"qty": 0.7}}) got = refresh.preview(root, None, [], None) assert [(x["키"], x["판"]) for x in got["단가산출"]] == [(key, "바뀜")] assert [c["폴더"] for c in got["사본"]] == [folder] assert refresh.reapply_changed(root)["다시"] == ["내 단가산출"] assert library.changed(root) == [] and [f.name for f, _ in library.copies(root)] == [folder] after = daega_inputs(root) block = next(b for b in after["blocks"] if b["id"] == after["libs"][folder]) assert block["cover"] == cover_key and block["lines"][0]["qty"] == 0.7 def test_내_자료집_SK_왕복_같은_금액(world, tmp_path): key = _mine(world) root, folder = _lib(world, tmp_path, key) daega, _, _ = _b09(root) block = next(b for b in daega["blocks"] if b["id"] == daega["libs"][folder]) first, second = block["lines"][0]["cid"], block["lines"][1]["cid"] priced = {block["cover"]: {first: {"qty": 0.9}, second: {"labor": 210000}}} made = personal.build(root, block["cover"], priced, "내 SK 2") doc = made["문서"] assert doc["종류"] == "단가산출" and doc["바탕"] == {"로직": PCT} assert doc["덮개"]["labor:LB000036#0"] == {"qty": 0.9} assert doc["덮개"]["labor:LB000002#0"] == {"labor": 210000} assert doc["더한줄"][0]["요소"] == "LB000002" assert made["담음"] == {"품": 1, "단가": 1} and made["안담음"] == {"품": 0, "단가": 0} third = block["lines"][2]["cid"] # 더한줄 인력 = 그 더한줄 수량 cover = {block["cover"]: {first: {"qty": 0.9}, third: {"qty": 0.3}}} doc = personal.build(root, block["cover"], cover, "내 SK 2")["문서"] assert doc["더한줄"][0]["수량"] == 0.3 and doc["덮개"] == {"labor:LB000036#0": {"qty": 0.9}} _, done, _ = _b09(root, cover) want = done[daega["libs"][folder]]["sum"] with store.at(world["personal"]): new = personal.write(doc)["이름"] with pytest.raises(store.StoreError) as same: personal.write(doc) assert same.value.status == 409 and same.value.detail["같은이름"] == new assert new.startswith("SK-") and new != key root2, folder2 = _lib(world, tmp_path / "b", new) daega2, done2, _ = _b09(root2) assert done2[daega2["libs"][folder2]]["sum"] == pytest.approx(want, abs=2) def test_내_자료집_품셈_블록_SK(world, tmp_path): root = _project(tmp_path / "proj", [(100, "SS000901")]) _snap(root, "pipe@100.0", "SS000901", [{"로직": PCT, "승격": True, "수량": {"손": 1}}], {}) daega = daega_inputs(root) top = next(b for b in daega["blocks"] if b["id"] == daega["owners"]["pipe@100.0"]) block = next(b for b in daega["blocks"] if b["id"] == top["lines"][0]["key"]) assert block["kind"] == "품셈" and block["origin"]["로직"] == PCT cover = {block["cover"]: {block["lines"][0]["cid"]: {"qty": 0.5}}} doc = personal.build(root, block["cover"], cover, "내 해체")["문서"] assert doc["바탕"] == {"로직": PCT} and doc["단위"] == "㎡" assert doc["덮개"] == {"labor:LB000036#0": {"qty": 0.5}} with store.at(world["personal"]): new = personal.write(doc)["이름"] plain = _sk(world["personal"], 이름="견줌", 바탕={"로직": PCT}, 덮개=doc["덮개"]) a, b = _calc(world["personal"], new), _calc(world["personal"], plain) assert a["1단위"]["계"] == b["1단위"]["계"] > 0