Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Router_Combos.py
T
eomsangdonandClaude Opus 5.5 07ac61d4d6 merge(M02): origin/dev 받음 — 일위대가 조합 길 충돌 풂(39장)
- Labor_Api.ts · Router_Combos.py = 나무 길(sub5) · 미리 보기 길(sub2) 둘 다 지운 꼴 · 시스템 관리자 문 _door(sub4) 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
2026-10-04 17:52:17 +09:00

175 lines
6.9 KiB
Python

"""M02 일위대가 조합 층 API(PLAN 27-2a) — `/api/m02/layers/{layer}/combos`(옛 `/api/m01/combo*`).
⚠ 등록은 `main.py`(로그인만) · 층 권한은 `M02_Layer_Access`(`Router_Items._root`) 한 규칙:
system 읽기 = 로그인 누구나 · 쓰기 = 시스템 관리자(마스터 조합 · 키 UA)
company 읽기 = 같은 회사 · 쓰기 = 회사 관리자(키 UC{회사}-######)
personal 읽기 · 쓰기 = 본인(키 UP{사용자}-######)
저장소 = `M02_MasterTemplete_Combo_Store`.
담을 로직 고르기 · 일위대가 입력 모달이 읽는 단가산출 로직(M01 길은 시스템 관리자 전용)은
`/api/m02/logic-lib/…` 읽기만(로그인) — 모양은 M01 길과 같음.
39-6 — 일위대가 조합 = 실무문서 · 조합 길 전부 시스템 관리자만(`_door` · 담을 로직 읽기 뺌).
"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from common_util.common_util_auth import verify_session
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Labor_Inputs as labor_inputs
from M02_MasterTemplete import M02_Layer_Access as access
from M02_MasterTemplete import M02_MasterTemplete_Combo_Store as combos
from M02_MasterTemplete.M02_MasterTemplete_Router_Items import Layer, Session, _call, _root
def _door(request: Request, session: Session = Depends(verify_session)) -> None:
"""일위대가 조합 = 실무문서(39-6) — 조합 길 전부 시스템 관리자만 · 담을 로직 읽기는 로그인만."""
if "/logic-lib/" not in request.url.path and not access.is_system_admin(session):
raise HTTPException(status_code=403, detail="실무문서(일위대가 조합)는 시스템 관리자만 봄.")
router = APIRouter(
prefix="/api/m02", tags=["M02 MasterTemplete Combos"], dependencies=[Depends(_door)]
)
class ComboBody(BaseModel):
combo: dict[str, Any]
판: str | None = None # 새로 만들 때 빈 글 = 지금 판 · 고칠 때 어긋나면 409
@router.get("/combos/all")
def get_all(session: Session = Depends(verify_session)) -> list[dict]:
"""고르기(use-combo · 갈라 내기) — 세 층 `[{layer, owner, 판, 폴더, 줄}]` · 회사 없음 빈 층."""
out = []
for layer in ("system", "company", "personal"):
who = None if layer == "system" else access.own_owner(session, layer)
if layer != "system" and who is None:
out.append({"layer": layer, "owner": None, "판": "", "폴더": [], "줄": []})
continue
got = _call(_root(session, layer, who), combos.listing)
out.append({"layer": layer, "owner": who, **got})
return out
def _where(key: str) -> tuple[str, str | None]:
got = combos.where(key)
if got is None:
raise HTTPException(status_code=404, detail=f"없는 조합 「{key}」")
return got
@router.get("/combos/key/{key}")
def get_by_key(key: str, session: Session = Depends(verify_session)) -> dict:
"""키 하나(어느 층이든 · 그 층 읽기 권한) — `{layer, owner, 판, combo, 줄, …}`."""
layer, owner = _where(key)
return {"layer": layer, "owner": owner, **_call(_root(session, layer, owner), combos.one, key)}
@router.get("/combos/key/{key}/inputs")
def get_inputs_by_key(key: str, session: Session = Depends(verify_session)) -> dict:
"""조합 알약(어느 층이든) — 구조물 조합 면적산출 연결이 부름."""
layer, owner = _where(key)
return _call(_root(session, layer, owner), combos.pills, key)
@router.get("/layers/{layer}/combos")
def get_combos(layer: Layer, owner: str | None = None, session: Session = Depends(verify_session)):
return _call(_root(session, layer, owner), combos.listing)
@router.get("/layers/{layer}/combos/{key}")
def get_combo(
layer: Layer, key: str, owner: str | None = None, session: Session = Depends(verify_session)
):
return _call(_root(session, layer, owner), combos.one, key)
@router.get("/layers/{layer}/combos/{key}/inputs")
def get_combo_inputs(
layer: Layer, key: str, owner: str | None = None, session: Session = Depends(verify_session)
):
"""조합 알약 — 담은 로직 결과단위 분모마다 물량 하나(M02 면적산출이 받음)."""
return _call(_root(session, layer, owner), combos.pills, key)
@router.post("/layers/{layer}/combos")
def post_combo(
layer: Layer,
body: ComboBody,
owner: str | None = None,
session: Session = Depends(verify_session),
):
folder = _root(session, layer, owner, write=True)
return _call(folder, combos.create, body.combo, body.판 or None)
@router.put("/layers/{layer}/combos/{key}")
def put_combo(
layer: Layer,
key: str,
body: ComboBody,
owner: str | None = None,
session: Session = Depends(verify_session),
):
folder = _root(session, layer, owner, write=True)
return _call(folder, combos.edit, key, body.combo, body.판 or "")
@router.delete("/layers/{layer}/combos/{key}")
def delete_combo(
layer: Layer,
key: str,
판: str = "",
owner: str | None = None,
session: Session = Depends(verify_session),
):
return _call(_root(session, layer, owner, write=True), combos.delete, key, 판)
@router.delete("/practice")
def delete_practice(판: str = "", session: Session = Depends(verify_session)) -> dict:
"""실무문서 통째 지우기(PLAN 39-2) — 시스템 관리자만 · 마스터 조합 표 줄을 모두 비움 ·
구조물이 쓰는 조합이 있으면 409."""
if not access.is_system_admin(session):
raise HTTPException(status_code=403, detail="실무문서는 시스템 관리자만")
return _call(_root(session, "system", None, write=True), combos.drop_practice, 판)
# ── 담을 로직(읽기만) — M01 `/logics` · `/subs?group=로직` · `/logic` · `/logic/auto` 모양 ──
def _read(fn, *args):
try:
return fn(*args)
except m01.StoreError as e:
raise HTTPException(status_code=e.status, detail=e.detail) from e
@router.get("/logic-lib/logics", dependencies=[Depends(verify_session)])
def get_logics() -> dict:
return {"logics": _read(m01.logics, "", "", "", None)}
@router.get("/logic-lib/subs", dependencies=[Depends(verify_session)])
def get_logic_subs() -> dict:
return _read(m01.subs, "", "로직")
@router.get("/logic-lib/logic", dependencies=[Depends(verify_session)])
def get_logic(key: str) -> dict:
return _read(m01.logic, key)
@router.get("/logic-lib/auto", dependencies=[Depends(verify_session)])
def get_logic_auto(key: str) -> dict:
"""견본 입력 — 미리 보기용 자동값."""
return _read(m01.auto, key)
@router.get("/logic-lib/inputs", dependencies=[Depends(verify_session)])
def get_logic_inputs(keys: str = "") -> dict:
"""일위대가 입력 모달 — 로직 키들(쉼표)의 입력 목록 + 자동값(`M02_Labor_Inputs`)."""
return _read(labor_inputs.logic_inputs, keys.split(","))