"""B09 재료 창고 「지난 공사」(PLAN 703-1) — 같은 회사 다른 공사 일위대가 → 이 공사 서고 사본. 목록 = 그 공사 구조물 사본(`S:` · 일위대가 있는 것) · 서고 사본(`L:` · 사전조합 · 구조물조합) · 고친 칸 수 = 그 공사 저장 덮개(블록 cover — 조합 `S:`/`L:폴더` · 사전조합 `C:참조` · 짝 `Labor_Source.Builder`) · 같은 원본 · 같은 값 · 같은 덮개 = 한 줄 가져옴 = 사본 폴더 통째 → 이 공사 `library/{종류}@{층}@{주인}@{키}~{공사 · 사본 해시}`(구조물 사본 = 서고 구조물조합과 한 꼴) · manifest `가져온곳` = 그 공사 · 사본 · 다시 고르면 같은 사본 · 덮개 = 그 공사 저장 칸 + 이 공사 블록 cover(조합 = `L:새 폴더` · 사전조합 = 같은 `C:참조`) → 화면 캐시가 그 cover 가 비었을 때만 얹음([저장] 때 남음) · 단가 = 이 공사 단가 책(셈이 이 공사 것) · 권한 = 부르는 쪽(같은 회사). """ from __future__ import annotations import shutil from pathlib import Path from typing import Any from B09_Estimation import B09_Estimation_Bill_Library as library from B09_Estimation import B09_Estimation_Store as store from common_util.common_util_json import atomic_write_json from M02_MasterTemplete import M02_Labor_Ref as refs from M02_MasterTemplete.M02_Item_Snapshot import ( FOLDER_PATTERN, MANIFEST_NAME, PICKS_FILE, VALUES_FILE, linked_snapshots, now_text, structures_dir, ) from M02_MasterTemplete.M02_Item_Source import LABOR_FILE, read_json def _covers(root: Path) -> dict[str, Any]: return store.read_user(root).get("daega_cover") or {} def _cells(cover: Any) -> dict[str, Any]: """덮개 칸만(복사본 표지 `@copy` 뺌).""" return {k: v for k, v in (cover or {}).items() if not k.startswith("@")} def cover_of(head: str, folder: Path, kind: str) -> str: """사본 폴더의 대가 블록 cover — 사전조합 = 참조(공사 안 같은 사전조합 한 덮개).""" if kind != "precombo": return f"{head}:{folder.name}" ref = ((read_json(folder / MANIFEST_NAME) or {}).get("원본") or {}).get("참조") or {} return f"C:{refs.ref_id(ref)}" def _row(folder: Path, key: str, kind: str, cover: dict[str, Any]) -> dict[str, Any]: manifest = read_json(folder / MANIFEST_NAME) or {} origin = manifest.get("원본") or {} path = origin.get("경로") or [] if kind == "precombo": name, spec, unit = origin.get("name") or folder.name, "", origin.get("단위") or "" else: # 짝 `Labor_Source.Builder.combo` 이름 · 규격 · 단위 labor = read_json(folder / LABOR_FILE) or {} name = origin.get("name") or " ".join(path) or folder.name spec, unit = " › ".join(path), (labor.get("줄") or {}).get("단위") or "" cells = _cells(cover.get(cover_of(key[0], folder, kind))) sig = library.tag( { "원본": {k: origin.get(k) for k in ("layer", "owner", "key", "참조")}, "값": [read_json(folder / f) for f in (VALUES_FILE, PICKS_FILE)], "덮개": cells, } ) kind_text = "사전조합" if kind == "precombo" else "구조물 조합" return {"cover": key, "name": name, "spec": spec, "unit": unit, "kind": kind_text, "covers": len(cells), "sig": sig} # fmt: skip def rows_of(root: str | Path) -> list[dict[str, Any]]: """한 공사 가져올 일위대가 — 구조물 사본 → 서고 사본 · 같은 것 한 줄.""" root = Path(root) cover = _covers(root) out: list[dict[str, Any]] = [] for info in sorted(linked_snapshots(root).values(), key=lambda i: i["폴더"]): if "일위대가" in info.get("부분", []): folder = structures_dir(root) / info["폴더"] out.append(_row(folder, f"S:{folder.name}", "combo", cover)) for folder, manifest in library.copies(root): out.append(_row(folder, f"L:{folder.name}", str(manifest.get("종류")), cover)) seen: set[str] = set() return [r for r in out if not (r["sig"] in seen or seen.add(r["sig"]))] def _source(other: Path, cover_key: str) -> tuple[Path, str, str, str | None, str]: """덮개 열쇠 → (사본 폴더, 종류, 층, 주인, 키) · 틀린 모양 ValueError · 없음 LookupError.""" head, _, rest = cover_key.partition(":") if head == "S" and FOLDER_PATTERN.match(rest): folder = structures_dir(other) / rest origin = (read_json(folder / MANIFEST_NAME) or {}).get("원본") or {} kind, layer, owner, key = ( "combo", origin.get("layer"), origin.get("owner"), origin.get("key"), ) elif head == "L" and library.NAME.match(rest): folder = library.library_dir(other) / rest kind, layer, owner, key = rest.split("~")[0].split("@") owner = None if owner == "-" else owner.replace("-", "/") else: raise ValueError(f"가져올 일위대가 모양이 틀림 「{cover_key}」") if not isinstance(read_json(folder / MANIFEST_NAME), dict) or not layer or not key: raise LookupError(f"지난 공사에 그 일위대가가 없음 「{cover_key}」") return folder, kind, str(layer), owner, str(key) def import_past( root: str | Path, other: str | Path, other_ref: dict[str, Any], cover_key: str, who: dict[str, Any] | None = None, ) -> dict[str, Any]: """지난 공사 사본 하나 → 이 공사 서고 사본 — `{폴더, 만듦, 덮개, 덮개자리}` · 덮개 = 그 공사 저장 칸 · 덮개자리 = 이 공사 블록 cover.""" src, kind, layer, owner, key = _source(Path(other), cover_key) tag = {"공사": str(other_ref.get("project_id")), "사본": cover_key} name = library.folder_name(kind, layer, owner, key, tag) target = library.library_dir(root) / name cells = _cells(_covers(Path(other)).get(cover_of(cover_key[0], src, kind))) mine = cover_of("L", src, kind) if kind == "precombo" else f"L:{name}" out = {"폴더": name, "덮개": cells, "덮개자리": mine} if isinstance(read_json(target / MANIFEST_NAME), dict): return {**out, "만듦": False} if target.is_dir(): # 끊긴 쓰기(manifest 없음) 자리 shutil.rmtree(target) shutil.copytree(src, target, ignore=shutil.ignore_patterns(MANIFEST_NAME)) old = read_json(src / MANIFEST_NAME) or {} manifest = {k: old[k] for k in ("원본", "값", "원값") if k in old} manifest.update({"종류": kind, "가져온날": now_text(), "가져온이": who}) manifest["가져온곳"] = {**other_ref, "사본": cover_key} atomic_write_json(target / MANIFEST_NAME, manifest) # 마지막 — 있으면 다 쓴 폴더 return {**out, "만듦": True}