Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_1

충돌 하나(UI_Side)는 sub_laptop_4 의 층별 구성(옵션 보이기 · 도면 양식은 시스템 층에만)을 취해 풂.
This commit is contained in:
2026-09-29 12:34:44 +09:00
4 changed files with 51 additions and 7 deletions
+28 -2
View File
@@ -13,6 +13,8 @@ from pathlib import Path
from typing import Any
from common_util.common_util_json import atomic_write_json
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
@@ -61,12 +63,36 @@ def ensure(folder: Path) -> bool:
return True
def _combo_names() -> dict[str, str]:
"""일위대가 조합 키 → 이름(M01 시스템 마스터 · 층 무관) — 못 읽으면 빈 표."""
try:
rows = m01.read(combo_store.FILE)[0].get("줄") or []
except m01.StoreError:
return {}
return {str(r.get("키")): str(r.get("이름") or "") for r in rows}
def items(folder: Path) -> list[dict[str, Any]]:
"""그 층 항목 — SS 줄마다 `{key, 열, 종류, 분류1~3, 단위, 일위대가, 자재조합,
도번, 면적번, 재료번}`."""
도번, 면적번, 재료번, names}` · `names` = 화면 이름(키 대신 · 없으면 null) —
일위대가 = 조합 이름 · 자재조합 = 그 층 자재 조합 이름 ·
도번 · 면적번 · 재료번 = 항목(열) 이름."""
ua = _combo_names()
with store.at(folder):
rows = store.read_combos().get("줄", [])
return [{"key": r.get("키"), **{k: r.get(k) for k in ITEM_FIELDS}} for r in rows]
um = {str(r.get("키")): str(r.get("이름") or "") for r in matset.listing()["줄"]}
out = []
for r in rows:
head = " ".join(str(r[k]) for k in ("종류", "분류1", "분류2", "분류3") if r.get(k))
names = {
"일위대가": ua.get(str(r.get("일위대가"))) if r.get("일위대가") else None,
"자재조합": um.get(str(r.get("자재조합"))) if r.get("자재조합") else None,
**{
k: (head or r.get("열")) if r.get(k) else None for k in ("도번", "면적번", "재료번")
},
}
out.append({"key": r.get("키"), **{k: r.get(k) for k in ITEM_FIELDS}, "names": names})
return out
def _fresh_id(want: str, used: set[str]) -> str:
@@ -6,6 +6,8 @@
personal 읽기 · 쓰기 = 본인(SYSTEM_ADMIN 은 누구나) · 회사 없음 = 쓰기 409 「회사부터 등록」
옛 길(`/api/m02/structures/…` 등 · `M02_MasterTemplete_Router.py`)은 시스템 층 별칭으로 그대로.
회사 · 개인 층은 처음 부를 때 빈 뼈대를 만듦(`Items.ensure`).
결정(PLAN 21-9): 일위대가(UA) 갈라 내기는 새 UA 가 M01 시스템 마스터에 생기므로 층 길에서도
SYSTEM_ADMIN 만 · 첫 진입 뒤 구조물집계표가 이미 있어 같은 이름 새로 쓰기(판 없이)는 409 그대로.
"""
from __future__ import annotations
@@ -154,9 +154,12 @@ export function buildSide(opt: SideOptions): SideHandle {
className: "m02-side__lists ui-sidebar-panel",
children: [
summary,
section(options.title, options.body),
// 구조물 옵션 보이기 · 도면 양식은 시스템 층(마스터 템플릿)에만
...(layer === "system"
? [section(L("M02_KindDrawing"), bodies.get("drawing")!, ...actionRow("drawing"))]
? [
section(options.title, options.body),
section(L("M02_KindDrawing"), bodies.get("drawing")!, ...actionRow("drawing")),
]
: []),
],
});
+16 -3
View File
@@ -76,7 +76,8 @@ def test_시스템_층은_그대로_항목_목록은_SS_줄(world):
rows = client.get("/api/m02/items").json()
ss = store.read_combos()["줄"]
assert [r["key"] for r in rows] == [r["키"] for r in ss] and rows[0]["key"] == "SS000001"
assert set(rows[0]) == {"key", *items_router.items_store.ITEM_FIELDS}
assert set(rows[0]) == {"key", "names", *items_router.items_store.ITEM_FIELDS}
assert set(rows[0]["names"]) == {"일위대가", "자재조합", "도번", "면적번", "재료번"}
assert client.get("/api/m02/layers/system/structure-combos").json() == store.read_combos()
# 시스템 층 쓰기는 SYSTEM_ADMIN 만
assert client.post("/api/m02/layers/system/structures/rv").status_code == 403
@@ -145,14 +146,26 @@ def test_회사_항목을_개인으로_복사(world):
row = {"이름": "회사 자재", "담은자재": [_material()]}
um = admin.post(f"{base}/material-combos", json={"판": 판, "줄": row}).json()["키"]
admin.post(f"{base}/structures/cw/use-combo", json={"종류": "자재조합", "키": um})
ss = admin.get("/api/m02/items?layer=company").json()[0]["key"]
admin.post(f"{base}/structures/cw/use-combo", json={"종류": "일위대가", "키": "UA000004"})
row = admin.get("/api/m02/items?layer=company").json()[0]
ss = row["key"]
ua_name = items_router.items_store._combo_names()["UA000004"]
assert row["names"] == { # 키 대신 화면 이름
"일위대가": ua_name,
"자재조합": "회사 자재",
"도번": "돌기슭막이 찰쌓기 회사형",
"면적번": "돌기슭막이 찰쌓기 회사형",
"재료번": "돌기슭막이 찰쌓기 회사형",
}
user = _as(world)
body = {"from_layer": "company", "from_owner": "7", "key": ss}
got = user.post("/api/m02/layers/personal/items/copy", json=body)
assert got.status_code == 200, got.text
made = got.json()
assert made["열"] == "cw" and made["key"] == "SS000001"
assert made["열"] == "cw" and made["key"] == "SS000001" # 응답 = 새 SS 키(바로 고름)
mine_rows = user.get("/api/m02/items?layer=personal").json()
assert mine_rows[0]["key"] == made["key"] and mine_rows[0]["names"]["일위대가"] == ua_name
mine = "/api/m02/layers/personal"
copy = user.get(f"{mine}/templates/structure/cw").json()["문서"]
assert copy["도면"]["entities"] == [{"type": "line"}] and copy["열"] == "cw"