"""M01 마스터 데이터 관리자 API — 계약 `resources/master_data/_화면_계약.md`. ⚠ 권한은 등록하는 쪽(`main.py`)이 `system_admin_only` 로 붙임. """ from __future__ import annotations from decimal import Decimal from typing import Any, Literal from fastapi import APIRouter, HTTPException from pydantic import BaseModel from M01_MasterData import M01_MasterData_Store as store from M01_MasterData import M01_MasterData_Store_Make as make router = APIRouter(prefix="/api/m01", tags=["M01 MasterData"]) class CalcBody(BaseModel): key: str inputs: dict[str, Any] = {} row: dict[str, Any] | None = None # 저장 전 고친 로직 줄(없으면 저장된 파일로) file: str | None = None # 새 로직이면 더할 로직 파일 class Change(BaseModel): op: Literal["edit", "add", "delete"] key: str | None = None row: dict[str, Any] | None = None class FileChanges(BaseModel): file: str version: str changes: list[Change] class SaveBody(BaseModel): files: list[FileChanges] class NewLogic(BaseModel): logic: dict[str, Any] owner: str = make.OWNERS[0] class CopyLogic(BaseModel): key: str 이름: str = "" owner: str = make.OWNERS[0] class EditLogic(BaseModel): key: str version: str logic: dict[str, Any] owner: str = "" class DeleteLogic(BaseModel): key: str version: str def _num(v): """Decimal → JSON 수(그냥 두면 글자로 나감).""" if isinstance(v, Decimal): return int(v) if v == v.to_integral_value() else float(v) if isinstance(v, list): return [_num(x) for x in v] if isinstance(v, dict): return {k: _num(x) for k, x in v.items()} return v def _call(fn, *args): try: return _num(fn(*args)) except store.StoreError as e: raise HTTPException(status_code=e.status, detail=e.detail) from e @router.get("/groups") def get_groups() -> dict: return {"groups": _call(store.groups)} @router.get("/groups/{group}/files") def get_files(group: str) -> dict: return {"files": _call(store.files_of, group)} @router.get("/subs") def get_subs(file: str = "", group: str = "") -> dict: return _call(store.subs, file, group) @router.get("/rows") def get_rows( file: str, page: int = 1, size: int = 50, q: str = "", unlinked: bool = False, sub: str = "", detail: str = "", state: str = "", ) -> dict: return _call(store.rows, file, page, size, q, unlinked, sub, detail, state) @router.get("/tables") def get_tables( file: str = "", group: str = "", sub: str = "", detail: str = "", q: str = "", page: int = 1, size: int = 30, usage: str = "", section: str = "", ) -> dict: return _call(store.tables, file, group, sub, detail, q, page, size, usage, section) @router.get("/table") def get_table(file: str, key: str) -> dict: return _call(store.table, file, key) @router.get("/table/options") def get_table_options(file: str, key: str) -> dict: """표 조건 칸의 값만 — 줄을 통째로 받지 않음.""" return _call(make.table_options, file, key) @router.get("/sections") def get_sections( book: str, division: str = "", chapter: str = "", q: str = "", limit: int = 200 ) -> dict: """원문 절 목록 — 만들기 첫 걸음.""" return _call(make.sections, book, division, chapter, q, limit) @router.get("/logics") def get_logics( sub: str = "", detail: str = "", q: str = "", blocked: int | None = None, owner: str = "" ) -> dict: return {"logics": _call(store.logics, sub, detail, q, blocked, owner)} @router.get("/logic") def get_logic(key: str) -> dict: return _call(store.logic, key) @router.get("/elements") def get_elements(group: str, q: str = "", limit: int = 50) -> dict: return _call(store.elements, group, q, limit) @router.get("/pick") def get_pick(kind: str, q: str = "", limit: int = 50) -> dict: return _call(store.pick, kind, q, limit) @router.get("/materials") def get_materials( sub: str, detail: str = "", spec: str = "", region: str = "", unit: str = "", limit: int = 50, ) -> dict: """재료 고르기 조건 안 후보 — sub = 구분 · detail = 상세구분 · spec = 규격 · region = 자재지역 · unit = 호표 줄 단위(주면 그 단위 줄만).""" return _call(store.materials, sub, detail, spec, region, unit, limit) @router.post("/calc") def post_calc(body: CalcBody) -> dict: return _call(store.calc, body.key, body.inputs, body.row, body.file) @router.post("/text") def post_text(body: CalcBody) -> dict: """읽는 식 줄 — 몸은 `/calc` 와 같음.""" return _call(store.text, body.key, body.inputs, body.row, body.file) @router.post("/save") def post_save(body: SaveBody) -> dict: return {"files": _call(store.save, [f.model_dump() for f in body.files])} @router.post("/logic/new") def post_logic_new(body: NewLogic) -> dict: return _call(make.logic_new, body.logic, body.owner) @router.post("/logic/copy") def post_logic_copy(body: CopyLogic) -> dict: return _call(make.logic_copy, body.key, body.이름, body.owner) @router.post("/logic/edit") def post_logic_edit(body: EditLogic) -> dict: return _call(make.logic_edit, body.key, body.version, body.logic, body.owner) @router.post("/logic/delete") def post_logic_delete(body: DeleteLogic) -> dict: return _call(make.logic_delete, body.key, body.version)