Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_1
# Conflicts: # M02_MasterTemplete/StructOptions/M02_SO_Api.ts # M02_MasterTemplete/StructOptions/M02_SO_Options.ts
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
"""M02 구조물 정의 한 벌(PLAN 48-1) — 정본 `resources/master_template/구조물정의.json`.
|
||||
|
||||
틀 = `docs/raw/M02_구조물옵션정의/정의_틀.md`. 읽기 = 로그인 누구나 · 쓰기 = 시스템 관리자만.
|
||||
판 = 정수 · 저장마다 +1 · 앞 판은 `구조물정의_이력/판_N.json` 으로 남김(지난 정의 값 읽기용).
|
||||
검사 = 칸 꼴 · 키 겹침 · 표시 · 그룹 · 배치 · 규칙 대상 있음 — 틀리면 422 · 낡은 판 409.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from B05_Profile import B05_Profile_Structures_Schema as schema
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
FILE = "구조물정의.json"
|
||||
HISTORY = "구조물정의_이력"
|
||||
TOP = {"판", "schema_version", "comment", "구조물군", "동작", "types"}
|
||||
SECTIONS = {"종류": dict, "표시": dict, "그룹": dict, "배치": dict, "규칙": list}
|
||||
SPOT = ("줄", "칸", "너비")
|
||||
|
||||
|
||||
def read() -> dict[str, Any]:
|
||||
return schema.load_definition()
|
||||
|
||||
|
||||
def history_path(version: int) -> Path:
|
||||
return Path(schema.DEFINITION_PATH).parent / HISTORY / f"판_{version}.json"
|
||||
|
||||
|
||||
def _type_errors(doc: dict[str, Any]) -> tuple[list[str], dict[str, dict[str, Any]]]:
|
||||
"""(틀린 것 · 종류 → {옵션 키: 옵션}) — 종류 꼴 · 키 겹침 · 구조물군 있음."""
|
||||
errors: list[str] = []
|
||||
keys: dict[str, dict[str, Any]] = {}
|
||||
groups = doc.get("구조물군")
|
||||
if not isinstance(groups, dict):
|
||||
errors.append("구조물군은 {키: 이름} 묶음이어야 함")
|
||||
groups = {}
|
||||
for row in doc.get("types") if isinstance(doc.get("types"), list) else []:
|
||||
try:
|
||||
t = schema.StructureType.model_validate(row)
|
||||
except ValidationError as e:
|
||||
errors.append(
|
||||
f"종류 「{row.get('type_id') if isinstance(row, dict) else row}」 꼴 — {e}"
|
||||
)
|
||||
continue
|
||||
if t.type_id in keys:
|
||||
errors.append(f"종류 키 「{t.type_id}」 겹침")
|
||||
if t.group not in groups:
|
||||
errors.append(f"「{t.type_id}」 구조물군 「{t.group}」 이 구조물군에 없음")
|
||||
opts: dict[str, Any] = {}
|
||||
for o in t.options:
|
||||
if o.key in opts:
|
||||
errors.append(f"「{t.type_id}」 옵션 키 「{o.key}」 겹침")
|
||||
if o.input == "select" and not o.choices:
|
||||
errors.append(f"「{t.type_id}.{o.key}」 고르기 꼴은 선택지가 있어야 함")
|
||||
opts[o.key] = o
|
||||
keys[t.type_id] = opts
|
||||
if not isinstance(doc.get("types"), list) or not keys:
|
||||
errors.append("types 는 종류 목록이어야 함")
|
||||
return errors, keys
|
||||
|
||||
|
||||
def _kind_errors(doc: dict[str, Any], keys: dict[str, dict[str, Any]]) -> list[str]:
|
||||
"""종류마다 덧칸 — `{기준측점: 참거짓, 동작: [동작 이름]}`."""
|
||||
errors: list[str] = []
|
||||
acts = doc.get("동작") if isinstance(doc.get("동작"), dict) else {}
|
||||
for tid, row in doc["종류"].items():
|
||||
if tid not in keys or not isinstance(row, dict):
|
||||
errors.append(f"종류 덧칸 「{tid}」 — 없는 종류이거나 묶음 아님")
|
||||
continue
|
||||
for name, value in row.items():
|
||||
if name == "기준측점" and isinstance(value, bool):
|
||||
continue
|
||||
if name == "동작" and isinstance(value, list) and all(a in acts for a in value):
|
||||
continue
|
||||
errors.append(
|
||||
f"종류 덧칸 「{tid}.{name}」 이 틀림(기준측점 참거짓 · 동작 = 동작 이름들)"
|
||||
)
|
||||
return errors
|
||||
|
||||
|
||||
def _spot_errors(doc: dict[str, Any], keys: dict[str, dict[str, Any]]) -> list[str]:
|
||||
"""배치 — `{종류: {옵션 키: {줄, 칸, 너비}}}` · 1 이상 정수."""
|
||||
errors: list[str] = []
|
||||
for tid, row in doc["배치"].items():
|
||||
if tid not in keys or not isinstance(row, dict):
|
||||
errors.append(f"배치 「{tid}」 — 없는 종류이거나 묶음 아님")
|
||||
continue
|
||||
for key, spot in row.items():
|
||||
ok = isinstance(spot, dict) and set(spot) <= set(SPOT)
|
||||
ok = ok and all(type(v) is int and v >= 1 for v in spot.values())
|
||||
if key not in keys[tid] or not ok:
|
||||
errors.append(
|
||||
f"배치 「{tid}.{key}」 — 없는 옵션이거나 줄 · 칸 · 너비가 1 이상 정수 아님"
|
||||
)
|
||||
return errors
|
||||
|
||||
|
||||
def _rule_errors(doc: dict[str, Any], keys: dict[str, dict[str, Any]]) -> list[str]:
|
||||
"""규칙 — `{종류, 만약: {옵션, 값들}, 그러면: {옵션, 선택지? · 보이기? · 기본값?}}`."""
|
||||
errors: list[str] = []
|
||||
for i, rule in enumerate(doc["규칙"], 1):
|
||||
where = f"규칙 {i}"
|
||||
if not isinstance(rule, dict) or rule.get("종류") not in keys:
|
||||
errors.append(f"{where} — 없는 종류")
|
||||
continue
|
||||
opts = keys[rule["종류"]]
|
||||
when, then = rule.get("만약"), rule.get("그러면")
|
||||
if not isinstance(when, dict) or not isinstance(then, dict):
|
||||
errors.append(f"{where} — 만약 · 그러면은 묶음이어야 함")
|
||||
continue
|
||||
src, dst = opts.get(when.get("옵션")), opts.get(then.get("옵션"))
|
||||
if src is None or dst is None or src is dst:
|
||||
errors.append(f"{where} — 만약 · 그러면 옵션이 없거나 같음")
|
||||
continue
|
||||
values = when.get("값들")
|
||||
if not isinstance(values, list) or not values:
|
||||
errors.append(f"{where} — 값들이 비었음")
|
||||
elif src.input == "select" and not set(map(str, values)) <= set(src.choices):
|
||||
errors.append(f"{where} — 값들이 「{src.key}」 선택지에 없음")
|
||||
effects = set(then) - {"옵션"}
|
||||
if not effects or not effects <= {"선택지", "보이기", "기본값"}:
|
||||
errors.append(f"{where} — 그러면은 선택지 · 보이기 · 기본값 중 하나 이상")
|
||||
allowed = dst.choices
|
||||
if "선택지" in then:
|
||||
pick = then["선택지"]
|
||||
if dst.input != "select" or not isinstance(pick, list) or not set(pick) <= set(allowed):
|
||||
errors.append(f"{where} — 선택지는 「{dst.key}」 고르기 선택지 안에서만")
|
||||
else:
|
||||
allowed = pick
|
||||
if "보이기" in then and not isinstance(then["보이기"], bool):
|
||||
errors.append(f"{where} — 보이기는 참거짓")
|
||||
if "기본값" in then and dst.input == "select" and str(then["기본값"]) not in allowed:
|
||||
errors.append(f"{where} — 기본값이 「{dst.key}」 선택지에 없음")
|
||||
return errors
|
||||
|
||||
|
||||
def check(doc: Any) -> list[str]:
|
||||
"""정의 한 벌 검사 — 틀린 것 목록(빈 목록 = 통과)."""
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Groups as groups
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Options as options
|
||||
|
||||
if not isinstance(doc, dict):
|
||||
return ["정의는 묶음이어야 함"]
|
||||
errors = [f"모르는 칸 「{k}」" for k in doc if k not in TOP and k not in SECTIONS]
|
||||
errors += [
|
||||
f"「{k}」 꼴이 틀림" for k, kind in SECTIONS.items() if not isinstance(doc.get(k), kind)
|
||||
]
|
||||
if not isinstance(doc.get("동작"), dict):
|
||||
errors.append("동작은 {이름: 뜻} 묶음이어야 함")
|
||||
if errors:
|
||||
return errors
|
||||
found, keys = _type_errors(doc)
|
||||
errors += found
|
||||
plain = {tid: set(opts) for tid, opts in keys.items()}
|
||||
for fn, section in ((options._clean, "표시"), (groups._clean, "그룹")):
|
||||
try:
|
||||
fn(doc[section], plain)
|
||||
except store.StoreError as e:
|
||||
errors += e.detail["errors"] if isinstance(e.detail, dict) else [str(e.detail)]
|
||||
return errors + _kind_errors(doc, keys) + _spot_errors(doc, keys) + _rule_errors(doc, keys)
|
||||
|
||||
|
||||
def _commit(change: Callable[[dict[str, Any]], dict[str, Any]], version: Any) -> dict[str, Any]:
|
||||
"""판이 같을 때만 고쳐 씀 — 앞 판은 이력으로 · 새 판 = 앞 판 + 1."""
|
||||
path = Path(schema.DEFINITION_PATH)
|
||||
with store._LOCK:
|
||||
current = read()
|
||||
have = int(current.get("판", 1))
|
||||
if str(version) != str(have):
|
||||
raise store.StoreError(409, {"stale": [FILE], "판": have})
|
||||
doc = {**change(current), "판": have}
|
||||
errors = check(doc)
|
||||
if errors:
|
||||
raise store.StoreError(422, {"errors": errors})
|
||||
atomic_write_json(history_path(have), current)
|
||||
atomic_write_json(path, {**doc, "판": have + 1})
|
||||
return read()
|
||||
|
||||
|
||||
def save(doc: Any, version: Any) -> dict[str, Any]:
|
||||
"""정의 통째로 고쳐 씀(시스템 관리자) — 돌려주는 것 = 새 정의."""
|
||||
if not isinstance(doc, dict):
|
||||
raise store.StoreError(400, "정의는 묶음이어야 함")
|
||||
return _commit(lambda _: dict(doc), version)
|
||||
|
||||
|
||||
def save_sections(changes: dict[str, Any], version: Any) -> dict[str, Any]:
|
||||
"""정의 몇 칸만 고쳐 씀(옵션 보이기 · 그룹 화면)."""
|
||||
return _commit(lambda current: {**current, **changes}, version)
|
||||
@@ -1,45 +1,34 @@
|
||||
"""M02 구조물 옵션 그룹 — 구조물 종류 안에 이름 붙은 묶음(PLAN 46-2).
|
||||
|
||||
정본 = `resources/master_template/구조물옵션그룹.json` =
|
||||
`{type_id: [{"id": "g1a2b3c", "이름": "유입구 기슭막이", "옵션": ["inlet_revet_form", …]}]}`
|
||||
— 없는 종류 = 그룹 없음(파일이 없어도 됨) · 빈 그룹(옵션 0)도 그룹.
|
||||
정본 = 구조물 정의 한 벌(`M02_SO_Definition` · PLAN 48-1)의 「그룹」 칸 =
|
||||
`{type_id: [{"id": "g1a2b3c", "이름": "유입구 기슭막이", "옵션": [...], "형상"?: {...}}]}`
|
||||
— 없는 종류 = 그룹 없음 · 빈 그룹(옵션 0)도 그룹 · 형상 = 길이 · 높이 · 전 · 후 받기 + 뜻.
|
||||
|
||||
그룹 하나 = 프로젝트 B05 · B06 폼의 보조 컨테이너 하나(제목 = 이름) — 그 안에서 구조물 조합
|
||||
항목 하나를 지정(구조물 기록 `group_items[그룹 id]`). 그룹에 안 넣은 옵션 = 본체 칸(`summary_item`).
|
||||
옵션 하나는 한 종류 안에서 그룹 하나에만. 시스템 공통 · 고쳐 씀은 시스템 관리자(길이 막음).
|
||||
판 = 파일 sha256 앞 16자(`store.version_of`) · 낡은 판으로 쓰면 409.
|
||||
판 = 정의 판(글) · 낡은 판으로 쓰면 409 · 저장마다 정의 판 +1 · 이력.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import load_structure_types
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from B05_Profile.B05_Profile_Structures_Schema import load_definition, load_structure_types
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
FILE = "구조물옵션그룹.json"
|
||||
GROUP_ID = re.compile(r"^g[0-9a-z]{1,16}$")
|
||||
NAME_MAX = 40
|
||||
SHAPE = ("길이", "높이", "전", "후") # 그룹이 받을 형상 칸 — `{칸: {받기: 참거짓, 뜻: 글}}`
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _load() -> tuple[dict[str, Any], str]:
|
||||
"""(그룹 표 · 판) — 파일이 없으면 빈 표 · 판 빈 글."""
|
||||
path = store.FOLDER / FILE
|
||||
if not path.is_file():
|
||||
return {}, ""
|
||||
raw = path.read_bytes()
|
||||
try:
|
||||
doc = json.loads(raw)
|
||||
except ValueError as e:
|
||||
raise store.StoreError(500, f"{FILE} 을 못 읽음 — {e}") from e
|
||||
if not isinstance(doc, dict):
|
||||
raise store.StoreError(500, f"{FILE} 은 종류별 묶음이어야 함")
|
||||
return doc, store.version_of(raw)
|
||||
"""(그룹 표 · 판) — 정의 한 벌(`M02_SO_Definition`)의 「그룹」 칸 · 판 = 정의 판 글."""
|
||||
doc = load_definition()
|
||||
return doc.get("그룹") or {}, str(doc.get("판", 1))
|
||||
|
||||
|
||||
def groups_of() -> dict[str, list[dict[str, Any]]]:
|
||||
@@ -83,11 +72,23 @@ def listing() -> dict[str, Any]:
|
||||
return {"판": version, "그룹": doc, "종류": kinds}
|
||||
|
||||
|
||||
def _clean(groups: Any) -> dict[str, list[dict[str, Any]]]:
|
||||
"""받은 표 검사 — 없는 종류 · 키 · 겹친 id · 이름 · 옵션 두 그룹은 422 · 빈 종류는 뺌."""
|
||||
def _shape_ok(shape: Any) -> bool:
|
||||
return isinstance(shape, dict) and all(
|
||||
k in SHAPE
|
||||
and isinstance(v, dict)
|
||||
and isinstance(v.get("받기"), bool)
|
||||
and isinstance(v.get("뜻", ""), str)
|
||||
and set(v) <= {"받기", "뜻"}
|
||||
for k, v in shape.items()
|
||||
)
|
||||
|
||||
|
||||
def _clean(groups: Any, keys: dict[str, set[str]] | None = None) -> dict[str, list[dict[str, Any]]]:
|
||||
"""받은 표 검사 — 없는 종류 · 키 · 겹친 id · 이름 · 옵션 두 그룹 · 틀린 형상 422."""
|
||||
if not isinstance(groups, dict):
|
||||
raise store.StoreError(400, "그룹은 종류별 묶음이어야 함")
|
||||
keys = {t.type_id: {o.key for o in t.options} for t in load_structure_types()}
|
||||
if keys is None:
|
||||
keys = {t.type_id: {o.key for o in t.options} for t in load_structure_types()}
|
||||
errors: list[str] = []
|
||||
out: dict[str, list[dict[str, Any]]] = {}
|
||||
for type_id, rows in groups.items():
|
||||
@@ -126,9 +127,14 @@ def _clean(groups: Any) -> dict[str, list[dict[str, Any]]]:
|
||||
)
|
||||
else:
|
||||
owner[key] = name
|
||||
if "형상" in row and not _shape_ok(row["형상"]):
|
||||
errors.append(
|
||||
f"「{type_id}.{name}」 형상은 {{길이 · 높이 · 전 · 후: {{받기, 뜻}}}}"
|
||||
)
|
||||
ids.add(gid)
|
||||
names.add(name)
|
||||
clean.append({"id": gid, "이름": name, "옵션": [k for k in opts if k in keys[type_id]]})
|
||||
kept = {"id": gid, "이름": name, "옵션": [k for k in opts if k in keys[type_id]]}
|
||||
clean.append({**kept, "형상": row["형상"]} if "형상" in row else kept)
|
||||
if clean:
|
||||
out[type_id] = clean
|
||||
if errors:
|
||||
@@ -138,11 +144,7 @@ def _clean(groups: Any) -> dict[str, list[dict[str, Any]]]:
|
||||
|
||||
def save(groups: Any, version: str) -> dict[str, Any]:
|
||||
"""고쳐 씀 — 판이 같을 때만. 돌려주는 것 = 새 `listing()`."""
|
||||
fixed = _clean(groups)
|
||||
path = store.FOLDER / FILE
|
||||
with store._LOCK:
|
||||
have = store.version_of(path.read_bytes()) if path.is_file() else ""
|
||||
if have != (version or ""):
|
||||
raise store.StoreError(409, {"stale": [FILE], "판": have})
|
||||
atomic_write_json(path, fixed)
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Definition import save_sections
|
||||
|
||||
save_sections({"그룹": _clean(groups)}, version)
|
||||
return listing()
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
"""M02 구조물 옵션 보이기 — 구조물 종류마다 B05/B06 폼에 보일 옵션을 사용자가 체크.
|
||||
|
||||
정본 = `resources/master_template/구조물옵션표시.json` = `{type_id: {option_key: false}}`
|
||||
— 없는 종류 · 키는 보임(파일이 없어도 모두 보임).
|
||||
정본 = 구조물 정의(`M02_SO_Definition` · PLAN 48-1) 「표시」 칸 = `{type_id: {option_key: false}}`
|
||||
— 없는 종류 · 키는 보임.
|
||||
저장은 **숨김(false)만** 남김 — 「모두 보임」 = `{}`.
|
||||
옵션 정의 · 옛 값은 그대로 — 폼이 그리는 것만 갈림(`GET /api/projects/structure-types` `visible`).
|
||||
판 = 파일 sha256 앞 16자(`store.version_of`) · 낡은 판으로 쓰면 409.
|
||||
판 = 정의 판(글) · 낡은 판으로 쓰면 409 · 저장마다 정의 판 +1 · 이력.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -19,14 +18,13 @@ from B05_Profile.B05_Profile_Structures_Schema import (
|
||||
StructureOptionField,
|
||||
StructureType,
|
||||
StructureTypesResponse,
|
||||
load_definition,
|
||||
load_structure_types,
|
||||
registry_schema_version,
|
||||
)
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Groups import groups_of
|
||||
|
||||
FILE = "구조물옵션표시.json"
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -56,18 +54,9 @@ class TypesWithVisible(StructureTypesResponse):
|
||||
|
||||
|
||||
def _load() -> tuple[dict[str, Any], str]:
|
||||
"""(숨김 표 · 판) — 파일이 없으면 빈 표 · 판 빈 글."""
|
||||
path = store.FOLDER / FILE
|
||||
if not path.is_file():
|
||||
return {}, ""
|
||||
raw = path.read_bytes()
|
||||
try:
|
||||
doc = json.loads(raw)
|
||||
except ValueError as e:
|
||||
raise store.StoreError(500, f"{FILE} 을 못 읽음 — {e}") from e
|
||||
if not isinstance(doc, dict):
|
||||
raise store.StoreError(500, f"{FILE} 은 종류별 묶음이어야 함")
|
||||
return doc, store.version_of(raw)
|
||||
"""(표시 표 · 판) — 정의 한 벌(`M02_SO_Definition`)의 「표시」 칸 · 판 = 정의 판 글."""
|
||||
doc = load_definition()
|
||||
return doc.get("표시") or {}, str(doc.get("판", 1))
|
||||
|
||||
|
||||
def _shown(doc: dict[str, Any], type_id: str, key: str) -> bool:
|
||||
@@ -138,11 +127,12 @@ def listing() -> dict[str, Any]:
|
||||
return {"판": version, "표시": hidden, "종류": kinds}
|
||||
|
||||
|
||||
def _clean(shown: Any) -> dict[str, dict[str, bool]]:
|
||||
def _clean(shown: Any, keys: dict[str, set[str]] | None = None) -> dict[str, dict[str, bool]]:
|
||||
"""받은 표 검사 → 숨김만 남김. 없는 종류 · 키 · 참거짓 아닌 값은 422."""
|
||||
if not isinstance(shown, dict):
|
||||
raise store.StoreError(400, "표시는 종류별 묶음이어야 함")
|
||||
keys = {t.type_id: {o.key for o in t.options} for t in load_structure_types()}
|
||||
if keys is None:
|
||||
keys = {t.type_id: {o.key for o in t.options} for t in load_structure_types()}
|
||||
errors: list[str] = []
|
||||
out: dict[str, dict[str, bool]] = {}
|
||||
for type_id, row in shown.items():
|
||||
@@ -165,11 +155,7 @@ def _clean(shown: Any) -> dict[str, dict[str, bool]]:
|
||||
|
||||
def save(shown: Any, version: str) -> dict[str, Any]:
|
||||
"""고쳐 씀 — 판이 같을 때만. 돌려주는 것 = 새 `listing()`."""
|
||||
fixed = _clean(shown)
|
||||
path = store.FOLDER / FILE
|
||||
with store._LOCK:
|
||||
have = store.version_of(path.read_bytes()) if path.is_file() else ""
|
||||
if have != (version or ""):
|
||||
raise store.StoreError(409, {"stale": [FILE], "판": have})
|
||||
atomic_write_json(path, fixed)
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Definition import save_sections
|
||||
|
||||
save_sections({"표시": _clean(shown)}, version)
|
||||
return listing()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""M02 구조물 옵션 보이기 · 그룹 길 — 정본 `M02_SO_Options.py` · `M02_SO_Groups.py`.
|
||||
"""M02 구조물 옵션 보이기 · 그룹 · 정의 길 — 정본 `M02_SO_Definition.py`(PLAN 48-1) 한 벌.
|
||||
|
||||
`router` = 고쳐 씀 · 관리 읽기(`main.py` 가 `system_admin_only` 로 붙임) ·
|
||||
`read_router` = 읽기만(로그인 누구나 · `main.py` 가 `protected` 로 붙임).
|
||||
`read_router` = 읽기만(로그인 누구나 · `main.py` 가 `protected` 로 붙임) · 정의 쓰기는 여기서
|
||||
시스템 관리자만(`access.require(write=True)` · 아니면 403).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -14,6 +15,7 @@ from pydantic import BaseModel
|
||||
from common_util.common_util_auth import verify_session
|
||||
from M02_MasterTemplete import M02_Layer_Access as access
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Definition as definition
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Groups as groups
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Options as options
|
||||
|
||||
@@ -31,6 +33,11 @@ class GroupsBody(BaseModel):
|
||||
그룹: dict[str, Any] # {종류: [{id, 이름, 옵션[]}]}
|
||||
|
||||
|
||||
class DefinitionBody(BaseModel):
|
||||
판: int # 읽어 간 정의 판 — 어긋나면 409
|
||||
정의: dict[str, Any] # 정의 한 벌(틀 = docs/raw/M02_구조물옵션정의/정의_틀.md)
|
||||
|
||||
|
||||
def _call(fn, *args):
|
||||
with store.at(store.FOLDER):
|
||||
try:
|
||||
@@ -68,3 +75,17 @@ def get_structure_options_read(session=Depends(verify_session)) -> dict:
|
||||
"""구조물 옵션 보이기 읽기 — 로그인 누구나(마스터 한 벌 · 고쳐 씀은 시스템 관리자만)."""
|
||||
access.require(session, "system", None)
|
||||
return _call(options.listing)
|
||||
|
||||
|
||||
@read_router.get("/structure-definition")
|
||||
def get_structure_definition(session=Depends(verify_session)) -> dict:
|
||||
"""구조물 정의 한 벌(판 · 구조물군 · 동작 · types · 종류 · 표시 · 그룹 · 배치 · 규칙)."""
|
||||
access.require(session, "system", None)
|
||||
return _call(definition.read)
|
||||
|
||||
|
||||
@read_router.put("/structure-definition")
|
||||
def put_structure_definition(body: DefinitionBody, session=Depends(verify_session)) -> dict:
|
||||
"""정의 고쳐 씀 — 시스템 관리자만(403) · 낡은 판 409 · 검사 422 · 판 +1 · 앞 판은 이력."""
|
||||
access.require(session, "system", None, write=True)
|
||||
return _call(definition.save, body.정의, body.판)
|
||||
|
||||
Reference in New Issue
Block a user