Files
Aislo/M02_MasterTemplete/M02_Labor_Router.py
T
eomsangdonandClaude Opus 5.5 a22af84b62 feat(785-5): 만들기 · 길 — 확인한 고른 것 → SC · SK · 장부 · 새 M02_Hopyo_Router · 판정 · 검산 · 옮기기
- 새 M02_Hopyo_Router — 옛 hopyo 길 모두 M02_Labor_Router 에서 옮김(옛 화면 785-7 까지 돎) · 새 길 hopyo-judge 목록 · 호표 · 블록 · hopyo/{키}/check · hopyo/{키}/move · main 등록 · 시동 warm · Fit.ensure 걷음 · 올림 때 엑셀 사전(Row.lexicon) · 블록 풀이 뒤 셈 · 끝내기 때 저장본 지움
- 새 M02_Hopyo_Judge — 1차 줄 판(Row) + 단산 줄 2차 풀이(Clause → Solve) · 엑셀 칸(이름 · 규격 · 단위 · 단가) · 블록 금액 · 절 몫 · 블록 풀이 파생 저장본(HP 폴더 _derived · 입력 지문 · 지문 다를 때만) · judged(folder, key) · 검산(입력값 = 엔진이 쓴 칸 모두 · 지역 · 자재지역 · 조종원직종 비움 · 맡은 절 단위 줄 · 몫과 품 대조 · 금액 = 일위대가 로직 줄과 같은 셈)
- 새 M02_Hopyo_Make — 고른 것 → SC 줄(같은 별칭 · 줄 본뜬 {호표, 차례, 행} · 수량 = 엑셀 × 몫 × 배 · 같은 기초 둘째 = 한 줄 SC 사본 · 별도계상 자재 · 이음 · 율 · 고정 · 참조) · SK 더한줄(바탕 없음 · SkUnit.adds · Sangeun.Maker.write) · 하위 먼저 · 입력 필요 · 단위 환산 못 정함 = 그 줄(블록)만 막힘 · 장부 고른 것 꼴 짝 · 이미 옮긴 것 = 덮음 없으면 409(남의 SC 덮지 않음) · 확인한 줄 몸(Lines) = Build 에서 옮김
- Build 나눔 — 모두 고정 길 · Builder · _order · _origins · build_all 그대로(B09 빌림) · 확인한 줄 몸 → Make.Lines
- SkUnit · Sangeun — SkPatch · Fit · Basis 부름 걷음(기계경비 기초 차례 SkUnit 으로 · 근거 꼬리표 = Clause.basis · 맞춤 셈 409 없앰 · 재료 고름 MO 됨) · Kids 가 _same_mark 가짐
- 시험 새 test_642_5_make(판정 · 검산 · 옮기기 · 예시 1 · 2 · 3 · 덮음 409 · 막는 길) · test_m02_hopyo_excel 마당에 새 Router 더함 · HOPYO_MAX_BYTES 자리(길 옮긴 까닭)
- 낡은 시험 고침: test_718_1 · test_729 「맞춤 셈 전 409」 뺌(맞춤 뒤 셈 안 기다림) · test_628 맞춤셈 캐시 넣기 뺌(근거 = 조항 색인) · test_729 근거 없음 꼴 · 고정 이음 유가 칸(785-3 LINKS 늘어난 까닭)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:13:53 +09:00

190 lines
7.3 KiB
Python

