# ruff: noqa: E501 """흐름 로직이 일위대가 조합 · 프로젝트 길에서도 그대로(PLAN 36 · 사용자 원칙 「조합은 로직을 가져가 더하기만」 · 사본 폴더). 못박는 것 — 조합에 담은 로직을 모두 흐름으로 저장(계산 칸 지움 · 「입력」 은 흐름에서 같은 꼴로)한 뒤 - 로직마다 입력 목록(이름 · 단위 · 고르기 · 범위) = 바꾸기 전 - 조합 셈(`master_combo.run`) 로직별 결과 · 비목 합 = 바꾸기 전 - 프로젝트 단가 묶음(`M02_Item_Prices.collect`)만으로 셈 = 바꾸기 전(흐름이 쓰는 요소 · 표 · 다른 로직이 담김) - 빈 값으로 준 입력은 「빈 입력」 알림(예시값으로 안 넘어감) · 안 준 입력은 멈춤 """ from __future__ import annotations import shutil from decimal import Decimal from pathlib import Path import pytest from M01_MasterData import M01_MasterData_Store as store from M01_MasterData import M01_MasterData_Store_Flow as sflow from M02_MasterTemplete import M02_Item_Prices as prices from M02_MasterTemplete.DataFlow import M02_DF_Convert as cv REAL = store.FOLDER KEYS = ("GF000185", "GF000219") # 레미콘 검색어 · 펌프차 기계 · 인력 / 표 찾기 · 인력 COMBO = { "키": "UA999999", "이름": "시험 조합", "담은로직": [{"로직": k, "메모": None} for k in KEYS], } def _specs(row: dict) -> list: return [ (s["이름"], s.get("단위"), s.get("고르기"), s.get("범위")) for s in row.get("입력") or [] ] def _round(v): return round(Decimal(str(v)), 6) if v is not None else None def _combo(folder: Path) -> tuple[list, dict]: mf, mcb = store.cm.mf, store.cm.mcb given = {k: dict(store.cm.시험입력(store.logic(k)["logic"])) for k in KEYS} got = mcb.run(mf.Master(store.cm.load(folder=folder)), COMBO, given) lines = [ (x["로직"], x["ok"], {k: _round(x.get(k)) for k in ("노무비", "재료비", "경비", "계")}) for x in got["줄"] ] return lines, {k: _round(got.get(k)) for k in ("노무비", "재료비", "경비", "계")} def _bundle_sums() -> dict: mf = store.cm.mf snapshot = prices.load_prices(prices.to_json(prices.collect(list(KEYS)))) out = {} for k in KEYS: got = mf.run(snapshot, k, dict(store.cm.시험입력(store.logic(k)["logic"]))) out[k] = {n: _round(got[n]) for n in ("노무비", "재료비", "경비", "계")} return out def test_조합에_담은_로직을_흐름으로_바꿔도_같음(tmp_path: Path, monkeypatch) -> None: for path in REAL.glob("*.json"): shutil.copy(path, tmp_path / path.name) monkeypatch.setattr(store, "FOLDER", tmp_path) specs = {k: _specs(store.logic(k)["logic"]) for k in KEYS} lines, sums = _combo(tmp_path) assert all(ok for _, ok, _ in lines) and sums["계"] > 0 bundle = _bundle_sums() for key in KEYS: got = store.logic(key) flow = cv.convert(got["logic"], {"key": key, "master": store.loaded()[1]})["flow"] flow.pop("key", None) change = {"op": "edit", "key": key, "row": {**got["logic"], "흐름": flow}} sflow.save( [store._dec({"file": got["file"], "version": got["version"], "changes": [change]})] ) for key in KEYS: row = store.logic(key)["logic"] assert not set(row) & set(store.cm.mf.CALC_FIELDS) and row["흐름"] # 계산 칸만 지움 assert _specs(row) == specs[key] # 입력 약속 그대로 assert _combo(tmp_path) == (lines, sums) assert "_빠진" not in prices.collect(list(KEYS)) assert _bundle_sums() == bundle mf = store.cm.mf whole = mf.Master(store.cm.load(folder=tmp_path)) given = dict(store.cm.시험입력(store.logic(KEYS[0])["logic"])) first = next(iter(given)) blank = mf.run( whole, KEYS[0], {**given, first: None} ) # 빈 값으로 준 입력 = 그 값을 쓰는 곳만 비움 assert any(f"「{first}」 빈 입력" in n for n in blank.get("알림") or []) with pytest.raises(mf.FormulaError, match="없음"): # 안 준 입력 = 멈춤(엔진과 같음) mf.run(whole, KEYS[0], {k: v for k, v in given.items() if k != first})