From 399b2193714336e7dc29da697a495a15fd92e60c Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 15 Sep 2026 19:09:35 +0900 Subject: [PATCH] =?UTF-8?q?feat(z01):=20=EC=A4=84=EC=97=90=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=97=B4=EC=9D=80=20=E3=80=8C=E2=80=94=E3=80=8D=20?= =?UTF-8?q?=C2=B7=20columns=20=EB=B0=96=20=EC=97=B4=EC=9D=80=20=EC=95=88?= =?UTF-8?q?=20=EA=B7=B8=EB=A6=BC=20=C2=B7=20main.py=20=EC=97=90=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EC=A0=84=EC=9A=A9=20=EB=B3=B4=ED=98=B8=20=EB=AF=B8=EB=A6=AC=20?= =?UTF-8?q?=EB=91=A0(=EB=9D=BC=EC=9A=B0=ED=84=B0=20import=C2=B7=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EB=91=90=20=EC=A4=84=EC=9D=80=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EC=9D=B4=20=EC=98=AC=EB=9D=BC=EC=98=A4=EB=A9=B4=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=ED=92=82=20=E2=80=94=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20import=20=EB=8A=94=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EB=A5=BC=20=EB=AA=BB=20=EB=9D=84=EC=9B=80)(=EB=B8=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq --- Z01_MasterData/Z01_MasterData_UI_Cells.ts | 7 +++++++ Z01_MasterData/Z01_MasterData_UI_Page.ts | 5 ++--- main.py | 7 +++++++ resources/tester/test_z01_master_cells.py | 9 ++++++++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Z01_MasterData/Z01_MasterData_UI_Cells.ts b/Z01_MasterData/Z01_MasterData_UI_Cells.ts index 7669fe5e..7bc9a47f 100644 --- a/Z01_MasterData/Z01_MasterData_UI_Cells.ts +++ b/Z01_MasterData/Z01_MasterData_UI_Cells.ts @@ -33,6 +33,13 @@ export function cellText(value: unknown, unit?: string | null): string { return String(value); } +/** 한 줄의 칸들 — columns 차례대로만(columns 에 없는 열은 안 그림) · 줄에 그 열이 없으면 「—」. */ +export function rowCells(row: Record, columns: MasterColumn[]): string[] { + return columns.map((column) => + Object.hasOwn(row, column.key) ? cellText(row[column.key], column.unit) : "—", + ); +} + export function pageCount(total: number, size: number): number { return Math.max(1, Math.ceil(total / size)); } diff --git a/Z01_MasterData/Z01_MasterData_UI_Page.ts b/Z01_MasterData/Z01_MasterData_UI_Page.ts index 7d8fd84c..eccef02b 100644 --- a/Z01_MasterData/Z01_MasterData_UI_Page.ts +++ b/Z01_MasterData/Z01_MasterData_UI_Page.ts @@ -27,7 +27,7 @@ import { type MasterRows, type MasterTable, } from "./Z01_MasterData_Api_Fetch"; -import { cellText, columnTitle, pageCount, shownColumns } from "./Z01_MasterData_UI_Cells"; +import { columnTitle, pageCount, rowCells, shownColumns } from "./Z01_MasterData_UI_Cells"; import "./Z01_MasterData_UI_Style.css"; const PAGE_SIZE = 50; @@ -229,8 +229,7 @@ function buildRowsView(): RowsView { const body = el("tbody"); for (const row of last.rows) { const tr = el("tr"); - for (const column of columns) { - const text = cellText(row[column.key], column.unit); + for (const text of rowCells(row, columns)) { tr.append(el("td", { text, attrs: { title: text } })); } body.append(tr); diff --git a/main.py b/main.py index 45f4e487..07cf9a3b 100644 --- a/main.py +++ b/main.py @@ -79,6 +79,7 @@ from common_util.common_util_audit import note_api_call, record_call_burst from common_util.common_util_auth import ( require_company, require_project_access, + require_system_admin, verify_session, ) @@ -654,6 +655,12 @@ app.include_router(b09_progress_router, dependencies=protected_with_company) app.include_router(b09_edits_router, dependencies=protected_with_company) app.include_router(b09_factors_router, dependencies=protected_with_company) app.include_router(b09_material_prices_router, dependencies=protected_with_company) +# Z01 마스터 데이터 — 회사·프로젝트가 아니라 시스템 관리자만 본다(2026-09-15 브레인 Z01). +# ⚠ 라우터 모듈(랩탑 메인)이 아직 없음 — 없는 것을 import 하면 서버가 안 뜸. +# 올라오면 아래 두 줄 주석을 풂. +system_admin_only = [Depends(verify_session), Depends(require_system_admin)] +# from Z01_MasterData.Z01_MasterData_Router import router as z01_master_data_router +# app.include_router(z01_master_data_router, dependencies=system_admin_only) # 개발 전용 잠금 해제 — 다른 라우터와 **같은 보호**를 받는다(로그인·회사·프로젝트 접근). # 그 위에 서버가 환경까지 한 번 더 본다. app.include_router(dev_unlock_router, dependencies=protected_with_company) diff --git a/resources/tester/test_z01_master_cells.py b/resources/tester/test_z01_master_cells.py index 82c485e1..e9433950 100644 --- a/resources/tester/test_z01_master_cells.py +++ b/resources/tester/test_z01_master_cells.py @@ -4,6 +4,8 @@ ② `hidden` 열(내부 id · sha · 생성시각)은 기본 숨김 · 「숨긴 열 보기」면 보임 ③ 단위가 있는 열의 수는 천 단위 쉼표(소수 자리는 자르지 않음) · 단위 없는 수(연도 등)는 그대로 ④ 쪽 수는 줄이 없어도 1 + ⑤ columns 차례가 열 차례 — rows 에 columns 에 없는 열이 섞여도 안 그림 · columns 에 있는데 + 줄에 그 열이 없으면 「—」(값이 null 이면 빈칸) (2026-09-15 브레인) TS 를 실제로 돌린다(`test_b06_berm_review_info` 와 같은 방식). """ @@ -19,7 +21,9 @@ SOURCE = PROJECT_ROOT / "Z01_MasterData" / "Z01_MasterData_UI_Cells.ts" _RUNNER = """ import { writeFileSync } from "node:fs"; -import { cellText, columnTitle, pageCount, shownColumns } from "./Z01_MasterData_UI_Cells.js"; +import { + cellText, columnTitle, pageCount, rowCells, shownColumns, +} from "./Z01_MasterData_UI_Cells.js"; const columns = [ { key: "id", label: "id", unit: null, hidden: true }, @@ -40,6 +44,7 @@ writeFileSync(process.argv[2], JSON.stringify({ cellText("보통인부", null), ], pages: [pageCount(0, 50), pageCount(6999, 50), pageCount(50, 50)], + row: rowCells({ extra: "x", daily_wage_krw: 250000, occupation_name: null }, columns), })); """ @@ -80,3 +85,5 @@ def test_칸_글자와_숨김_열(tmp_path: Path) -> None: assert got["titles"] == ["id", "직종명", "일 노임 (원)"] assert got["cells"] == ["", "250,000", "1.23456", "2026", '{"a":1}', "예", "보통인부"] assert got["pages"] == [1, 140, 1] + # columns 차례 · id 는 줄에 없어 「—」 · null 은 빈칸 · extra 는 안 그림 + assert got["row"] == ["—", "", "250,000"]