- 설계내역서 표 오른쪽 서랍(공용 side_panels · 접고 펴기 · 좁은 화면 = 아래) — 찾기 하나 + 출처 탭 이 공사 · 내 것 · 회사 · 품셈(704-1 기준 목록 · 우클릭 계산서 보기) · 지난 공사 - 두 번 누름 · Enter = 고른 내역 줄에 단가연결 · 끌어 놓기 = 놓은 줄 · 연결 = 단가연결 모달과 같은 길(702-3 즉시 금액) - 품셈 = 넣을 값 창에 계산서 입력 전부(목록 바꿈은 기본과 다른 것만 — 석축헐기 뒷길이 60 이 0 으로 열리던 것) - 서버 B09_Estimation_Past + GET/POST /estimation/past — 같은 회사 다른 공사 구조물 사본 · 서고 사본 → 이 공사 서고 사본(폴더 통째 · 가져온곳 · 덮개째 · 단가 = 이 공사 책) · 남의 회사 403 - 표 높이 셈(spaceBelow)이 뒤 형제만 보므로 서랍 = DOM 앞 · CSS order 로 오른쪽 - 시험 test_703_1 6 · 셸 꼴 글 시험 200-2 · 202-1 = 표 칸 + 서랍 꼴로 고침(낡은 글) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
140 lines
6.7 KiB
Python
140 lines
6.7 KiB
Python
"""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}
|