Merge remote-tracking branch 'origin/dev' into sub_laptop_1
This commit is contained in:
@@ -278,6 +278,29 @@ def _columns(kind: str, all_rows: list[dict[str, Any]]) -> list[str]:
|
||||
return list(keys)
|
||||
|
||||
|
||||
class SortError(ValueError):
|
||||
"""모르는 열로 세우라 함 — 조용히 원본 차례로 돌아가지 않음(브레인 ④)."""
|
||||
|
||||
|
||||
def _order_key(value: Any) -> tuple:
|
||||
"""섞인 칸도 세움 — 수 먼저, 그 뒤 글자."""
|
||||
if isinstance(value, bool):
|
||||
return (1, str(value))
|
||||
if isinstance(value, (int, float)):
|
||||
return (0, float(value))
|
||||
if isinstance(value, (dict, list)):
|
||||
return (1, json.dumps(value, ensure_ascii=False, default=str))
|
||||
return (1, str(value))
|
||||
|
||||
|
||||
def _sorted(rows: list[dict[str, Any]], column: str, desc: bool) -> list[dict[str, Any]]:
|
||||
"""⚠ 빈 칸은 오름·내림 **어느 쪽이든 끝** — 미공표 노임 14 · 손료계수 없는 기계 226 이
|
||||
첫 쪽을 통째로 채우면 표를 못 씀(브레인 ②)."""
|
||||
filled = [r for r in rows if r.get(column) not in (None, "")]
|
||||
empty = [r for r in rows if r.get(column) in (None, "")]
|
||||
return sorted(filled, key=lambda r: _order_key(r[column]), reverse=desc) + empty
|
||||
|
||||
|
||||
def notice(kind: str) -> list[str]:
|
||||
"""표 수준 알림 — 화면이 따로 가지면 문구가 두 벌이 됨(브레인 ③)."""
|
||||
return [_SCOPE_NOTICE] + ([_RATE_NOTICE] if kind == "rate" else [])
|
||||
@@ -349,7 +372,12 @@ def public(row: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
|
||||
def table(
|
||||
kind: str, page: int = 1, size: int = tables.DEFAULT_PAGE_SIZE, q: str = ""
|
||||
kind: str,
|
||||
page: int = 1,
|
||||
size: int = tables.DEFAULT_PAGE_SIZE,
|
||||
q: str = "",
|
||||
sort: str = "",
|
||||
desc: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
all_rows = rows(kind)
|
||||
columns = _columns(kind, all_rows)
|
||||
@@ -363,10 +391,16 @@ def table(
|
||||
if needle
|
||||
else all_rows
|
||||
)
|
||||
if sort: # ⚠ 거른 뒤에 세움 — 거꾸로 하면 차례가 깨짐(브레인 ③)
|
||||
if sort not in columns:
|
||||
raise SortError(f"없는 열로 세울 수 없음: {sort}")
|
||||
hits = _sorted(hits, sort, desc)
|
||||
size = max(1, min(size, tables.MAX_PAGE_SIZE))
|
||||
start = (max(page, 1) - 1) * size
|
||||
return {
|
||||
"columns": [column_meta(kind, c) for c in columns],
|
||||
# 못 고치는 칸(계산값)도 **세우기는 됨** — 화면은 이 목록으로 눌릴 제목을 가림
|
||||
"sortable": list(columns),
|
||||
"rows": [public(r) for r in hits[start : start + size]],
|
||||
"total": len(hits),
|
||||
**spec(kind, columns),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
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)
|
||||
GET /api/master-data/base-prices/{kind}?page=&size=&q=&sort=&desc= 기초단가 한 표(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]` 로 붙임.
|
||||
@@ -50,9 +50,17 @@ def _kind(kind: str) -> str:
|
||||
|
||||
@router.get("/base-prices/{kind}")
|
||||
def get_base_prices(
|
||||
kind: str, page: int = 1, size: int = tables.DEFAULT_PAGE_SIZE, q: str = ""
|
||||
kind: str,
|
||||
page: int = 1,
|
||||
size: int = tables.DEFAULT_PAGE_SIZE,
|
||||
q: str = "",
|
||||
sort: str = "",
|
||||
desc: int = 0,
|
||||
) -> dict:
|
||||
return base_prices.table(_kind(kind), page=page, size=size, q=q)
|
||||
try:
|
||||
return base_prices.table(_kind(kind), page=page, size=size, q=q, sort=sort, desc=bool(desc))
|
||||
except base_prices.SortError as error:
|
||||
raise HTTPException(status_code=400, detail=str(error)) from error
|
||||
|
||||
|
||||
@router.put("/base-prices/{kind}/{row_id:path}")
|
||||
|
||||
Reference in New Issue
Block a user