feat(z01): 기초단가 다섯 고치기 — 노임·기계·자재·유가·요율을 kind 마다 한 표(자료 제 열쇠 @id · 판 기준일 칸) · 고친 값은 덮개 resources/data_master_override/{kind}.json 에만(원본은 읽기만) · 원본 바뀐 덮개는 덮개 값 유지 + source_changed · 주인 없음은 버리지 않고 모아 보기(서버 정렬 · 거름 · 쪽)와 한꺼번에 비우기 · 기계 시간당 단가는 계산값으로 잠그고 산출근거(@formula) · 노임·유가 덮개가 기계 계산에 닿음 · B09 hourly_cost_of 와 대조(브레인 계약 · 사용자 지시)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
@@ -1,19 +1,32 @@
|
||||
"""Z01 마스터 데이터 라우터 — 읽기 전용(고치기는 다음 차례 · 2026-09-15 브레인).
|
||||
"""Z01 마스터 데이터 라우터 — 읽기 + 기초단가 고치기(2026-09-15 브레인).
|
||||
|
||||
GET /api/master-data/tree 갈래 → 파일 → 표
|
||||
GET /api/master-data/rows?file=&table=&page=&size=&q= 표 줄(쪽 나누기 · 검색)
|
||||
GET /api/master-data/base-prices/{kind}?page=&size=&q= 기초단가 한 표(labor|machine|material|oil|rate)
|
||||
PUT /api/master-data/base-prices/{kind}/{row_id} {values:{열:값}} → 덮개에만 씀 · null = 되돌리기
|
||||
GET /api/master-data/overrides 고친 것·원본 바뀐 것·주인 없는 것(서버 정렬)
|
||||
⚠ 권한은 등록하는 쪽(`main.py` · 랩탑 서브)이 `dependencies=[verify_session, require_system_admin]` 로 붙임.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from common_util.common_util_auth import verify_session
|
||||
from Z01_MasterData import Z01_MasterData_BasePrices as base_prices
|
||||
from Z01_MasterData import Z01_MasterData_Tables as tables
|
||||
|
||||
router = APIRouter(prefix="/api/master-data", tags=["Z01 MasterData"])
|
||||
|
||||
|
||||
class BasePriceEdit(BaseModel):
|
||||
values: dict[str, Any]
|
||||
|
||||
|
||||
@router.get("/tree")
|
||||
def get_tree() -> dict:
|
||||
return tables.tree()
|
||||
@@ -27,3 +40,44 @@ def get_rows(
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="없는 파일이나 표입니다.")
|
||||
return result
|
||||
|
||||
|
||||
def _kind(kind: str) -> str:
|
||||
if kind not in base_prices.KINDS:
|
||||
raise HTTPException(status_code=404, detail=f"없는 기초단가: {kind}")
|
||||
return kind
|
||||
|
||||
|
||||
@router.get("/base-prices/{kind}")
|
||||
def get_base_prices(
|
||||
kind: str, page: int = 1, size: int = tables.DEFAULT_PAGE_SIZE, q: str = ""
|
||||
) -> dict:
|
||||
return base_prices.table(_kind(kind), page=page, size=size, q=q)
|
||||
|
||||
|
||||
@router.put("/base-prices/{kind}/{row_id:path}")
|
||||
def put_base_price(
|
||||
kind: str, row_id: str, body: BasePriceEdit, session: dict = Depends(verify_session)
|
||||
) -> JSONResponse:
|
||||
status, payload = base_prices.edit(_kind(kind), row_id, body.values, session.get("user_id"))
|
||||
if status != 200:
|
||||
raise HTTPException(status_code=status, detail=payload["message"])
|
||||
return JSONResponse(content=payload)
|
||||
|
||||
|
||||
@router.get("/overrides")
|
||||
def get_overrides(
|
||||
kind: str = "", state: str = "", page: int = 1, size: int = tables.DEFAULT_PAGE_SIZE
|
||||
) -> dict:
|
||||
return base_prices.override_items(kind and _kind(kind), state, page=page, size=size)
|
||||
|
||||
|
||||
class OverrideClear(BaseModel):
|
||||
kind: str
|
||||
state: Literal["edited", "source_changed", "orphan"]
|
||||
|
||||
|
||||
@router.post("/overrides/clear")
|
||||
def clear_overrides(body: OverrideClear, _session: dict = Depends(verify_session)) -> dict:
|
||||
"""한 kind 의 그 state 덮개를 한꺼번에 뺌 — kind·state 둘 다 있어야 함(통째로 비우기 막음)."""
|
||||
return {"removed": base_prices.clear_overrides(_kind(body.kind), body.state)}
|
||||
|
||||
Reference in New Issue
Block a user