"""M02 구조물집계표조합(SS 장부) — 구조물 열마다 줄 하나 · 경로 옮겨 받기(PLAN 26-2a). 제어용 장부 · 관리자는 집계표 화면(또는 나무)으로 고침 · 서버가 집계표 [저장] 때 맞춤 · 키 = SS + 6자리 · 한 번 준 키는 그 열에 남음 · 지운 열의 키는 다시 안 씀(`다음` 이 기억). 줄 = `{키, 열, 경로, 이름, 종류, 분류1~3(경로 앞 넷 — 옛 코드가 읽음), 단위, 일위대가, 자재조합, 도번, 면적번, 재료번, 바인딩, 펼침}`. `M02_MasterTemplete_Store.py` 가 커서 떼어 냄 — 저장소는 `store.sync_combos` · `store.read_combos` 이름으로 이 파일을 부름. """ from __future__ import annotations import json from typing import Any from common_util.common_util_json import atomic_write_json from M02_MasterTemplete import M02_MasterTemplete_Store as store from M02_MasterTemplete import M02_MasterTemplete_Tree as tree HEAD = "SS" NOTE = ( "구조물집계표 구조물 열(꼴=수)마다 한 줄 · 서버가 집계표 [저장] 때 맞춤(손으로 고치지 않음) · " "키 SS 는 한 번 주면 안 바뀌고 지운 열의 키는 다시 안 씀 · 경로 = 폴더 이름 배열" ) def _row(key: str, col: dict[str, Any], numbers: dict[str, str]) -> dict[str, Any]: path = tree.path_of(col) return { "키": key, "열": col["id"], "경로": path, "이름": tree.name_of(path), **tree.old_fields(path), "단위": col.get("단위"), "일위대가": col.get("일위대가"), "자재조합": col.get("자재조합"), **{field: numbers.get(field, "") for _, field, _, _ in store.PARTS}, "바인딩": col.get("바인딩"), "펼침": col.get("펼침"), } def sync() -> dict[str, Any]: """장부를 집계표에 맞춤 — 새 열 = 새 SS 키 · 지운 열 = 줄 지움 · 고친 열 = 칸 갱신 · 줄 차례 = 집계표 열 차례. 바뀐 것이 없으면 파일을 안 씀.""" path = store.root() / store.COMBO_FILE with store._LOCK: old = json.loads(path.read_bytes()) if path.is_file() else {} keys = {row["열"]: row["키"] for row in old.get("줄", [])} taken = [int(k[len(HEAD) :]) for k in keys.values()] after = max(old.get("다음", 1), max(taken, default=0) + 1) numbers = store.structure_numbers() rows = [] for col in store._structure_columns(): if col["id"] not in keys: keys[col["id"]] = f"{HEAD}{after:06d}" after += 1 rows.append(_row(keys[col["id"]], col, numbers.get(col["id"], {}))) doc = {"그룹": "구조물집계표조합", "판": 1, "비고": NOTE, "다음": after} if old.get("지운번호"): doc["지운번호"] = old["지운번호"] # 지운 열의 도번 8자리 — 다시 안 줌 doc["줄"] = rows if doc != old: atomic_write_json(path, doc) return doc def read() -> dict[str, Any]: """장부 — 파일이 아직 없으면 지금 열 전부로 처음 한 번 만듦 · `경로` 가 없던 옛 줄은 읽을 때 종류 · 분류1~3 에서 채움(빈 분류 건너뜀 · 파일은 다음 [저장] 때 새 모양).""" path = store.root() / store.COMBO_FILE doc = json.loads(path.read_bytes()) if path.is_file() else sync() for row in doc.get("줄") or []: if "경로" not in row: row["경로"] = tree.row_path(row) row["이름"] = tree.name_of(row["경로"]) return doc def migrate_paths() -> bool: """옛 집계표 · 장부 → 경로 모양(열 `경로` · 머리 줄 수 · 장부 `경로` · `이름`) — 한 번 · 두 번 돌려도 같음. 바뀐 것이 없으면 파일을 안 씀 · 바꿨으면 참. 지금 층(`store.at`) 기준.""" got = store.read("table", store.STRUCTURE_TABLE) if tree.normalize_table(got["문서"]) != got["문서"]: store.write("table", store.STRUCTURE_TABLE, got["판"], got["문서"]) return True path = store.root() / store.COMBO_FILE before = path.read_bytes() if path.is_file() else b"" sync() return (path.read_bytes() if path.is_file() else b"") != before