- 모든 요소·표·로직 줄의 열쇠 → 키(LB000123 꼴) · 옛 열쇠·원문 번호는 원문번호 칸 · 대장 _키대장.json (키 36,840 · 다음 번호)
- 변수 적는 법 키로 — 로직 식·연결·준용·통합·후보·조달 연결 일괄 변환 · {이름} 낀 참조는 ID:원문번호
- 인력 8 파일 → 인력.json(줄마다 조사 · 머리 조사 묶음) · 기계 2 파일 → 기계.json(세부분류)
- 소요량·계수·로직 파일 이름 = 그룹_원문_NN장_장 제목 · 머리 부문·차례
- 엔진(값 찾기 · 알림에 키 옆 이름) · check_master(키 모양·겹침·대장·끊긴 키 · 기계 요소 줄도 본문 결손 대조) · M01 서버(새 줄 키는 대장 다음 번호 · 키 못 고침) · 화면 칸 이름만 맞춤
- 로직 일괄 시험 계산 1,351 줄 — 옮기기 전후 줄마다 결과·금액 같음
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
114 lines
3.0 KiB
Python
114 lines
3.0 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 = "", unlinked: bool = False) -> dict:
|
|
return _call(store.rows, file, page, size, q, unlinked)
|
|
|
|
|
|
@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(key: str, book: 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("/pick")
|
|
def get_pick(kind: str, q: str = "", limit: int = 50) -> dict:
|
|
return _call(store.pick, kind, 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])}
|