"""조합 갈라 내기 — 구조물 열 하나가 가리키는 조합을 고친 내용으로 새로 만들어 그 열만 새 키를 가리키게 함(PLAN 15장 · 검토 `14_조합편집검토.md` 1장). · 일위대가 조합(UA · `일위대가조합.json`) — 새 줄은 M01 조합 저장소가 만듦(부르기만) · 자재 조합(UM · 마스터 템플릿 `자재조합.json`) — M02 자재 조합 저장소가 만듦(표 「다음」 번호) 조합은 여러 열 · 모든 프로젝트가 같이 씀 — 제자리 고치지 않음. 담은 것(차례 · 메모까지)이 이미 있는 조합과 같으면 새로 안 만들고 그 키(원래 키 먼저) · 새 줄의 본뜬키 = 원래 키 · 소유 현장. """ from __future__ import annotations from typing import Any, Callable from M01_MasterData import M01_MasterData_Store as m01 from M01_MasterData import M01_MasterData_Store_Combo as combo_store from M02_MasterTemplete import M02_MasterTemplete_Matset as matset from M02_MasterTemplete import M02_MasterTemplete_Store as store _OWNER = "현장" def _m01(fn, *args): """M01 막힘은 M02 막힘으로 — 상태 · 까닭 그대로.""" try: return fn(*args) except m01.StoreError as e: raise store.StoreError(e.status, e.detail) from e def _free_name(name: str, rows: list[dict]) -> str: taken = {str(r.get("이름") or "") for r in rows} n, out = 2, name while out in taken: out, n = f"{name} ({n})", n + 1 return out def _branch( column: str, version: str, field: str, rows: list[dict], slot: str, ident: tuple[str, ...], items: list[dict], source: str, make: Callable[[str, list[dict]], dict], name: str, ) -> dict[str, Any]: """집계표 판 검사 → 같은 내용 찾기 · 없으면 `make(이름, 줄들)` 로 새 줄 → 집계표 그 열 `field`. `slot` = 담은 칸(담은로직 · 담은자재) · `ident` = 담은 줄에서 견주는 칸(로직 · 메모 / 코드).""" table = store.read("table", store.STRUCTURE_TABLE) if version and table["판"] != version: raise store.StoreError(409, {"stale": [store.STRUCTURE_TABLE], "판": table["판"]}) col = next((c for c in store._structure_columns() if c["id"] == column), None) if col is None: raise store.StoreError(404, f"{store.STRUCTURE_TABLE}에 없는 구조물 열 「{column}」") if not isinstance(items, list) or not items: raise store.StoreError(400, "담은 것이 비었음") def body(got) -> tuple: # 차례대로 · 빈 메모 = null return tuple( tuple(str(x.get(k) or "") for k in ident) for x in got or [] if isinstance(x, dict) ) same = [r for r in rows if body(r.get(slot)) == body(items)] hit = next((r for r in same if source and r.get("키") == source), same[0] if same else None) if hit: key, made_name, made = str(hit["키"]), str(hit.get("이름") or ""), False else: made_name = _free_name(name, rows) key, made = make(made_name, rows), True # ponytail: 조합 쓰기와 집계표 쓰기는 한 묶음 아님 — 사이에 집계표가 바뀌면 409 뒤 주인 없는 # 조합 하나가 남음(다시 하면 같은 내용이라 그 키를 씀) doc = table["문서"] for c in doc.get("열", []): if isinstance(c, dict) and c.get("id") == column: c[field] = key info = store.write("table", store.STRUCTURE_TABLE, table["판"], doc) # SS 도 여기서 맞춤 return {"키": key, "이름": made_name, "새로만듦": made, "집계표판": info["판"]} def branch_combo( column: str, source: str, items: list[dict], name: str, version: str = "" ) -> dict[str, Any]: """일위대가 조합 갈라 내기 — 구분 · 상세구분 · 단위 · 출처 = 본뜬 것.""" was = _m01(combo_store.combo, source)["combo"] items = [ {"로직": str(x.get("로직") or ""), "메모": x.get("메모")} for x in items or [] if isinstance(x, dict) ] def make(made_name: str, _rows) -> str: row = { "이름": made_name, **{k: was.get(k) for k in ("구분", "상세구분", "단위")}, "담은로직": items, "본뜬키": source, "출처": was.get("출처"), } return str(_m01(combo_store.combo_new, row, _OWNER)["key"]) name = name.strip() or str(was.get("이름") or source) rows = _m01(m01.read, combo_store.FILE)[0].get("줄") or [] return _branch( column, version, "일위대가", rows, "담은로직", ("로직", "메모"), items, source, make, name ) def material_combo(key: str) -> dict[str, Any]: return matset.one(key) def branch_material_combo( column: str, source: str, items: list[dict], name: str, version: str = "" ) -> dict[str, Any]: """자재 조합 갈라 내기 — `source` 가 빈 글이면 이 열의 첫 자재 조합(본뜬키 null).""" was = material_combo(source) if source else {} items = [ {k: x.get(k) for k in ("코드", "이름", "규격", "단위")} for x in items or [] if isinstance(x, dict) ] def make(made_name: str, _rows) -> str: row = {"이름": made_name, "담은자재": items, "본뜬키": source or None, "소유": _OWNER} return matset.create(row)["키"] heads = next((c for c in store._structure_columns() if c["id"] == column), {}) default = " ".join(h for h in store._heads(heads) if h) if heads else column name = name.strip() or str(was.get("이름") or default or column) rows = matset.listing()["줄"] return _branch( column, version, "자재조합", rows, "담은자재", ("코드",), items, source, make, name )