Files
Aislo/B09_Estimation/B09_Estimation_Past.py
T
eomsangdonandClaude Opus 5.5 fdb3eb9b77 feat(B09): 오른쪽 재료 창고 · 지난 공사 일위대가 가져오기(PLAN 703-1)
- 설계내역서 표 오른쪽 서랍(공용 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>
2026-10-31 14:59:58 +09:00

140 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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}