"""M02 일위대가 계산 · 알약 길(PLAN 39-1a · 설계_정함 1장) — `/api/m02/layers/{층}/labor/{열}/…`.
문서 읽기 · 쓰기는 양식 길(`GET/PUT /api/m02/layers/{층}/templates/labor/{열}`) 그대로 ·
여기는 저장하지 않는 셈만 + 서고(단가승격 · 사전조합 · 50-3) 새로 · 복사(키는 서버가 줌 · 고침 ·
지우기는 양식 길 그대로) + 개인 자재 목록 · 추가(148-2 · 개인 층만 · 품번은 서버) +
서고 나무 자유 폴더(158-1 · 716-4 `{종류}-tree` · `M02_Precombo_Folders` · 읽기 = 층 읽기 ·
고침 = 층 쓰기) +
일위대가 실무자료 길 = `M02_Hopyo_Router`(785-5).
층 권한 = `Router_Items._root`(`M02_Layer_Access` 한 벌).
⚠ 등록은 `main.py`(로그인만).
"""
from __future__ import annotations
from typing import Any, Literal
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from common_util.common_util_auth import verify_session
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_Layer_Access as access
from M02_MasterTemplete import M02_Library_Ask as library_ask
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_Material_Own as own
from M02_MasterTemplete import M02_Precombo_Folders as pre_folders
from M02_MasterTemplete.M02_MasterTemplete_Router_Items import (
Layer,
Session,
TreeBody,
_call,
_root,
)
from M02_MasterTemplete.M02_MasterTemplete_Router_Items import (
PasteBody as TreePasteBody,
)
router = APIRouter(prefix="/api/m02", tags=["M02 MasterTemplete Labor"])
class CalcBody(BaseModel):
"""`doc` = 저장 전 화면 문서(없으면 저장본) · `수량` = {알약 키: 값}(없으면 저장된 면적산출)."""
doc: dict[str, Any] | None = None
수량: dict[str, Any] | None = None
def _doc(column: str, sent: dict[str, Any] | None) -> dict[str, Any]:
got = sent if sent is not None else store.read("labor", column)["문서"]
return labor.clean({**got, "열": column})
def _calc(column: str, body: CalcBody) -> dict[str, Any]:
doc = _doc(column, body.doc)
return {**labor.calc(doc, body.수량), "알약": labor.pills(doc)}
def _pills(column: str) -> dict[str, Any]:
return {"열": column, "알약": labor.pills(_doc(column, None))}
@router.post("/layers/{layer}/labor/{column}/calc")
def calc_labor(
layer: Layer,
column: str,
body: CalcBody,
owner: str | None = None,
session: Session = Depends(verify_session),
) -> dict:
"""줄마다 기준 · 반영 · 합 + 알약(저장 안 함 · 화면은 최신 응답만)."""
return _call(_root(session, layer, owner), _calc, column, body)
@router.get("/layers/{layer}/labor/{column}/pills")
def labor_pills(
layer: Layer,
column: str,
owner: str | None = None,
session: Session = Depends(verify_session),
) -> dict:
"""면적산출 알약 — 로직 물량 알약 「이름|단위」 + 자재 알약 「자재|MT키」(저장본)."""
return _call(_root(session, layer, owner), _pills, column)
class LibraryBody(BaseModel):
"""새로 = `문서` · 복사 = `본뜬` {층, 주인, 키}(원본 읽기 권한 · 못 읽는 참조는 같이 복사) ·
새로 같은 내용 409 → `새로` = 그래도 새로 · `씀` = 그 키(733-2 · `M02_Library_Ask`)."""
문서: dict[str, Any] | None = None
본뜬: dict[str, Any] | None = None
새로: bool = False
씀: str | None = None
def _copy(kind: str, doc: dict[str, Any], source: refs.Here, ref: dict) -> dict[str, Any]:
return refs.create(kind, refs.carry(refs.origin_mark(doc, ref), source))
@router.post("/layers/{layer}/library/{kind}")
def new_library(
layer: Layer,
kind: Literal["precombo", "sangeun"],
body: LibraryBody,
owner: str | None = None,
session: Session = Depends(verify_session),
) -> dict:
"""서고 새로 · 복사 — 키는 서버가 그 층 다음 번호(SP · SC) · 답 `이름` = 새 키."""
folder = _root(session, layer, owner, write=True)
if body.본뜬 is None:
return _call(folder, library_ask.create, kind, body.문서 or {}, body.새로, body.씀)
src = body.본뜬
if src.get("층") not in ("system", "company", "personal"):
raise HTTPException(status_code=400, detail="본뜬 층이 틀림")
got = access.grant(session, src["층"], src.get("주인")) # 원본 읽기 권한
ref = {"층": got.layer, "주인": got.owner, "키": str(src.get("키") or "")}
if refs.kind_of(ref["키"]) != kind:
raise HTTPException(status_code=400, detail="본뜬 종류가 틀림")
doc = refs.read_ref(ref) if refs._KEY.match(ref["키"]) else None
if doc is None:
raise HTTPException(status_code=404, detail=f"없는 {refs.TITLE[kind]} 「{ref['키']}」")
return _call(folder, _copy, kind, doc, (got.layer, got.owner), ref)
@router.get("/layers/{layer}/{kind}-tree")
def library_tree(
layer: Layer,
kind: Literal["precombo", "sangeun"],
owner: str | None = None,
session: Session = Depends(verify_session),
) -> dict:
"""서고 나무 폴더(일위대가 · 단가산출) — `{판, 폴더: [경로 …], 자리: {키: 경로}}` ·
자리 없는 문서 = 뿌리."""
return _call(_root(session, layer, owner), pre_folders.view, kind)
@router.post("/layers/{layer}/{kind}-tree")
def edit_library_tree(
layer: Layer,
kind: Literal["precombo", "sangeun"],
body: TreeBody,
owner: str | None = None,
session: Session = Depends(verify_session),
) -> dict:
"""새 폴더 · 이름 바꾸기 · 옮기기 · 지우기(폴더 = 안에 든 문서째 · 쓰는 곳 있으면 409) ·
여럿 = `대상` → 새 나무 · 판 다르면 409."""
folder = _root(session, layer, owner, write=True)
return _call(folder, pre_folders.edit, body.model_dump(), kind, session)
@router.post("/layers/{layer}/{kind}-tree/paste")
def paste_library_tree(
layer: Layer,
kind: Literal["precombo", "sangeun"],
body: TreePasteBody,
owner: str | None = None,
session: Session = Depends(verify_session),
) -> dict:
"""복사한 층(읽기 권한) 문서 · 폴더 → 이 층(쓰기 권한) `경로` 아래 새 키 → 새 나무 + 알림."""
target = _root(session, layer, owner, write=True)
source = _root(session, body.from_layer, body.from_owner)
return _call(target, pre_folders.paste, source, body.대상, body.경로, kind)
@router.get("/layers/{layer}/materials")
def own_materials(
layer: Layer,
owner: str | None = None,
session: Session = Depends(verify_session),
) -> dict:
"""개인 자재 목록(148-2) — 개인 층만 · 회사 · 마스터 = 빈 목록."""
if layer != "personal":
return {"줄": []}
return {"줄": own.rows(_root(session, layer, owner))}
@router.post("/layers/{layer}/materials")
def new_material(
layer: Layer,
body: dict[str, Any],
owner: str | None = None,
session: Session = Depends(verify_session),
) -> dict:
"""신규 자재(148-2) — `{이름, 규격, 단위, 값}` → 새 줄(품번 `PM` + 6자리 서버 일련) ·
개인 층만."""
if layer != "personal":
raise HTTPException(status_code=400, detail="신규 자재는 개인 층에서만")
folder = _root(session, layer, owner, write=True)
return _call(folder, own.add, folder, body)