diff --git a/B09_Estimation/B09_Estimation_Bill_Hopyo.py b/B09_Estimation/B09_Estimation_Bill_Hopyo.py index 66fda419b..e3f32b602 100644 --- a/B09_Estimation/B09_Estimation_Bill_Hopyo.py +++ b/B09_Estimation/B09_Estimation_Bill_Hopyo.py @@ -141,7 +141,9 @@ def _copies(docs: dict[int, dict[str, Any]], tag: str, no: int) -> dict[str, dic rid = refs.ref_id(_ref(tag, at)) if rid in out or at not in docs: continue - out[rid] = refs.with_pills(docs[at]) + # 이 공사 엑셀 수량 = 굳힌 손(723-4 · `with_pills` 의 723-1 수량 걷기는 M02 저장용) + doc = docs[at] + out[rid] = {**refs.with_pills(doc), **{k: doc.get(k) or [] for k in ("담은로직", "자재")}} todo += [ int(one["참조"]["키"][3:]) for one in docs[at].get("담은로직") or [] diff --git a/B09_Estimation/B09_Estimation_Daega_Personal.py b/B09_Estimation/B09_Estimation_Daega_Personal.py index d35d2a962..36005a843 100644 --- a/B09_Estimation/B09_Estimation_Daega_Personal.py +++ b/B09_Estimation/B09_Estimation_Daega_Personal.py @@ -1,7 +1,8 @@ """B09 「내 자료집에 저장」(PLAN 703-2) — 펼친 일위대가(대가 블록) → 개인 층 사전조합(SC). 담는 것 = 담은로직(넣을 값 · 고른 자재 얹음) · 자재 · 줄 수량(= 수량산출 수기 `손`) — - 맨 위 줄 수량 덮개 = 손(원래 손 × 덮개 ÷ 블록 줄 수량 · 대가 줄 ÷ 배를 되돌림) + 맨 위 줄 수량 덮개 = 손(원래 손 × 덮개 ÷ 블록 줄 수량 · 대가 줄 ÷ 배를 되돌림) · + SC 쓸 때 손 걷힘(723-1 `refs.no_qty` · 쓰는 곳 알약 · 이 공사 입력으로 이음) 안 담는 것 = 품 덮개(부른 블록 안 줄 · 품셈 블록 줄 — 로직 통째 배수로 바꾸면 다른 줄까지 바뀜) · 단가 덮개(사전조합 꼴에 단가 칸 없음) → 수만 알림(품은 PLAN 705 고정 줄이 생기면 담김) 원천 = 블록 cover — 조합 `S:`/`L:` = 사본 폴더(일위대가 · 넣을 값 · 고른 자재 · 셈 수량) · @@ -107,7 +108,8 @@ def source(root: Path, block: dict[str, Any], base: str) -> tuple[list[dict], li doc = ((read_json(folder / LABOR_FILE) or {}).get("참조사본") or {}).get(refs.ref_id(ref)) if isinstance(doc, dict): return _precombo(doc) - if head == "C" and (doc := _frozen(root, rest)) is not None: + rid = (block.get("origin") or {}).get("참조") or rest # 덮개 `C:참조|쓰는 곳`(723-4) = 참조만 + if head == "C" and (doc := _frozen(root, rid)) is not None: return _precombo(doc) raise LookupError(f"일위대가 원천을 못 찾음 「{base}」") diff --git a/B09_Estimation/B09_Estimation_Labor_Source.py b/B09_Estimation/B09_Estimation_Labor_Source.py index 5dd10e815..5d0f43c11 100644 --- a/B09_Estimation/B09_Estimation_Labor_Source.py +++ b/B09_Estimation/B09_Estimation_Labor_Source.py @@ -7,8 +7,8 @@ 사전조합 참조 SC = 겹 대가(굳힌 `참조사본`) · 줄 = 그 안 로직 · 참조 줄 · 자재 줄(175-2) · 안 줄 수량(723-4) = 굳힌 옛 손 → 쓰는 구조물 사본 면적 알약 값(723-1 「참조 id#차례」 · 「참조 id#자재|코드」) → 없음 = 빈 줄(`ask` · 이 공사에서 입력 = - 702 덮개 qty) · - 알약 · 없음 쓴 블록 덮개 = `C:참조|S:사본` · `C:참조|L:서고`(옛 손뿐 = `C:참조`) + 702 덮개 qty) · 알약 값 쓴 블록 덮개 = `C:참조|S:사본`(사본마다 값 다름) · + 그 밖 = `C:참조`(공사 안 한 덮개) 품셈 일위대가화(승격) 줄 · 옛 참조 SP = 로직마다 따로(노무 · 재료 · 중기 · 산근 · % 줄) — 조합이 수량만큼 부름 · 기계 = 기계경비 로직 줄 · 다른 돈 로직 줄 = 하위 산근 산근 단가산출 줄(일위대가화 아닌 로직 줄) = 산근 호표 참조(키 = 145-1 `sangeun_key`) @@ -173,11 +173,11 @@ class Builder: 단가산출 속 일위대가 · SK 줄(참조) = 그 대가 블록을 부르는 줄(펼침 · B09 차례 그대로) · 고정 줄 = `fixed` · 단가 고친 줄(덮개 · 더한줄 단가 · 값묶음) = `fixed`(줄 단가 = M02 셈) · cover = `P:` + SK 이름표(SK 를 고쳐 [업데이트] 해도 B09 덮개 그대로) · + 속 일위대가 안 줄 수량 없음(723-4) = 엔진엔 0 값묶음 · 그 줄 = 사전조합 블록(이 공사 입력) · 줄 `sk` · `poom`(M02 에서 고친 줄의 품셈 값 = 원본 덮개 `원값`).""" from M02_MasterTemplete import M02_Item_Sangeun as item_sk from M02_MasterTemplete import M02_Sangeun_Calc as calc from M02_MasterTemplete import M02_Sangeun_Doc as sd - from M02_MasterTemplete.M02_Labor_Expand import Expand rid = refs.ref_id(ref) key = str((doc.get("바탕") or {}).get("로직") or "") @@ -190,7 +190,7 @@ class Builder: try: got = calc.resolve(doc, whole, read, (rid,)) row = {**got["줄"], "키": key, "호표": [dict(x) for x in got["줄"]["호표"]]} - if calc._bundles(row, whole, Expand(whole, read), (rid,)): + if calc._bundles(row, whole, _Loose(whole, read), (rid,)): return None except (sd.DocError, mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueError): return None @@ -334,15 +334,15 @@ class Builder: use: tuple[dict, str] = ({}, ""), ) -> str: """사전조합(SC) = 겹 대가 — 로직 줄 = 품셈 호표 · 자재 줄(175-2) · 줄 수량(723-4) = - 옛 손 → use 알약 값 → 없음(`ask`) · use = (쓰는 구조물 사본 면적 알약 값, 덮개 자리).""" + 굳힌 손 → use 알약 값 → 없음(`ask`) · use = (쓰는 구조물 사본 면적 알약 값, 덮개 자리).""" from M01_MasterData import M01_MasterData_Store_Combo as combo_store rid, (given, where) = refs.ref_id(ref), use - loose: list[bool] = [] # 줄마다 손 아닌 수량(알약 · 없음) + pilled: list[bool] = [] # 줄마다 알약 값을 썼나 def qty_of(key: str, hand: Any) -> float | None: # 짝 = `M02_Labor_Expand._qty` 차례 got = _f(hand) - loose.append(got is None) + pilled.append(got is None and key in given) return _f(given.get(key)) if got is None else got inner = [one for one in doc.get("담은로직") or [] if isinstance(one, dict)] @@ -379,7 +379,7 @@ class Builder: "origin": {"참조": rid}, "lines": lines, }, - f"C:{rid}|{where}" if where and any(loose) else None, + f"C:{rid}|{where}" if where and any(pilled) else None, ) # ── 조합(구조물 사본) ─────────────────────────────────────────────── @@ -444,7 +444,22 @@ class Builder: whole = item_prices.load_prices(read_json(folder / PRICE_FILE) or {}) if manifest["종류"] == "sangeun": # 내 자료집 단가산출(719-2) return self.sangeun(whole, ref, doc, copies, 1) - return self.precombo(whole, ref, doc, copies, 1, ({}, f"L:{folder.name}")) + return self.precombo(whole, ref, doc, copies, 1) + + +def _Loose(whole: Any, read: Any) -> Any: + """B09 SK 속 펼침(723-4) — 사전조합 안 줄 수량 없음 = 0 값묶음(엔진 차례 · 수량만 씀 · + 그 줄 값 = B09 사전조합 블록 · 이 공사에서 입력) · 그 밖 까닭은 그대로.""" + from M02_MasterTemplete.M02_Labor_Expand import NO_QTY, Expand + + class Loose(Expand): + def __call__(self, ref: dict, trail: tuple[str, ...] = ()) -> dict[str, Any]: + got = super().__call__(ref, trail) + if got.get("기준") is None and str(got.get("까닭") or "").endswith(NO_QTY): + return {**got, "기준": dict.fromkeys(labor_doc.COSTS, Decimal(0))} + return got + + return Loose(whole, read) def fixed_line(fx: dict, qty: Any) -> dict[str, Any]: diff --git a/B09_Estimation/B09_Estimation_UI_Tabs.ts b/B09_Estimation/B09_Estimation_UI_Tabs.ts index 1a0c479a3..a91838c22 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs.ts @@ -158,6 +158,12 @@ export const blockLevel = (key: string, top: (index: number) => boolean): TabTab of: (i) => (top(i) ? 1 : 2), }); +/** 대가 줄 비고 — 호표 번호 · 까닭 · 수량 없는 SC 안 줄(723-4 `ask`)은 번호 뒤에도 경고 */ +export const noteOf = (ln: Row): string => + ln.ask && ln.qty == null + ? [ln.ref_no, ln.why].filter(Boolean).join(" · ") + : ln.ref_no || ln.why || ""; + /* ── 열 꼴 ────────────────────────────────────────────────────────────── */ export const text = (key: string, label: string, width?: number, group?: string[]): DataColumn => ({ diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold.ts index f253eab01..50ff41147 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold.ts @@ -22,7 +22,7 @@ import { attachHoverInfo } from "@ui/ui_template_hover_info"; import { cellColumn, createDataTable, type DataRow } from "@ui/ui_template_data_table"; import type { MapContextMenuItem } from "@ui/ui_template_context_menu"; import type { Row } from "./B09_Estimation_Calc_Base"; -import { money, parseNumber, qty, text, type TabContext } from "./B09_Estimation_UI_Tabs"; +import { money, noteOf, parseNumber, qty, text, type TabContext } from "./B09_Estimation_UI_Tabs"; import { attachRowMenu } from "./B09_Estimation_UI_Tabs_Bill_Edit"; import { foldMenu } from "./B09_Estimation_UI_Tabs_Bill_Fold_Menu"; import { API_BASE_URL } from "@config/config_frontend"; @@ -139,7 +139,7 @@ function foldCard( expense_price: line.prices?.expense, amount: line.amount, base: baseText(line), - note: line.ref_no || line.why || "", + note: noteOf(line), })); const edited = lines.filter((at) => (at.line.covered || []).length); const columns = [ diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold_Menu.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold_Menu.ts index 34c593700..af5d1b2a0 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold_Menu.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold_Menu.ts @@ -95,7 +95,7 @@ async function savePersonal(ctx: TabContext, block: Row): Promise { title: "내 자료집에 저장", message: sk ? "내 자료집 단가산출로 — 옵션 값 · 고른 자재 · 품 · 단가 고친 칸까지" - : "내 자료집 일위대가로 — 옵션 값 · 고른 자재 · 줄 수량 고친 칸까지", + : "내 자료집 일위대가로 — 옵션 값 · 고른 자재까지(줄 수량은 저장 안 함 · 쓰는 곳에서 이음)", value: block.name || "", okLabel: "저장", }); diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts index 11bbd0ade..e723c24a9 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts @@ -15,6 +15,7 @@ import type { Row } from "./B09_Estimation_Calc_Base"; import { blockLevel, money, + noteOf, text, type Link, type TabBuild, @@ -99,7 +100,7 @@ export const buildDaegaBlocks: TabBuild = ({ sheets }): TabTable | string => { unit: ln.unit || "", prices: { sum: ln.price, ...ln.prices }, amounts: { sum: ln.amount, ...ln.amounts }, - note: ln.ref_no || ln.why || "", + note: noteOf(ln), tag: ln.kind === "fixed" ? HOPYO_TAG.fix : HOPYO_TAG.pum, // 705-1 고정 줄 })), })), diff --git a/resources/tester/test_204_2_b09_bill_library.py b/resources/tester/test_204_2_b09_bill_library.py index 1a6a9a5c6..d7854a95d 100644 --- a/resources/tester/test_204_2_b09_bill_library.py +++ b/resources/tester/test_204_2_b09_bill_library.py @@ -27,7 +27,7 @@ if str(ROOT) not in sys.path: from B09_Estimation import B09_Estimation_Bill_Library as library # noqa: E402 from B09_Estimation import B09_Estimation_Router as router # noqa: E402 from B09_Estimation import B09_Estimation_Store as store_b09 # noqa: E402 -from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs # noqa: E402 +from B09_Estimation.B09_Estimation_Labor_Source import ASK, daega_inputs # noqa: E402 from B09_Estimation.B09_Estimation_Linked import linked_resource_keys # noqa: E402 from config import config_system # noqa: E402 from M02_MasterTemplete import M02_Labor_Ref as refs # noqa: E402 @@ -62,7 +62,9 @@ def world(tmp_path, monkeypatch): company = layers.company_dir(7) company.mkdir(parents=True) with store.at(company): - other = refs.create("precombo", {"이름": "회사 조합", "단위": "㎡", "담은로직": lines[:1]})["이름"] + other = refs.create("precombo", {"이름": "회사 조합", "단위": "㎡", "담은로직": lines[:1]})[ + "이름" + ] project = tmp_path / "storage" / "7" / "1" / "proj" project.mkdir(parents=True) return {"system": system, "key": key, "company": other, "root": project} @@ -84,7 +86,10 @@ def test_사전조합_고름_사본_들여옴_새_호표_다시_고르면_재사 got, block = _block(root, folder) assert (block["kind"], block["name"], block["unit"]) == ("사전조합", "서고 조합", "㎡") kinds = [(ln["kind"], ln.get("qty")) for ln in block["lines"]] - assert kinds == [("sangeun", 2.0), ("sangeun", 1.0), ("material", 3.0)] + # 723-1 · 723-4 = M02 SC 안 줄 수량 저장 안 함 → 내역에서 바로 쓴 SC = 수량 없음(이 공사에서 입력) + assert kinds == [("sangeun", None), ("sangeun", None), ("material", None)] + assert all(ln["ask"] == ASK for ln in block["lines"]) + assert block["cover"] == f"C:{block['origin']['참조']}" # 공사 안 한 덮개 그대로 assert got["owners"] == {} # 구조물 사본 없어도 호표 # 단가 묶음 = 담은 로직 · 사전조합 자재 · 고른 자재(사본 셈이 마스터를 안 읽음) bundle = json.loads((copy / "단가.json").read_text("utf-8")) diff --git a/resources/tester/test_702_3_b09_estimation_live.py b/resources/tester/test_702_3_b09_estimation_live.py index 0e54c70ec..99d29ad70 100644 --- a/resources/tester/test_702_3_b09_estimation_live.py +++ b/resources/tester/test_702_3_b09_estimation_live.py @@ -44,6 +44,10 @@ def test_서고_사본_산근_목록에_듦_대가_단가_채움(world): by_key = {r["key"]: r for r in sg["sg_list"]} labor = compute_labor(daega, {**base, "gaps": []}, by_key) row = next(r for r in labor["daega"] if r["key"] == block["id"]) + assert not row["sum"] and row["blank"] == 3 # 723-4 서고 SC 안 줄 수량 없음 = 이 공사에서 입력 + cover = {block["cover"]: {ln["cid"]: {"qty": q} for ln, q in zip(block["lines"], (2, 1, 3))}} + labor = compute_labor(daega, {**base, "gaps": []}, by_key, cover) + row = next(r for r in labor["daega"] if r["key"] == block["id"]) assert row["sum"] and not row.get("blank") diff --git a/resources/tester/test_703_b09_daega_copy.py b/resources/tester/test_703_b09_daega_copy.py index c4930ef5c..94d823351 100644 --- a/resources/tester/test_703_b09_daega_copy.py +++ b/resources/tester/test_703_b09_daega_copy.py @@ -3,7 +3,8 @@ - 복사본 = 덮개 `K:…` 의 `@copy` {from · name · spec} — 셈 짝(`Calc_Labor` 파이썬 · TS 거울)이 원본 블록을 본떠 끝 번호 새 호표 · 덮개 = 제 것 · 원본 없음 = 짝 잃음 · 저장 다듬기 · 서고 사본 지킴(prune) -- 내 자료집 = 개인 층 사전조합(임시 storage) — 줄 수량 덮개 = 손 · 품 · 단가 덮개 = 안 담음 수 · +- 내 자료집 = 개인 층 사전조합(임시 storage) — 줄 수량 덮개 = 문서 손(쓸 때 걷힘 · 723-1) · + 품 · 단가 덮개 = 안 담음 수 · 같은 이름 409 → 덮어쓰기 · 새 이름 """ @@ -178,7 +179,8 @@ def test_자료집_같은_이름_물음(world): saved = json.loads( (world["folder"] / "precombo" / f"{first['이름']}.json").read_text(encoding="utf-8") ) - assert saved["이름"] == "내 돌붙임" and saved["담은로직"][1]["수량"] == {"손": 9} + # 723-1 = M02 SC 안 줄 수량 저장 안 함(덮어쓴 손 9 도 걷힘 · 쓰는 곳 알약 · 이 공사 입력으로 이음) + assert saved["이름"] == "내 돌붙임" and saved["담은로직"][1]["수량"] is None assert saved["담은로직"][0]["입력값"] == b145.STONE assert len(list((world["folder"] / "precombo").glob("SC-*.json"))) == 2 @@ -212,5 +214,5 @@ def test_자료집_길_같은_이름_409_덮어쓰기(world, monkeypatch): over = client.post(url, json={**body, "overwrite": first.json()["키"]}) assert over.status_code == 200 and over.json()["키"] == first.json()["키"] saved = world["folder"] / "precombo" / f"{first.json()['키']}.json" - assert json.loads(saved.read_text(encoding="utf-8"))["담은로직"][0]["수량"] == {"손": 4} + assert json.loads(saved.read_text(encoding="utf-8"))["담은로직"][0]["수량"] is None # 723-1 assert client.post(url, json={**body, "cover": "S:없음"}).status_code == 404 diff --git a/resources/tester/test_719_1_sk_freeze.py b/resources/tester/test_719_1_sk_freeze.py index bcaf387f8..d0d1217b8 100644 --- a/resources/tester/test_719_1_sk_freeze.py +++ b/resources/tester/test_719_1_sk_freeze.py @@ -93,15 +93,24 @@ def test_B09_서고_사본_값_같음_원본판(world, tmp_path): "precombo", {"이름": "조합", "단위": "㎡", "담은로직": [{"참조": ref["ref"], "수량": {"손": 2}}]}, )["이름"] - _, live = _top({"folder": world["personal"], "ref": {**MINE, "키": sc}}, 1) + # 723-1 = SC 안 줄 수량(손 2) 저장 안 함 → 쓰는 쪽 알약 값으로 이음(층 · 사본 같은 값) + pill = f"{refs.ref_id({**MINE, '키': sc})}#0" + with store.at(world["personal"]): + top = labor.clean( + {"열": "rv", "담은로직": [{"참조": {**MINE, "키": sc}, "수량": {"손": 1}}]} + ) + live = labor.calc(top, {pill: 2})["줄"][0] + area = {"시트": [{"id": "s", "칸": {"A1": {"값": 2}}, "연결": [{"칸": "A1", "키": pill}]}]} before = _print(world["personal"]) root = tmp_path / "proj" made = library.import_copy(root, "precombo", "personal", "7/42", sc) assert made["만듦"] and _print(world["personal"]) == before folder = library.library_dir(root) / made["폴더"] read = {p.name: json.loads(p.read_text(encoding="utf-8")) for p in folder.glob("*.json")} - got = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, None)["줄"][0] - assert not got["까닭"] + bare = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, None)["줄"][0] + assert bare["기준"] is None and "수량 없음" in bare["까닭"] # 이은 값 없음 = 1 로 안 셈 + got = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, area)["줄"][0] + assert not got["까닭"] and not live["까닭"] assert got["기준"]["계"] == pytest.approx(live["기준"]["계"], rel=1e-9) mark = read["manifest.json"]["원본판"] built = library._build("precombo", "personal", "7/42", sc)[0] diff --git a/resources/tester/test_719_2_sk_project.py b/resources/tester/test_719_2_sk_project.py index 252707852..f121cb5cf 100644 --- a/resources/tester/test_719_2_sk_project.py +++ b/resources/tester/test_719_2_sk_project.py @@ -21,7 +21,7 @@ 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_Labor_Source import ASK, 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 @@ -122,15 +122,46 @@ def test_SK_단가_덮개_속_일위대가_펼침(world, tmp_path): if not _engine_ready(world): pytest.skip("715-1b 엔진 입력 덧칸(단가 · 값묶음) 뒤 — 지금은 M02 SK 셈도 못 함") key = _mine(world, full=True) - live = _calc(world["personal"], key) + # 723-1 = 속 일위대가 안 줄 수량 저장 안 함 → M02 SK = 수량 없음 · B09 = 그 줄만 빈 줄(이 공사 입력) + assert "수량 없음" in _calc(world["personal"], key)["까닭"] 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) sc_line = next(ln for ln in lines if ln["kind"] == "daega") - assert done[sc_line["key"]]["kind"] == "사전조합" and sc_line["qty"] == 2 - _same_money(block, live) + sc = done[sc_line["key"]] + assert sc["kind"] == "사전조합" and sc_line["qty"] == 2 + assert sc["blank"] == 1 and sc["lines"][0]["why"] == ASK and block["blank"] >= 1 + # 이 공사에서 입력(702 덮개 qty 1) = 옛 손 1 과 같은 값 → M02 셈(알약 값 1) 과 같은 금액 + cover = {sc["cover"]: {sc["lines"][0]["cid"]: {"qty": 1}}} + block = _b09(root, cover)[1][daega["libs"][folder]] + assert block["blank"] == 0 + sc_ref = next(x for x in _added(world, key) if "참조" in x)["참조"] + _same_money(block, _calc_given(world["personal"], key, {f"{refs.ref_id(sc_ref)}#0": 1})) + + +def _added(world, key): + with store.at(world["personal"]): + return store.read("sangeun", key)["문서"]["더한줄"] + + +def _calc_given(folder, key, pills): + """M02 SK 셈 — 속 일위대가 안 줄 수량 = 알약 값(723-1 `Expand` given).""" + from M01_MasterData import M01_MasterData_Store as m01 + from M02_MasterTemplete import M02_Sangeun_Calc as calc + from M02_MasterTemplete import M02_Sangeun_Doc as sd + from M02_MasterTemplete.M02_Labor_Expand import Expand + + whole = m01.loaded()[1] + with store.at(folder): + clean = sd.clean(store.read("sangeun", key)["문서"], None, whole) + me = refs.ref_id({"층": refs.here()[0], "주인": refs.here()[1], "키": clean["키"]}) + got = calc.run( + clean, whole, None, refs.read_ref, Expand(whole, refs.read_ref, pills), (me,) + ) + assert not got.get("까닭") + return got def test_구조물_사본_SK_줄_업데이트_덮개_입력값_고른자재_굳힌키_그대로( diff --git a/resources/tester/test_723_4_b09_sc_qty.py b/resources/tester/test_723_4_b09_sc_qty.py index 1ab872a2c..f335b4e46 100644 --- a/resources/tester/test_723_4_b09_sc_qty.py +++ b/resources/tester/test_723_4_b09_sc_qty.py @@ -3,7 +3,8 @@ - 723-1 = M02 일위대가(SC) 안 줄 · 자재 수량 저장 안 함(null = 수량 없음 · 1 아님) - 구조물 조합에서 온 SC = 그 사본 면적 알약 값(「참조 id#차례」 · 「참조 id#자재|코드」)으로 굳힘 - 내역에서 바로 쓴 SC(서고 사본) = 빈 줄 `ask` → 이 공사에서 입력(702 덮개 qty) → 금액 -- 옛 손(B09 굳힌 사본) = 그대로 · 덮개 열쇠 `C:참조` 그대로(기존 공사 금액 · 덮개 같음) +- 옛 손(B09 굳힌 사본) = 그대로 · 덮개 열쇠 `C:참조` 그대로(기존 공사 금액 · 덮개 같음) · + 알약 값 쓴 블록만 `C:참조|S:사본`(사본마다 값 다름 · 이 공사 입력 덮개와 안 섞임) - 셈 짝(`B09_Estimation_Calc_Labor` .py ↔ .ts) 거울 """ @@ -59,7 +60,7 @@ def test_수량_없는_SC_서고_사본_경고(): daega, block = _build(_doc(), ({}, "L:lib")) assert [ln["qty"] for ln in block["lines"]] == [None, None, None] assert all(ln["ask"] == ASK for ln in block["lines"]) - assert block["cover"] == f"C:{RID}|L:lib" # 다른 곳 SC 덮개와 안 섞임 + assert block["cover"] == f"C:{RID}" # 공사 안 한 덮개(703-1 지난 공사 가져옴 꼴 그대로) out = compute_labor(daega, SHEETS) got = out["daega_blocks"][0] assert got["blank"] == 3 and got["sum"] == 0 @@ -103,3 +104,53 @@ def test_거울_파이썬_TS(): py = json.loads(json.dumps(compute_labor(daega, SHEETS, {}, cover), ensure_ascii=False)) ts = b145._ts(daega, SHEETS, {}, cover) assert b145._same(py, ts["out"]) + + +def _structure(tmp_path, area): + """구조물 사본(참조 SC 한 줄 · 굳힌 SC = 수량 없음) + 면적.json(SC 안 줄 알약 연결 · 723-1).""" + from M02_MasterTemplete import M02_Item_Snapshot as snapshots + + root = b145._project(tmp_path / "proj", [(100, "SS000901")]) + sc = {**_doc(), "자재": []} + b145._snap(root, "pipe@100.0", "SS000901", [{"참조": REF, "수량": {"손": 1}}], {}, {RID: sc}) + folder = snapshots.structures_dir(root) / snapshots.folder_name("pipe@100.0") + if area is not None: + (folder / "면적.json").write_text(json.dumps(area, ensure_ascii=False), encoding="utf-8") + return root, folder + + +def _sc_block(root): + from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs + + got = daega_inputs(root) + combo = next(b for b in got["blocks"] if b["id"] == got["owners"]["pipe@100.0"]) + return got, next(b for b in got["blocks"] if b["id"] == combo["lines"][0]["key"]) + + +def test_구조물_사본_면적_알약_값_굳힘_설계_연동(tmp_path): + links = [{"칸": "A1", "키": f"{RID}#0"}, {"칸": "A2", "키": f"{RID}#1"}] + area = {"시트": [{"id": "s", "칸": {"A1": {"값": 1.5}, "A2": {"값": 2}}, "연결": links}]} + root, folder = _structure(tmp_path, area) + got, block = _sc_block(root) + assert [ln["qty"] for ln in block["lines"]] == [1.5, 2.0] + assert block["cover"] == f"C:{RID}|S:{folder.name}" and not any( + "ask" in ln for ln in block["lines"] + ) + out = compute_labor(got, SHEETS) + by_id = {b["id"]: b for b in out["daega_blocks"]} + assert by_id[block["id"]]["sum"] == 1500.0 + 4000.0 + assert by_id[got["owners"]["pipe@100.0"]]["sum"] == 5500.0 # 조합 = SC 1 단위 × 1 + # 설계 연동 — 면적 값이 바뀌면 다시 셈(사본 면적.json) + area["시트"][0]["칸"]["A1"]["값"] = 3 + (folder / "면적.json").write_text(json.dumps(area, ensure_ascii=False), encoding="utf-8") + assert [ln["qty"] for ln in _sc_block(root)[1]["lines"]] == [3.0, 2.0] + + +def test_구조물_사본_알약_안_이으면_빈_줄(tmp_path): + root, folder = _structure(tmp_path, None) + got, block = _sc_block(root) + assert [ln["qty"] for ln in block["lines"]] == [None, None] + assert block["cover"] == f"C:{RID}" # 알약 안 씀 = 서고 SC 와 같은 「이 공사 입력」 덮개 + out = compute_labor(got, SHEETS) + owner = next(r for r in out["daega"] if r["key"] == got["owners"]["pipe@100.0"]) + assert owner["blank"] >= 1 # 부른 SC 빈 줄 = 조합도 빈 줄(166-6)