Files
Aislo/M02_MasterTemplete/M02_Item_Paste.py
T
eomsangdonandClaude Opus 5.5 61d2ed5997 refactor(M02): 재료산출 시트 지움 — 종류 material · 재료번 SM · 좌측 줄 · 집계표 「재료산출」 줄 · 카드 · 전체 편집 · 복사 길 · 프로젝트 복사본 「재료」 · 고르기 모달 · 시스템 층 · fixture · 시험
- 새 프로젝트 씨앗(KINDS)에서도 빠짐 · 새 열은 구조물 도면 · 면적산출만 만듦
- storage 층(회사 · 개인 · 프로젝트) material/ 파일은 지우지 않음(읽기만 끊음)
- 장부 재료번 칸은 다음 집계표 [저장] 때 빠짐

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
2026-10-04 17:56:49 +09:00

106 lines
4.4 KiB
Python

"""여러 항목 · 폴더를 층 사이 붙여넣기(PLAN 30-2) — 구조물 라이브러리 나무의 [복사] → [붙여넣기].
붙일 폴더(`dest`) 아래 새 항목 — 대상 층 새 키(SC · SP) · 열 `본뜬` = 원본(층 · 주인 · 키) ·
같은 이름은 「이름 (2)」 · 폴더는 안의 항목 · 빈 폴더까지(폴더 이름이 겹치면 「폴더 (2)」).
파일(도면 · 면적 · 입력 연결)은 `Items.copy_item` 이 옮김. 권한은 부르는 쪽 몫.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
from M02_MasterTemplete import M02_MasterTemplete_Items as items_store
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_MasterTemplete_Tree as tree
def _under(path: list[str], folder: list[str]) -> bool:
return path[: len(folder)] == folder
def outer(targets: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""고른 것 가운데 다른 고른 폴더 안에 든 것은 뺌(폴더가 안의 것까지 다룸) —
옮기기 · 지우기 · 붙여넣기."""
folders = [list(t.get("경로") or []) for t in targets if not t.get("키")]
def inside(t: dict[str, Any]) -> bool:
path = list(t.get("경로") or [])
return any(_under(path, f) and (t.get("키") or path != f) for f in folders if f)
return [t for t in targets if not inside(t)]
def _table(folder: Path) -> dict[str, Any]:
with store.at(folder):
return tree.normalize_table(store.read("table", store.STRUCTURE_TABLE)["문서"])
def _items(doc: dict[str, Any]) -> list[dict[str, Any]]:
return [c for c in doc.get("열") or [] if tree.is_item(c)]
def _unique(path: list[str], taken: list[list[str]]) -> list[str]:
n, out = 2, path
while out in taken:
out, n = [*path[:-1], f"{path[-1]} ({n})"], n + 1
return out
def paste(
source: Path,
target: Path,
targets: list[dict[str, Any]],
dest: list[str],
origin: dict[str, Any],
) -> dict[str, Any]:
"""`targets` = `[{경로, 키?}]`(키 = 항목 · 없으면 폴더 경로) → `target` 층 `dest` 아래 ·
돌려줌 알림 `{op: "paste", 경로, 열: [새 열 id]}`. `origin` = `{layer, owner}`(본뜬)."""
src = _table(source)
with store.at(source):
keys = {str(r.get("열")): str(r.get("키")) for r in store.read_combos().get("줄", [])}
src_items = [(tree.path_of(c), keys.get(c["id"])) for c in _items(src)]
src_folders = tree.folders_of(src)
items_store.ensure(target)
dst = _table(target)
taken = [tree.path_of(c) for c in _items(dst)]
taken_folders = tree.folders_of(dst)
plan: list[tuple[str, list[str]]] = [] # (원본 키, 새 경로)
new_folders: list[list[str]] = []
def busy(path: list[str]) -> bool:
paths = [*taken, *(p for _, p in plan)]
return path in [*taken_folders, *new_folders] or any(_under(p, path) for p in paths)
for one in outer(targets):
if one.get("키"):
key = str(one["키"])
found = next((p for p, k in src_items if k == key), None)
if found is None:
raise store.StoreError(404, f"없는 항목 「{key}」")
plan.append((key, _unique([*dest, found[-1]], [*taken, *(p for _, p in plan)])))
continue
folder = list(one.get("경로") or [])
if not folder or (
folder not in src_folders and not any(_under(p, folder) for p, _ in src_items)
):
raise store.StoreError(404, f"없는 폴더 「{' '.join(folder)}」")
top, n = [*dest, folder[-1]], 2
while busy(top):
top, n = [*dest, f"{folder[-1]} ({n})"], n + 1
new_folders.append(top)
new_folders += [[*top, *f[len(folder) :]] for f in src_folders if _under(f, folder)]
plan += [(k, [*top, *p[len(folder) :]]) for p, k in src_items if _under(p, folder) and k]
made = [
items_store.copy_item(source, target, key, {**origin, "key": key}, path)["열"]
for key, path in plan
]
if new_folders: # 빈 폴더도 남게 — 집계표 `폴더`
with store.at(target):
got = store.read("table", store.STRUCTURE_TABLE)
doc = got["문서"]
doc["폴더"] = [*(doc.get("폴더") or []), *new_folders]
store.write("table", store.STRUCTURE_TABLE, got["판"], doc)
return {"op": "paste", "경로": dest, "열": made}