"""프로젝트에서 개인 층으로 — [개인에 저장](save-as) · 프로젝트에서 새 항목(new) (PLAN 26-2b). 개인 층 새 항목 = 구조물집계표 새 열 하나 + 빈 도면 · 면적산출 · 재료산출(번호 · SS 키는 저장소가 줌). [개인에 저장]은 구조물 복사본의 열 정의 · 도면 · 면적 · 재료 · 자재조합 · 입력연결을 담음 — 단가 묶음 · 입력값 · 고른자재는 프로젝트 몫이라 안 가져감. 쓰기는 지금 층 저장소 함수(`store.at` · `store.write` · `matset.create` · 항목 연결 `write_file`) — 나무 고침(`M02_MasterTemplete_Tree`)과 같은 집계표 저장 한 줄기(경로 · 머리 줄 수 맞춤 · 번호 · SS 장부)를 탐(PLAN 26-2a). 권한(본인 개인 층 · 회사 없음 409)은 부르는 쪽 몫. """ from __future__ import annotations import json from pathlib import Path from typing import Any from common_util.common_util_json import atomic_write_json from M02_MasterTemplete import M02_MasterTemplete_Inputs as inputs_store from M02_MasterTemplete import M02_MasterTemplete_Items as items_store from M02_MasterTemplete import M02_MasterTemplete_Matset as matset from M02_MasterTemplete import M02_MasterTemplete_Store as store from M02_MasterTemplete import M02_MasterTemplete_Tree as tree from M02_MasterTemplete import M02_Template_Layers as layers from M02_MasterTemplete.M02_Item_Source import LINK_FILE, UM_FILE, read_json #: 복사본 파일 → 층 양식 종류 PART_KINDS = (("도면.json", "structure"), ("면적.json", "area"), ("재료.json", "material")) def heads_of(path: list[str], name: str | None) -> list[str]: """경로 + 이름 → 항목 경로(깊이 자유 · 빈 칸은 뺌) — 머리 줄 수는 집계표 저장이 맞춤.""" full = [str(p).strip() for p in [*path, name or ""] if str(p or "").strip()] if not full: raise ValueError("새 항목의 경로 · 이름이 비어 있습니다.") return full def _free_path(doc: dict[str, Any], full: list[str]) -> list[str]: """같은 경로 항목이 이미 있으면 마지막 이름을 「이름 (2)」 로 (나무는 같은 경로 두 항목을 안 둠).""" taken = [tree.path_of(c) for c in doc.get("열", []) if tree.is_item(c)] out, n = list(full), 2 while out in taken: out, n = [*full[:-1], f"{full[-1]} ({n})"], n + 1 return out def _new_matset(row: dict[str, Any], fallback: str) -> str: """자재 조합 줄을 이 층에 새 키로 — 이름이 겹치면 「이름 (2)」.""" taken = {str(r.get("이름") or "") for r in matset.listing()["줄"]} name = base = str(row.get("이름") or fallback) n = 2 while name in taken: name, n = f"{base} ({n})", n + 1 got = {k: row.get(k) for k in ("담은자재", "소유", "비고")} return matset.create({**got, "이름": name, "본뜬키": None})["키"] def create_item( folder: Path, path: list[str], name: str | None, unit: str | None, copy: Path | None = None, origin: dict[str, Any] | None = None, ) -> dict[str, Any]: """개인 층 `folder` 에 새 항목 — `copy` = 구조물 복사본 폴더(있으면 그 내용을 담음). 돌려줌 `{key, 열, 이름}`.""" heads = heads_of(path, name) total = read_json(copy / "집계.json") if copy else None base_col = dict((total or {}).get("열") or {}) base_row = (total or {}).get("줄") or {} items_store.ensure(folder) with store.at(folder): table = store.read("table", store.STRUCTURE_TABLE) doc = table["문서"] used = {c.get("id") for c in doc.get("열", []) if isinstance(c, dict)} new_id = items_store._fresh_id(str(base_col.get("id") or "item").split("@")[0], used) um_row = read_json(copy / UM_FILE) if copy else None um_key = _new_matset(um_row, heads[0]) if isinstance(um_row, dict) else None heads = _free_path(doc, heads) column = { **json.loads(json.dumps(base_col)), "id": new_id, "머리": heads, "경로": heads, "꼴": "수", "단위": unit or base_col.get("단위") or base_row.get("단위"), "일위대가": base_col.get("일위대가") or base_row.get("일위대가"), "자재조합": um_key, } column.pop("본뜬열", None) if origin: column["본뜬"] = origin tree._place(doc, column) # 같은 앞마디 형제 곁(제목행 병합이 이어지게) store.write("table", store.STRUCTURE_TABLE, table["판"], doc) # 빈 세 파일 · 번호 · SS for file, kind in PART_KINDS if copy else (): got = read_json(copy / file) if not isinstance(got, dict): continue number = store._KEY[kind] have = store.read(kind, new_id)["문서"] body = {k: v for k, v in got.items() if k != "계산값"} body = {**body, "열": new_id, number: have.get(number)} if kind in store.SHEETS: body = store._server_values(body) with store._LOCK: atomic_write_json(store._path(kind, new_id), body) links = read_json(copy / LINK_FILE) if copy else None if isinstance(links, dict) and links.get("줄"): inputs_store.write_file(new_id, {"판": 1, "줄": links["줄"]}) ss = next(r for r in store.read_combos()["줄"] if r.get("열") == new_id) return {"key": ss["키"], "열": new_id, "이름": tree.name_of(heads)} def personal_item(company: int, user: int, made: dict[str, Any]) -> dict[str, Any]: """새로 만든 개인 층 항목의 `summary_item`.""" return { "layer": "personal", "owner": f"{company}/{user}", "key": made["key"], "name": made["이름"], } def personal_folder(company: int, user: int) -> Path: return layers.personal_dir(company, user)