- 새 Labor_Detail.ts(mountLaborDetail · 차례 = 정보 → 담은 로직 → 자재목록 → 다이어그램 · 고칠 때마다 calc 를 뒤에서 · 최신 답만 · 같은 로직 · 자재 두 번 막음) - 새 Labor_Input.ts(담은 로직 줄 입력 모달 · 빈 칸 = 자동값 · 메모 · 수량 = 이은 칸 / 손 입력 / 면적산출에서 잇기 · 사라진 입력값 알림 · 자재 수량 모달) - 새 Labor_Rows.ts(작은 글 「지역 서울 · 수량 B6 = 12.5㎡」 · 빈 값 n 붉게 · 금액 칸 넷 두 줄(위 기준 · 아래 반영) · 합계 줄 · 보기 갈래는 손잡이 없이) - Labor_Api.ts: labor 문서 · calc 답 모양 · calcLabor · fetchLogicInputs · previewCombo 지움 - Labor_Diagram.ts: buildDiagramData → buildLaborDiagram(줄 입력값 + calc · 수량 있으면 반영 · 없으면 기준 표기 · 자재목록 카드 하나 → 재료비 · 알약 = 입력 모달) - 미리 보기 지움: Labor_Preview.ts · M02 preview 길 · Combo_Store.preview · 옛 조합 상세의 미리 보기 · 다이어그램 칸 - 시험: test_m02_labor_diagram 새 모양 · test_m01_combo 미리 보기 시험은 같은 계산(combo_preview)을 바로 부름 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
222 lines
7.9 KiB
Python
222 lines
7.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 길과 같음.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
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_Copy as combo_copy
|
|
from M02_MasterTemplete import M02_MasterTemplete_Combo_Store as combos
|
|
from M02_MasterTemplete.M02_MasterTemplete_Router_Items import Layer, Session, _call, _root
|
|
|
|
router = APIRouter(prefix="/api/m02", tags=["M02 MasterTemplete Combos"])
|
|
|
|
|
|
class ComboBody(BaseModel):
|
|
combo: dict[str, Any]
|
|
판: str | None = None # 새로 만들 때 빈 글 = 지금 판 · 고칠 때 어긋나면 409
|
|
|
|
|
|
class ComboTreeBody(BaseModel):
|
|
"""`{op: add-folder|rename|move|delete, 경로?, 이름?, 새경로?, 키?, 판}` — 폴더 깊이 2."""
|
|
|
|
op: str
|
|
경로: list[str] | None = None
|
|
이름: str | None = None
|
|
새경로: list[str] | None = None
|
|
키: str | None = None
|
|
판: str
|
|
|
|
|
|
class TreeManyBody(BaseModel):
|
|
"""여러 개 옮기기 · 지우기(PLAN 30-3) — `ops` = 나무 고침(`ComboTreeBody` 모양) · 저장 한 번."""
|
|
|
|
판: str
|
|
ops: list[dict[str, Any]]
|
|
|
|
|
|
class PasteBody(BaseModel):
|
|
"""층 사이 복사(PLAN 30-3) — 원본 층(`from_layer` · `from_owner`)의 조합 `키` · 폴더 `폴더` →
|
|
이 층 `경로` 폴더에 새 키로. `판` = 이 층 조합 표 판."""
|
|
|
|
from_layer: Layer
|
|
from_owner: str | None = None
|
|
키: list[str] = []
|
|
폴더: list[list[str]] = []
|
|
경로: list[str] = []
|
|
판: str = ""
|
|
|
|
|
|
@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.post("/layers/{layer}/combos/tree")
|
|
def post_tree(
|
|
layer: Layer,
|
|
body: ComboTreeBody,
|
|
owner: str | None = None,
|
|
session: Session = Depends(verify_session),
|
|
):
|
|
folder = _root(session, layer, owner, write=True)
|
|
return _call(folder, combos.edit_tree, body.model_dump())
|
|
|
|
|
|
@router.post("/layers/{layer}/combos/tree-many")
|
|
def post_tree_many(
|
|
layer: Layer,
|
|
body: TreeManyBody,
|
|
owner: str | None = None,
|
|
session: Session = Depends(verify_session),
|
|
):
|
|
folder = _root(session, layer, owner, write=True)
|
|
return _call(folder, combos.edit_tree_many, body.model_dump())
|
|
|
|
|
|
@router.post("/layers/{layer}/combos/paste")
|
|
def post_paste(
|
|
layer: Layer,
|
|
body: PasteBody,
|
|
owner: str | None = None,
|
|
session: Session = Depends(verify_session),
|
|
):
|
|
"""원본 층 읽기 권한 · 이 층 쓰기 권한(M02_Layer_Access) — 마스터 붙이기 = 시스템 관리자."""
|
|
source = _call(_root(session, body.from_layer, body.from_owner), lambda: combos._load()[0])
|
|
folder = _root(session, layer, owner, write=True)
|
|
return _call(folder, combo_copy.paste, source, body.model_dump())
|
|
|
|
|
|
# ── 담을 로직(읽기만) — 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(","))
|