Files
Aislo/M01_MasterData/M01_MasterData_Router.py
T
eomsangdonandClaude Sonnet 5 f80046e4ae feat(M01): B01 마스터 데이터 단추 하나로 · 시중물가 「조달 찾기」 모달 · 찾기 API
- B01 옛 「마스터 데이터」(Z01) 단추 제거 · 「마스터 요소」 이름을 「마스터 데이터」 로
- 재료 › 시중물가 줄마다 「조달 찾기」 — 나라장터자재 + 시중물가 조달 줄을 이름·규격으로 찾아 「나라장터:<열쇠>」 연결 · 노랑 표시 · 연결 끊기
- GET /api/m01/procurement 추가 · 값 묶음(조달{…}) 확정 전이라 연결은 조달›연결 칸에 임시로 담음

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
2026-09-19 23:29:17 +09:00

114 lines
2.9 KiB
Python

"""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
router = APIRouter(prefix="/api/m01", tags=["M01 MasterData"])
class CalcBody(BaseModel):
book: str
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]
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("/rows")
def get_rows(file: str, page: int = 1, size: int = 50, q: str = "") -> dict:
return _call(store.rows, file, page, size, q)
@router.get("/tables")
def get_tables(file: str, q: str = "") -> dict:
return _call(store.tables, file, q)
@router.get("/table")
def get_table(file: str, key: str) -> dict:
return _call(store.table, file, key)
@router.get("/logics")
def get_logics(book: str = "", chapter: str = "", q: str = "", blocked: int | None = None) -> dict:
return {"logics": _call(store.logics, book, chapter, q, blocked)}
@router.get("/logic")
def get_logic(book: str, key: str) -> dict:
return _call(store.logic, book, key)
@router.get("/elements")
def get_elements(group: str, q: str = "", limit: int = 50) -> dict:
return _call(store.elements, group, q, limit)
@router.get("/procurement")
def get_procurement(q: str = "", limit: int = 50) -> dict:
return _call(store.procurement, q, limit)
@router.post("/calc")
def post_calc(body: CalcBody) -> dict:
return _call(store.calc, body.book, 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])}