From 61d2ed59978e80027d76ca8cb9f1cfecf291d448 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 4 Oct 2026 17:56:49 +0900 Subject: [PATCH] =?UTF-8?q?refactor(M02):=20=EC=9E=AC=EB=A3=8C=EC=82=B0?= =?UTF-8?q?=EC=B6=9C=20=EC=8B=9C=ED=8A=B8=20=EC=A7=80=EC=9B=80=20=E2=80=94?= =?UTF-8?q?=20=EC=A2=85=EB=A5=98=20material=20=C2=B7=20=EC=9E=AC=EB=A3=8C?= =?UTF-8?q?=EB=B2=88=20SM=20=C2=B7=20=EC=A2=8C=EC=B8=A1=20=EC=A4=84=20?= =?UTF-8?q?=C2=B7=20=EC=A7=91=EA=B3=84=ED=91=9C=20=E3=80=8C=EC=9E=AC?= =?UTF-8?q?=EB=A3=8C=EC=82=B0=EC=B6=9C=E3=80=8D=20=EC=A4=84=20=C2=B7=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=C2=B7=20=EC=A0=84=EC=B2=B4=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=20=C2=B7=20=EB=B3=B5=EC=82=AC=20=EA=B8=B8=20=C2=B7=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EB=B3=B5=EC=82=AC?= =?UTF-8?q?=EB=B3=B8=20=E3=80=8C=EC=9E=AC=EB=A3=8C=E3=80=8D=20=C2=B7=20?= =?UTF-8?q?=EA=B3=A0=EB=A5=B4=EA=B8=B0=20=EB=AA=A8=EB=8B=AC=20=C2=B7=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=B8=B5=20=C2=B7=20fixture=20?= =?UTF-8?q?=C2=B7=20=EC=8B=9C=ED=97=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새 프로젝트 씨앗(KINDS)에서도 빠짐 · 새 열은 구조물 도면 · 면적산출만 만듦 - storage 층(회사 · 개인 · 프로젝트) material/ 파일은 지우지 않음(읽기만 끊음) - 장부 재료번 칸은 다음 집계표 [저장] 때 빠짐 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK --- M02_MasterTemplete/M02_Item_Copy.py | 11 +- M02_MasterTemplete/M02_Item_Paste.py | 2 +- M02_MasterTemplete/M02_Item_Personal.py | 6 +- M02_MasterTemplete/M02_Item_Snapshot.py | 2 +- M02_MasterTemplete/M02_Item_Source.py | 6 +- .../M02_MasterTemplete_Api_Fetch.ts | 20 +- M02_MasterTemplete/M02_MasterTemplete_Book.py | 2 +- .../M02_MasterTemplete_CardShell.ts | 2 +- .../M02_MasterTemplete_ItemCards.ts | 27 +- .../M02_MasterTemplete_ItemSource.ts | 4 +- .../M02_MasterTemplete_Items.py | 13 +- .../M02_MasterTemplete_Router.py | 7 +- .../M02_MasterTemplete_Router_Items.py | 5 +- .../M02_MasterTemplete_Router_Layers.py | 4 +- .../M02_MasterTemplete_Store.py | 34 +- .../M02_MasterTemplete_Structure.ts | 36 +- .../M02_MasterTemplete_UI_Main.ts | 38 +- .../M02_MasterTemplete_UI_Side.ts | 13 +- .../M02_Project_Items_Router.py | 6 +- M02_MasterTemplete/M02_Template_Layers.py | 11 +- resources/master_template/material/item1.json | 17 - resources/master_template/material/item2.json | 17 - resources/master_template/material/item3.json | 17 - .../m02_master_sample/material/c4.json | 36 - .../m02_master_sample/material/ms.json | 17 - .../m02_master_sample/material/pp_len.json | 17 - .../m02_master_sample/material/rv.json | 1425 ----------------- resources/tester/helper_m02_master_sample.py | 2 +- .../test_spreadsheet_saved_values.py | 4 +- .../spreadsheet/test_spreadsheet_store.py | 31 +- resources/tester/test_item_copy_complete.py | 2 +- resources/tester/test_m02_combo_branch.py | 2 +- resources/tester/test_m02_empty_master.py | 2 +- resources/tester/test_m02_item_keys.py | 4 +- resources/tester/test_m02_layers_store.py | 3 +- resources/tester/test_m02_structure.py | 16 +- resources/tester/test_m02_structure_combo.py | 4 +- resources/tester/test_m02_structure_panel.py | 16 +- resources/tester/test_m02_tree.py | 2 +- resources/tester/test_m02_tree_paste.py | 2 +- resources/tester/test_sheet_drawing_select.py | 8 +- .../test_structures_summary_snapshot.py | 9 +- resources/tester/ui/item-picker-data.test.ts | 10 +- ui_template/sheet/ui_template_sheet.ts | 12 +- ui_template/sheet/ui_template_sheet_ops.ts | 9 +- ui_template/sheet/ui_template_sheet_render.ts | 22 +- ui_template/sheet/ui_template_sheet_text.ts | 1 - ui_template/sheet/ui_template_sheet_types.ts | 2 +- ui_template/ui_template_item_picker_data.ts | 8 +- ui_template/ui_template_item_picker_detail.ts | 7 +- ui_template/ui_template_item_picker_edit.ts | 29 +- .../ui_template_item_picker_project.ts | 5 +- ui_template/ui_template_locale_m2.ts | 2 - 53 files changed, 198 insertions(+), 1811 deletions(-) delete mode 100644 resources/master_template/material/item1.json delete mode 100644 resources/master_template/material/item2.json delete mode 100644 resources/master_template/material/item3.json delete mode 100644 resources/tester/fixtures/m02_master_sample/material/c4.json delete mode 100644 resources/tester/fixtures/m02_master_sample/material/ms.json delete mode 100644 resources/tester/fixtures/m02_master_sample/material/pp_len.json delete mode 100644 resources/tester/fixtures/m02_master_sample/material/rv.json diff --git a/M02_MasterTemplete/M02_Item_Copy.py b/M02_MasterTemplete/M02_Item_Copy.py index 90c6e6ba7..e6f336de4 100644 --- a/M02_MasterTemplete/M02_Item_Copy.py +++ b/M02_MasterTemplete/M02_Item_Copy.py @@ -50,13 +50,12 @@ from M02_MasterTemplete.M02_Item_Source import ( KINDS = { "도면": "도면.json", "면적": "면적.json", - "재료": "재료.json", "입력연결": LINK_FILE, "입력값": VALUES_FILE, "고른자재": PICKS_FILE, } -#: 뼈대 검사에 쓰는 층 양식 종류(도면 · 면적산출 · 재료산출) -SKELETON = {"도면": "structure", "면적": "area", "재료": "material"} +#: 뼈대 검사에 쓰는 층 양식 종류(도면 · 면적산출) +SKELETON = {"도면": "structure", "면적": "area"} SEATS = ("면적", "재료", "선택", None) _MT = re.compile(r"^MT\d{6}$") @@ -190,13 +189,13 @@ def write_file( content: Any, recalc: Callable[[dict], dict] | None = None, ) -> dict[str, Any]: - """판이 같을 때만 씀 — 면적 · 재료는 `recalc`(서버 계산값)를 거친 것을 씀.""" + """판이 같을 때만 씀 — 면적은 `recalc`(서버 계산값)를 거친 것을 씀.""" target = folder_of(project_root, folder) check(kind, content) current = version_of(read_json(target / KINDS[kind])) if version != current: raise Stale(current) - if recalc is not None and kind in ("면적", "재료"): + if recalc is not None and kind == "면적": content = recalc(content) atomic_write_json(target / KINDS[kind], content) manifest = read_json(target / MANIFEST_NAME) or {} @@ -244,7 +243,7 @@ def _kept_picks(picks: Any, labor: dict[str, Any]) -> dict[str, Any]: def reapply(project_root: str | Path, folder: str) -> dict[str, Any]: """재적용 — 원본(층 · 마스터)을 다시 가져옴 · 입력값 · 고른자재는 같은 키면 살림. - 원본과 달라 덮인 파일(도면 · 면적 · 재료 · 입력연결)은 `덮임` 에 알림.""" + 원본과 달라 덮인 파일(도면 · 면적 · 입력연결)은 `덮임` 에 알림.""" target = folder_of(project_root, folder) manifest = read_json(target / MANIFEST_NAME) or {} values = read_json(target / VALUES_FILE) diff --git a/M02_MasterTemplete/M02_Item_Paste.py b/M02_MasterTemplete/M02_Item_Paste.py index ea4a01fa7..453dce6fb 100644 --- a/M02_MasterTemplete/M02_Item_Paste.py +++ b/M02_MasterTemplete/M02_Item_Paste.py @@ -2,7 +2,7 @@ 붙일 폴더(`dest`) 아래 새 항목 — 대상 층 새 키(SC · SP) · 열 `본뜬` = 원본(층 · 주인 · 키) · 같은 이름은 「이름 (2)」 · 폴더는 안의 항목 · 빈 폴더까지(폴더 이름이 겹치면 「폴더 (2)」). -파일(도면 · 면적 · 재료 · 입력 연결)은 `Items.copy_item` 이 옮김. 권한은 부르는 쪽 몫. +파일(도면 · 면적 · 입력 연결)은 `Items.copy_item` 이 옮김. 권한은 부르는 쪽 몫. """ from __future__ import annotations diff --git a/M02_MasterTemplete/M02_Item_Personal.py b/M02_MasterTemplete/M02_Item_Personal.py index 93ec208a9..80775a8d2 100644 --- a/M02_MasterTemplete/M02_Item_Personal.py +++ b/M02_MasterTemplete/M02_Item_Personal.py @@ -1,7 +1,7 @@ """프로젝트에서 개인 층으로 — [개인에 저장](save-as) · 프로젝트에서 새 항목(new) (PLAN 26-2b). -개인 층 새 항목 = 구조물집계표 새 열 하나 + 빈 도면 · 면적산출 · 재료산출(번호 · SS 키는 저장소가 -줌). [개인에 저장]은 구조물 복사본의 열 정의 · 도면 · 면적 · 재료 · 입력연결을 담음 — +개인 층 새 항목 = 구조물집계표 새 열 하나 + 빈 도면 · 면적산출(번호 · SS 키는 저장소가 +줌). [개인에 저장]은 구조물 복사본의 열 정의 · 도면 · 면적 · 입력연결을 담음 — 단가 묶음 · 입력값 · 고른자재는 프로젝트 몫이라 안 가져감. 쓰기는 지금 층 저장소 함수(`store.at` · `store.write` · 항목 연결 `write_file`) — 나무 고침(`M02_MasterTemplete_Tree`)과 같은 집계표 저장 한 줄기(경로 · 머리 줄 수 맞춤 · 번호 · SS 장부)를 탐(PLAN 26-2a). @@ -23,7 +23,7 @@ from M02_MasterTemplete import M02_Template_Layers as layers from M02_MasterTemplete.M02_Item_Source import LINK_FILE, read_json #: 복사본 파일 → 층 양식 종류 -PART_KINDS = (("도면.json", "structure"), ("면적.json", "area"), ("재료.json", "material")) +PART_KINDS = (("도면.json", "structure"), ("면적.json", "area")) def heads_of(path: list[str], name: str | None) -> list[str]: diff --git a/M02_MasterTemplete/M02_Item_Snapshot.py b/M02_MasterTemplete/M02_Item_Snapshot.py index e64d83e4a..3b9dfd3d9 100644 --- a/M02_MasterTemplete/M02_Item_Snapshot.py +++ b/M02_MasterTemplete/M02_Item_Snapshot.py @@ -1,6 +1,6 @@ """구조물 집계 항목 복사본 — 프로젝트 `templates/structures/{구조물 id}/` (PLAN 25-2b · 26-2b). -복사본은 **구조물마다 하나** — 같은 항목을 고른 구조물 둘도 각자 도면 · 면적 · 재료 · +복사본은 **구조물마다 하나** — 같은 항목을 고른 구조물 둘도 각자 도면 · 면적 · 일위대가 · 입력연결 · 입력값 · 고른자재 · 단가를 가짐(길이 · 소재 · 높이 · 옵션이 구조물마다 다름). 폴더 이름 = 구조물 id(`structure_id`) · 관 지점은 `pipe@측점`. diff --git a/M02_MasterTemplete/M02_Item_Source.py b/M02_MasterTemplete/M02_Item_Source.py index a03b87eba..8e7d1d64e 100644 --- a/M02_MasterTemplete/M02_Item_Source.py +++ b/M02_MasterTemplete/M02_Item_Source.py @@ -1,7 +1,7 @@ """집계 항목 원본 — 층(시스템 · 회사 · 개인)에서 항목 내용을 모음 (PLAN 21-1 · 25-1b · 25-2b). 모으는 것 = `{파일 이름: 내용}`: - `집계.json` SS 줄 + 열 정의 · `도면.json` SD · `면적.json` SA · `재료.json` SM · + `집계.json` SS 줄 + 열 정의 · `도면.json` SD · `면적.json` SA · `일위대가.json` UA 줄 + 담은 로직 줄(M01 시스템) · `입력연결.json` 층의 `inputs/<열id>.json`(없으면 빈 연결) · `단가.json` 계산에 닿는 마스터 줄 전부 @@ -20,8 +20,8 @@ from typing import Any from M02_MasterTemplete import M02_Template_Layers as layers TABLE_NAME = "구조물집계표" -#: 열 id → 스냅샷 파일(구조물 도면 · 면적산출 · 재료산출) -PART_FILES = (("structure", "도면.json"), ("area", "면적.json"), ("material", "재료.json")) +#: 열 id → 스냅샷 파일(구조물 도면 · 면적산출) +PART_FILES = (("structure", "도면.json"), ("area", "면적.json")) LINK_FILE = "입력연결.json" PRICE_FILE = "단가.json" LABOR_FILE = "일위대가.json" diff --git a/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts b/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts index 012a60ee2..d8e5b4aa0 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts @@ -8,8 +8,8 @@ import { API_BASE_URL } from "@config/config_frontend"; /** 층 이름 — 서버와 같은 글 */ export type Layer = "system" | "company" | "personal" | "project"; /** 서버 종류 — 화면의 「표 양식」 = table · 「도면 양식」 = drawing · 「구조물 도면」 = structure · - * 「면적산출」 = area · 「재료산출」 = material(둘 다 구조물집계표 열마다 한 통합문서) */ -export type Kind = "table" | "drawing" | "structure" | "area" | "material"; + * 「면적산출」 = area(구조물집계표 열마다 한 통합문서) */ +export type Kind = "table" | "drawing" | "structure" | "area"; export interface TemplateInfo { 종류: Kind; @@ -129,7 +129,7 @@ export const deleteLayerTemplate = ( /* --- 구조물 도면(종류 structure · 이름 = 구조물집계표 열 id) --- * 읽기 · 저장 · 지우기는 위 readTemplate · saveTemplate · deleteTemplate 에 "structure" */ -/** 구조물 도면 문서 — `도면` = 도면 양식과 같은 CAD 문서 · 면적산출 · 재료산출은 따로(같은 열 id) */ +/** 구조물 도면 문서 — `도면` = 도면 양식과 같은 CAD 문서 · 면적산출은 따로(같은 열 id) */ export interface StructureDoc { 양식: "구조물도면"; 종류: "structure"; @@ -143,19 +143,18 @@ export interface StructureDoc { export const listStructures = async (): Promise => (await listTemplates("system", null)).filter((t) => t.종류 === "structure"); -/** 새로 — 도번 자동 · 빈 도면 (+ 없으면 빈 면적산출 · 재료산출 통합문서) · 이미 있으면 StaleError */ +/** 새로 — 도번 자동 · 빈 도면 (+ 없으면 빈 면적산출 통합문서) · 이미 있으면 StaleError */ export const createStructure = (column: string): Promise => scoped(`/structures/${enc(column)}`, { method: "POST" }); -/** 한 열의 번호 — 도번 `SD` · 면적번 `SA` · 재료번 `SM` + 같은 8자리 +/** 한 열의 번호 — 도번 `SD` · 면적번 `SA` + 같은 8자리 * [종류][분류1][분류2][분류3](없는 쪽은 빈 글) */ export interface StructureNumbers { 도번: string; 면적번: string; - 재료번: string; } -/** `{열 id: {도번, 면적번, 재료번}}` — 구조물 열만(글 열 · 없는 열은 키 없음 = 「없음」) */ +/** `{열 id: {도번, 면적번}}` — 구조물 열만(글 열 · 없는 열은 키 없음 = 「없음」) */ export const fetchStructureNumbers = (): Promise> => scoped("/structures/numbers"); @@ -247,12 +246,7 @@ export const copyArea = async (column: string, from: string): Promise => { await scoped(`/area/${enc(column)}/copy?from=${enc(from)}`, { method: "POST" }); }; -/** 다른 열(`from`) 재료산출 내용(알약 연결 포함)을 받는 열(`column`)로 가져옴 — 재료번 · 열은 그대로 */ -export const copyMaterial = async (column: string, from: string): Promise => { - await scoped(`/material/${enc(column)}/copy?from=${enc(from)}`, { method: "POST" }); -}; - -/** 회사 · 개인 층의 집계표 항목(열 하나 + 도면 · 면적 · 재료 파일 + 자재조합 줄)을 내 개인 층으로 복사 — +/** 회사 · 개인 층의 집계표 항목(열 하나 + 도면 · 면적 파일)을 내 개인 층으로 복사 — * 새 SS 키로 만들고 `본뜬` 표시 · `fromOwner` = 회사 층 회사 · 개인 층 「회사/사용자」 */ export const copyItemToPersonal = (body: { fromLayer: Layer; diff --git a/M02_MasterTemplete/M02_MasterTemplete_Book.py b/M02_MasterTemplete/M02_MasterTemplete_Book.py index f87b7f2b4..319c4be00 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Book.py +++ b/M02_MasterTemplete/M02_MasterTemplete_Book.py @@ -8,7 +8,7 @@ 옛 `SS` 키는 맞출 때 새 모양으로 옮기고(숫자 여섯 그대로 · 겹치면 다음 번호) 옛 키는 줄 `옛키` 에 남김 — 프로젝트 복사본이 들고 있는 옛 키로도 찾음(`find`). 줄 = `{키, 열, 경로, 이름, 종류, 분류1~3(경로 앞 넷 — 옛 코드가 읽음), 단위, 일위대가, -도번, 면적번, 재료번, 바인딩, 펼침, 옛키?}`. `M02_MasterTemplete_Store.py` 가 커서 떼어 냄 — +도번, 면적번, 바인딩, 펼침, 옛키?}`. `M02_MasterTemplete_Store.py` 가 커서 떼어 냄 — 저장소는 `store.sync_combos` · `store.read_combos` · `store.find_combo` 이름으로 이 파일을 부름. """ diff --git a/M02_MasterTemplete/M02_MasterTemplete_CardShell.ts b/M02_MasterTemplete/M02_MasterTemplete_CardShell.ts index a62a07a1e..21fcf0f05 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_CardShell.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_CardShell.ts @@ -1,6 +1,6 @@ /* ============================================================================= * M02_MasterTemplete_CardShell.ts - * 항목 카드 공용 틀(PLAN 31-7) — 도면 정보 · 면적산출 · 재료산출 · 선택 입력(관리 화면 · 고르기 모달)이 + * 항목 카드 공용 틀(PLAN 31-7) — 도면 정보 · 면적산출 · 선택 입력(관리 화면 · 고르기 모달)이 * 내용만 넣음. 모양 · 높이 · 스크롤은 `M02_MasterTemplete_Structure.css`. * 머리 = 제목 줄(h3 · 제목 · 저장 안 한 점 ● · 오른쪽 단추 자리) + 한 줄 부제(번호 · 이름 · 넘치면 말줄임) * 본문 = 카드 높이를 채움 · 넘치면 카드 안 스크롤 · 상태 = 불러오는 중 · 실패 + [다시 불러오기] · 비었음 diff --git a/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts b/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts index fa329afec..b7460ac25 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts @@ -1,13 +1,13 @@ /* ============================================================================= * M02_MasterTemplete_ItemCards.ts - * 항목 카드 — 도면 정보 · 면적산출 · 재료산출(보기만 + [수정]) + 덧붙는 카드(선택 입력 …). + * 항목 카드 — 도면 정보 · 면적산출(보기만 + [수정]) + 덧붙는 카드(선택 입력 …). * M02 구조물 설계 컨테이너(`M02_MasterTemplete_Structure.ts`)에서 떼어 냄(계획서 25-2d) — 항목 고르기 * 모달(`ui_template_item_picker_detail.ts`)이 **같은 카드**를 쓰려고. 문서를 어디서 읽는지는 * 자료 자리(`ItemSource`)가 정함 · 카드는 몰라도 됨. * 카드 겉 틀 · 제목 줄 · 부제 · [저장] · ? · 점(●) · 상태 글은 공용 틀(`M02_MasterTemplete_CardShell` · 31-7). - * 면적산출 · 재료산출 = 스프레드시트 · 부품은 카드를 열 때만 `import()`(다른 페이지 번들에 안 섞임). + * 면적산출 = 스프레드시트 · 부품은 카드를 열 때만 `import()`(다른 페이지 번들에 안 섞임). * 덧붙는 카드는 `extraCards` 로 끼움 — 선택 입력 카드(`M02_MasterTemplete_Inputs.ts`)가 그 자리. - * `sheetEdit` 가 있으면 면적산출 · 재료산출 카드를 미리보기에서 고침(31-3) — 스프레드시트 `preview`(메뉴 없이 + * `sheetEdit` 가 있으면 면적산출 카드를 미리보기에서 고침(31-3) — 스프레드시트 `preview`(메뉴 없이 * 수식 입력 줄 · 칸 고침 · 단축키) + 아래 알약 줄 + 카드 [저장] · 없으면 보기만(고르기 모달). * ========================================================================== */ @@ -42,12 +42,10 @@ export type SheetKind = Exclude; export const PART_FIELD: Record = { structure: "도번", area: "면적번", - material: "재료번", }; export const PART_WORD: Record = { structure: "도면", area: "면적산출", - material: "재료산출", }; const TEXT = { @@ -79,7 +77,7 @@ const SHEET_KEYS: [string, string][] = [ const why = (error: unknown): string => (error instanceof Error ? error.message : ""); const fill = (text: string, value: string): string => text.replace("{value}", value); -/** 스프레드시트는 면적산출 · 재료산출 카드를 열 때만 받음 */ +/** 스프레드시트는 면적산출 카드를 열 때만 받음 */ const loadSpreadsheet = () => import("../A00_Common/spreadsheet/spreadsheet"); export interface ItemCardsOptions { @@ -87,7 +85,7 @@ export interface ItemCardsOptions { source: () => ItemSource; /** [수정] 단추를 보일까 — 부를 때마다 다시 봄 */ editable: () => boolean; - /** [도면 수정] · [면적산출 수정] · [재료산출 수정] */ + /** [도면 수정] · [면적산출 수정] */ onEdit: (kind: EditKind) => void; /** 문서에 번호가 없을 때 쓸 번호(집계표 번호 줄) */ numberOf?: (kind: EditKind, column: string) => string | undefined; @@ -97,11 +95,11 @@ export interface ItemCardsOptions { extraCards?: HTMLElement[]; /** 카드 가운데 하나라도 저장 안 한 고침이 생김 · 없어짐(PLAN 31-4 · 31-7) */ onDirty?: (dirty: boolean) => void; - /** 면적산출 · 재료산출 카드를 미리보기에서 고침(31-3) — 없으면 보기만 */ + /** 면적산출 카드를 미리보기에서 고침(31-3) — 없으면 보기만 */ sheetEdit?: SheetEditOptions; } -/** 카드 스프레드시트를 다루는 손잡이 — 알약 · 자재 조합 판이 씀 */ +/** 카드 스프레드시트를 다루는 손잡이 — 알약이 씀 */ export interface SheetCardControl { column: string; label: string; @@ -116,7 +114,7 @@ export interface SheetCardControl { export interface SheetEditOptions { /** 지금 고칠 수 있나 — 부를 때마다 다시 봄 */ can: () => boolean; - /** 아래 알약 줄(입력 연결) · 카드 아래 덧붙는 판(재료산출 = 자재 조합) — 전체 편집기와 같은 부품 */ + /** 아래 알약 줄(입력 연결) · 카드 아래 덧붙는 판 — 전체 편집기와 같은 부품 */ links: ( kind: SheetKind, control: SheetCardControl, @@ -135,7 +133,7 @@ export interface ItemCardsHandle { relabel: (labels: Partial>) => void; /** 지금 보이는 열 id */ column: () => string | null; - /** 카드(도면 · 면적산출 · 재료산출)에 저장 안 한 고침이 있나 — 페이지가 떠날 때 물음 */ + /** 카드(도면 · 면적산출)에 저장 안 한 고침이 있나 — 페이지가 떠날 때 물음 */ dirty: () => boolean; /** 고친 카드를 모두 저장(도면 · 엑셀) — 실패하면 던짐 */ saveAll: () => Promise; @@ -198,7 +196,7 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle { preview ? preview.write() : Promise.resolve(), ); - /** 면적산출 · 재료산출 카드 — 스프레드시트 미리보기 · 카드 고침(31-3) */ + /** 면적산출 카드 — 스프레드시트 미리보기 · 카드 고침(31-3) */ interface SheetCard extends Card { kind: SheetKind; host: HTMLElement; @@ -220,12 +218,11 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle { } as SheetCard; return card; }; - const sheets = [sheetCard("area"), sheetCard("material")]; + const sheets = [sheetCard("area")]; const cards: Card[] = [info, ...sheets]; const defaults: Record = { structure: TEXT.edit, area: fill(TEXT.editSheet, PART_WORD.area), - material: fill(TEXT.editSheet, PART_WORD.material), }; const root = el("div", { @@ -265,7 +262,7 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle { c.doc = next as Workbook; } - /** 미리보기 수정 — 메뉴 없이 수식 입력 줄 · 칸 고침 · 아래 알약 줄 · 덧붙는 판(재료산출 = 자재 조합) */ + /** 미리보기 수정 — 메뉴 없이 수식 입력 줄 · 칸 고침 · 아래 알약 줄 · 덧붙는 판 */ const paintEditable = async ( c: SheetCard, mine: number, diff --git a/M02_MasterTemplete/M02_MasterTemplete_ItemSource.ts b/M02_MasterTemplete/M02_MasterTemplete_ItemSource.ts index 7a325c9b8..c8903d81c 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_ItemSource.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_ItemSource.ts @@ -12,8 +12,8 @@ import { type Layer, } from "./M02_MasterTemplete_Api_Fetch"; -/** 집계표 열 하나에 딸린 문서 셋 — 구조물 도면 · 면적산출 · 재료산출 */ -export type ItemPart = "structure" | "area" | "material"; +/** 집계표 열 하나에 딸린 문서 — 구조물 도면 · 면적산출 */ +export type ItemPart = "structure" | "area"; export interface ItemSource { /** CAD 자동백업 칸을 가르는 층 — 프로젝트 복사본이면 `project` */ diff --git a/M02_MasterTemplete/M02_MasterTemplete_Items.py b/M02_MasterTemplete/M02_MasterTemplete_Items.py index f2c02e93a..40558d197 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Items.py +++ b/M02_MasterTemplete/M02_MasterTemplete_Items.py @@ -1,7 +1,7 @@ """M02 층 집계표 항목 — 회사 · 개인 층 빈 뼈대 · 항목 목록 · 항목 복사(PLAN 21-1 · 21-2). 항목 = 구조물집계표조합(SS) 한 줄 — 그 열의 일위대가(UA) · -도번 SD · 면적번 SA · 재료번 SM. +도번 SD · 면적번 SA. 층 폴더(시스템 · 회사 · 개인)는 모양이 같아 저장소 함수를 `store.at(층 폴더)` 안에서 그대로 부름. 권한은 부르는 쪽(`M02_MasterTemplete_Router_Items.py`) 몫 — 여기는 폴더와 파일만. """ @@ -30,7 +30,6 @@ ITEM_FIELDS = ( "일위대가", "도번", "면적번", - "재료번", ) @@ -62,10 +61,10 @@ def ensure(folder: Path) -> bool: def items(folder: Path) -> list[dict[str, Any]]: """그 층 항목 — SS 줄마다 `{key, 열, 경로, 이름, 종류, 분류1~3, 단위, 일위대가, - 도번, 면적번, 재료번, names}` · 경로 = 폴더 이름 배열 · 이름 = 경로 이음(26-1 · 종류 · + 도번, 면적번, names}` · 경로 = 폴더 이름 배열 · 이름 = 경로 이음(26-1 · 종류 · 분류1~3 은 경로 앞 넷) · `names` = 화면 이름(키 대신 · 없으면 null) — 일위대가 = 조합 이름 · - 도번 · 면적번 · 재료번 = 항목(열) 이름.""" + 도번 · 면적번 = 항목(열) 이름.""" with store.at(folder): ua = combos.names() # 마스터 · 이 층이 쓸 수 있는 회사 · 개인 조합(27-2c) rows = store.read_combos().get("줄", []) @@ -74,9 +73,7 @@ def items(folder: Path) -> list[dict[str, Any]]: head = r.get("이름") names = { "일위대가": ua.get(str(r.get("일위대가"))) if r.get("일위대가") else None, - **{ - k: (head or r.get("열")) if r.get(k) else None for k in ("도번", "면적번", "재료번") - }, + **{k: (head or r.get("열")) if r.get(k) else None for k in ("도번", "면적번")}, } out.append({"key": r.get("키"), **{k: r.get(k) for k in ITEM_FIELDS}, "names": names}) return out @@ -97,7 +94,7 @@ def copy_item( path: list[str] | None = None, ) -> dict[str, Any]: """다른 층 항목(SS `key`) → `target` 층 새 열 하나 — 머리 · 단위 · 일위대가 · 바인딩 그대로 · - 도면 · 면적산출 · 재료산출 내용(알약 연결 포함) · 항목 연결 복사 · + 도면 · 면적산출 내용(알약 연결 포함) · 항목 연결 복사 · 새 번호 · 새 SS 키 · 열 `본뜬` = `origin`(원래 층 · 주인 · 키) · `path` = 새 자리(폴더 + 이름 · 없으면 원래 경로 · 붙여넣기 PLAN 30-2). 돌려줌 `{key, 열, 이름}`.""" diff --git a/M02_MasterTemplete/M02_MasterTemplete_Router.py b/M02_MasterTemplete/M02_MasterTemplete_Router.py index d89e4f900..42d171b9c 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Router.py +++ b/M02_MasterTemplete/M02_MasterTemplete_Router.py @@ -59,7 +59,7 @@ def get_structure_combos() -> dict: @router.post("/structures/{column}") def post_structure(column: str) -> dict: - """구조물 도면 새로 — 열 id 하나 · 도번 자동 · 빈 면적산출 · 재료산출도 · 이미 있으면 409.""" + """구조물 도면 새로 — 열 id 하나 · 도번 자동 · 빈 면적산출도 · 이미 있으면 409.""" return _call(store.create_structure, column) @@ -104,11 +104,6 @@ def post_area_copy(column: str, source: str = Query(alias="from")) -> dict: return _call(store.copy_from, "area", column, source) -@router.post("/material/{column}/copy") -def post_material_copy(column: str, source: str = Query(alias="from")) -> dict: - return _call(store.copy_from, "material", column, source) - - @router.get("/templates/{kind}/{name}") def get_template(kind: str, name: str) -> dict: return _call(store.read, kind, name) diff --git a/M02_MasterTemplete/M02_MasterTemplete_Router_Items.py b/M02_MasterTemplete/M02_MasterTemplete_Router_Items.py index d893d97aa..23d4412ab 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Router_Items.py +++ b/M02_MasterTemplete/M02_MasterTemplete_Router_Items.py @@ -110,8 +110,7 @@ def get_items( all: bool = False, # noqa: A002 — 21-1 약속 이름 `?all=1` session: Session = Depends(verify_session), ): - """21-1 — 한 층이면 `[{key, 열, 종류, 분류1~3, 단위, 일위대가, 도번, 면적번, - 재료번}]` · + """21-1 — 한 층이면 `[{key, 열, 종류, 분류1~3, 단위, 일위대가, 도번, 면적번}]` · `?all=1` 이면 세 층 `[{layer, owner, items}]`(회사 · 개인은 세션 것 · 회사 없으면 빈 목록).""" if all: return [_listing(session, one, None) for one in ("system", "company", "personal")] @@ -303,7 +302,7 @@ def post_use_combo( @router.post("/layers/{layer}/{kind}/{column}/copy") def post_copy( layer: Layer, - kind: Literal["structures", "area", "material"], + kind: Literal["structures", "area"], column: str, source: str = Query(alias="from"), owner: str | None = None, diff --git a/M02_MasterTemplete/M02_MasterTemplete_Router_Layers.py b/M02_MasterTemplete/M02_MasterTemplete_Router_Layers.py index a9d65b1ab..3a27f09d2 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Router_Layers.py +++ b/M02_MasterTemplete/M02_MasterTemplete_Router_Layers.py @@ -315,8 +315,8 @@ async def save_layer_template( return await asyncio.to_thread( _store_save, folder, kind, name, body.판 or "", document, layer ) - if kind in ("area", "material"): - # 면적산출 · 재료산출 계산값은 서버가 같은 TS 엔진을 Node 로 돌려 새로 적음 + if kind == "area": + # 면적산출 계산값은 서버가 같은 TS 엔진을 Node 로 돌려 새로 적음 # (브라우저 값 버림 · 5장 ②) try: document = await asyncio.to_thread(spreadsheet.with_server_values, document) diff --git a/M02_MasterTemplete/M02_MasterTemplete_Store.py b/M02_MasterTemplete/M02_MasterTemplete_Store.py index 12a0b4497..0cb83b5ee 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Store.py +++ b/M02_MasterTemplete/M02_MasterTemplete_Store.py @@ -26,7 +26,7 @@ FOLDER: Path = Path(__file__).resolve().parent.parent / "resources" / "master_te KINDS = layers.KINDS # 시험은 FOLDER 를 사본으로 바꿈 # 지금 다루는 층 폴더 — 층 라우터가 `at(층 폴더)` 로 바꿈 · 안 바꾸면 시스템 층(`FOLDER`). # 회사 · 개인 층도 시스템과 같은 모양(`table/구조물집계표.json` · 조합 장부 · -# structure/area/material — PLAN 21-1)이라 아래 함수는 그대로 씀 +# structure/area — PLAN 21-1)이라 아래 함수는 그대로 씀 _ROOT: ContextVar[Path | None] = ContextVar("m02_layer_root", default=None) @@ -45,9 +45,8 @@ def at(folder: str | Path) -> Iterator[Path]: # 구조물집계표 구조물 열(꼴=수) 마다 세 파일(파일 이름 = 열 id) — -# 구조물 도면 `structure/` (도각 없는 전용 화면) · 면적산출 `area/` · 재료산출 `material/` -# (둘 다 스프레드시트 통합문서) -# 번호 = 도번 SD · 면적번 SA · 재료번 SM + 뒷자리 — 한 열의 세 번호는 같은 뒷자리 · 한 번 준 번호는 +# 구조물 도면 `structure/` (도각 없는 전용 화면) · 면적산출 `area/`(스프레드시트 통합문서) +# 번호 = 도번 SD · 면적번 SA + 뒷자리 — 한 열의 번호는 같은 뒷자리 · 한 번 준 번호는 # 파일에 남아 안 바뀜(PLAN 13장). 뒷자리 두 모양: # 옛 8자리 `SD01020300` = [종류][분류1][분류2][분류3] 두 자리씩(그대로 유효 · 더는 새로 안 줌) # 새 일련 `SD-000001` = 붙임표 + 6자리 · 자리 뜻 없음(PLAN 26-1 · 26-4a) — 옛 8자리 · @@ -57,7 +56,7 @@ def at(folder: str | Path) -> Iterator[Path]: STRUCTURE_TABLE = "구조물집계표" # 프로그램이 이름으로 부르는 양식 — 지우면 그 기능이 죽음(B07 이름은 `B07_DesignDetail_Engine_*`) IN_USE = { - ("table", STRUCTURE_TABLE): "구조물 도면 · 면적산출 · 재료산출 · 조합 장부가 씀", + ("table", STRUCTURE_TABLE): "구조물 도면 · 면적산출 · 조합 장부가 씀", ("drawing", "00_template_A1"): "B07 도면 도각", ("drawing", "00_template_compass"): "B07 방위표", ("drawing", "00_template_cover"): "B07 표지", @@ -66,11 +65,10 @@ IN_USE = { PARTS = ( ("structure", "도번", "SD", None), ("area", "면적번", "SA", "면적산출"), - ("material", "재료번", "SM", "재료산출"), ) _KEY = {kind: key for kind, key, _, _ in PARTS} -SHEETS = ("area", "material") # 통합문서 종류 — [저장] 때 서버가 계산값을 새로 적음 -_CODE = re.compile(r"^S[DAM](\d{8}|-\d{6})$") +SHEETS = ("area",) # 통합문서 종류 — [저장] 때 서버가 계산값을 새로 적음 +_CODE = re.compile(r"^S[DA](\d{8}|-\d{6})$") _OLD_CODE = re.compile(r"^S[DCA](\d{6})$") # PLAN 12장 6자리 — 끝에 00 을 붙여 읽음 _BAD_NAME = re.compile(r'[\\/:*?"<>|\x00-\x1f]|\.\.') # 도각 없는 빈 CAD 문서의 기본 도면층 — 없으면 B07 CAD 가 못 엶("Cannot read properties @@ -152,11 +150,11 @@ def _check_skeleton(kind: str, doc: Any) -> None: def write(kind: str, name: str, version: str, doc: Any, screen: bool = False) -> dict[str, Any]: """`version` 이 빈 글이면 새로 만듦(이미 있으면 409) · 아니면 그 판일 때만 덮어씀. - 구조물집계표를 저장하면 — 새 구조물 열의 빈 구조물 도면 · 면적산출 · 재료산출을 번호와 같이 + 구조물집계표를 저장하면 — 새 구조물 열의 빈 구조물 도면 · 면적산출을 번호와 같이 만들고 지운 열(또는 구조물이 아니게 된 열)의 세 파일을 지움(잠금 밖에서 · 안에서 부르면 죽음). - 통합문서(area · material)는 서버가 같은 TS 엔진을 Node 로 돌려 `계산값` 을 새로 적음 + 통합문서(area)는 서버가 같은 TS 엔진을 Node 로 돌려 `계산값` 을 새로 적음 (브라우저 값 버림 · 5장 ②) · 알약 `연결` 은 문서와 같이 그대로 적힘. - 번호 칸(도번 · 면적번 · 재료번)은 서버 것 — 있던 파일 번호를 그대로 둠(화면 값을 안 받음). + 번호 칸(도번 · 면적번)은 서버 것 — 있던 파일 번호를 그대로 둠(화면 값을 안 받음). 표 열의 `본뜬열`(분류 더하기로 복사한 새 열)은 적지 않음 — 집계표면 아직 파일이 없는 새 열이 원본 열의 일위대가 키와 세 파일 내용을 받음(새 번호 · 그 뒤로는 열마다 따로). `screen` = 화면 표 [저장](PLAN 26-7e) — 있던 항목의 경로 · 층 이름 · 빈 폴더는 저장본 그대로 @@ -186,7 +184,7 @@ def write(kind: str, name: str, version: str, doc: Any, screen: bool = False) -> _copy_inputs(source, column) backfill_missing_structures() # 지우기 앞 — 같은 저장의 새 열이 지운 번호를 안 받게 prune_removed_structures() - sync_combos() # 번호가 다 선 뒤 — 조합 표 도번 · 면적번 · 재료번 칸 + sync_combos() # 번호가 다 선 뒤 — 조합 표 도번 · 면적번 칸 return info @@ -202,7 +200,7 @@ def delete(kind: str, name: str, version: str | None = None) -> None: path.unlink() -# ── 구조물 도면 · 면적산출 · 재료산출 ───────────────────── +# ── 구조물 도면 · 면적산출 ───────────────────── def _docs(kind: str) -> list[tuple[str, Any]]: @@ -221,7 +219,7 @@ def _code(doc: Any, key: str) -> str | None: def _codes() -> dict[str, str]: - """`{열 id: 뒷자리}` — 구조물 도면 도번 먼저 · 없으면 면적번 · 재료번.""" + """`{열 id: 뒷자리}` — 구조물 도면 도번 먼저 · 없으면 면적번.""" codes: dict[str, str] = {} for kind, key, _, _ in reversed(PARTS): codes.update({name: c for name, doc in _docs(kind) if (c := _code(doc, key))}) @@ -229,7 +227,7 @@ def _codes() -> dict[str, str]: def structure_numbers() -> dict[str, dict[str, str]]: - """`{열 id: {도번, 면적번, 재료번}}` — 집계표 줄 · 좌측 줄이 한 번에 받음(없는 쪽은 빈 글).""" + """`{열 id: {도번, 면적번}}` — 집계표 줄 · 좌측 줄이 한 번에 받음(없는 쪽은 빈 글).""" out: dict[str, dict[str, str]] = {} for kind, key, _, _ in PARTS: for name, doc in _docs(kind): @@ -281,7 +279,7 @@ def _retired() -> list[str]: def _blank(kind: str, column: str) -> dict[str, Any]: """빈 문서(번호는 부르는 쪽이 붙임) — 구조물 도면 = 도각 없음 · 기본 도면층 하나 / - 면적산출 · 재료산출 = 진짜 빈 시트 하나(식 없어 `계산값` 없음 · 첫 [저장] 때 서버가 적음).""" + 면적산출 = 진짜 빈 시트 하나(식 없어 `계산값` 없음 · 첫 [저장] 때 서버가 적음).""" if kind == "structure": return { "양식": "구조물도면", @@ -342,7 +340,7 @@ def _take_origins(doc: Any) -> dict[str, str]: def create_structure(column: str) -> dict[str, Any]: """구조물 열 하나의 구조물 도면 새로 — 도번 SD + 새 일련(`-` + 6자리) · 이미 있으면 409. - 면적산출 · 재료산출 파일이 없으면 빈 통합문서도 같이 만듦(같은 뒷자리).""" + 면적산출 파일이 없으면 빈 통합문서도 같이 만듦(같은 뒷자리).""" if column not in _column_ids(): raise StoreError(404, f"{STRUCTURE_TABLE}에 없는 구조물 열 「{column}」") if not _ensure("structure", column): @@ -428,7 +426,7 @@ def _move_basis() -> list[str]: def renumber() -> dict[str, list[str]]: """한 번 — 옛 산출근거를 면적산출로 옮김 · 옛 번호(6자리 · 구-NN · 표-NN · 없음)를 8자리로 (6자리는 끝에 00 · 번호 없는 열은 열 차례로 일련번호) · - 구조물 아닌 열 파일 지움 · 빠진 파일(재료산출 등) 만듦. 내용은 그대로 · 두 번 돌려도 같음.""" + 구조물 아닌 열 파일 지움 · 빠진 파일(면적산출 등) 만듦. 내용은 그대로 · 두 번 돌려도 같음.""" moved = _move_basis() gone = prune_removed_structures() changed: list[str] = [] diff --git a/M02_MasterTemplete/M02_MasterTemplete_Structure.ts b/M02_MasterTemplete/M02_MasterTemplete_Structure.ts index 9420f9dda..2a348521f 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Structure.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Structure.ts @@ -1,15 +1,15 @@ /* ============================================================================= * M02_MasterTemplete_Structure.ts - * 구조물집계표 아래 설계 컨테이너 — 고른 열의 도면 정보 · 면적산출 · 재료산출 카드(보기만 + [수정]). + * 구조물집계표 아래 설계 컨테이너 — 고른 열의 도면 정보 · 면적산출 카드(보기만 + [수정]). * 카드 셋은 좌측 패널과 같은 공용 ui-sidebar 틀 — 고침은 [수정] 이 여는 M02 메인 칸 전체 편집 화면 * (`M02_MasterTemplete_UI_Main.ts`)이 맡음. - * 면적산출 · 재료산출 = 스프레드시트(`A00_Common/spreadsheet/`) · 열마다 따로 파일(`area|material/<열 id>.json`) · + * 면적산출 = 스프레드시트(`A00_Common/spreadsheet/`) · 열마다 따로 파일(`area/<열 id>.json`) · * 부품은 카드를 열 때만 `import()` 로 받음(다른 페이지 번들에 안 섞임). * * 구조물 도면은 집계표 열마다 하나(`resources/master_template/structure/<열 id>.json`) · * 서버가 열마다 미리 만들어 둠(번호 자동 · 새 열도 집계표 저장 때 서버가 같이 만듦 — `M02_MasterTemplete_Store.py`). * 이 칸은 표 부품 host 밖 형제 자리에 붙음 — 집계표는 그대로 두고 여기만 열마다 다시 그림. - * 집계표 일위대가 · 도면 · 면적산출 · 재료산출 줄의 조회 후 선택(`pick`)도 여기 — 일위대가 = M01 일위대가 + * 집계표 일위대가 · 도면 · 면적산출 줄의 조회 후 선택(`pick`)도 여기 — 일위대가 = M01 일위대가 * 목록에서 고름 · 나머지 = 다른 열 것을 골라 내용만 복사해 가져옴(번호 · 열은 그대로 · 서버가 복사). * 글자는 이 파일에 둠(도면 부품 `M02_MasterTemplete_Drawing.ts` 와 같은 방식). * ========================================================================== */ @@ -23,11 +23,9 @@ import { } from "@ui/ui_template_elements"; import { openModal } from "@ui/ui_template_modal"; import type { LinkOptions } from "../A00_Common/spreadsheet/spreadsheet_links"; -import { searchMaterials } from "../M01_MasterData/M01_MasterData_Api_Fetch"; import { fetchComboInputs, fetchUsableCombos } from "./M02_MasterTemplete_Labor_Api"; import { copyArea, - copyMaterial, copyStructure, fetchStructureNumbers, getActiveLayer, @@ -69,26 +67,24 @@ const TEXT = { noPrice: "이 열에 일위대가가 없어 알약 없음 — 구조물 조합 일위대가 줄에서 고름", noInputs: "일위대가 {value} 에 입력값이 없어 알약 없음", inputsFailed: "일위대가 입력값을 못 받음 — {value}", - discardCards: "카드(도면 · 면적산출 · 재료산출)에 저장 안 한 고침이 있음 — 버리고 넘어갈까?", + discardCards: "카드(도면 · 면적산출)에 저장 안 한 고침이 있음 — 버리고 넘어갈까?", }; /** 집계표 열 하나에 딸린 문서 셋 — 번호 칸 · 이름 · 복사 */ const COPY: Record Promise> = { structure: copyStructure, area: copyArea, - material: copyMaterial, }; /** 집계표 줄 열쇠 → 문서 */ const ROW_KIND: Record = { "s:drawing": "structure", "s:area": "area", - "s:material": "material", }; const why = (error: unknown): string => (error instanceof Error ? error.message : ""); const fill = (text: string, value: string): string => text.replace("{value}", value); -/** `{열 id: {도번, 면적번, 재료번}}` — 집계표 번호 줄(없는 열은 키 없음 = 「없음」) */ +/** `{열 id: {도번, 면적번}}` — 집계표 번호 줄(없는 열은 키 없음 = 「없음」) */ export type ColumnNumbers = Record; /** 일위대가 목록 — 이 층이 쓸 수 있는 세 층 조합(27-2c) · 고를 때마다 받음(층을 바꿔 다시 들어와도 맞게) */ @@ -206,7 +202,7 @@ export interface StructureColumn { 꼴?: string | null; } -/** 구조물 열 = 꼴=수 — NO · 측점 · 비고 같은 글 열은 도면 · 면적산출 · 재료산출 · 번호가 없음(서버와 같은 규칙) */ +/** 구조물 열 = 꼴=수 — NO · 측점 · 비고 같은 글 열은 도면 · 면적산출 · 번호가 없음(서버와 같은 규칙) */ export const isStructureColumn = (col: StructureColumn): boolean => col.꼴 === "수"; /** 관리 화면 「구조물 조합」에서 숨기는 것(PLAN 26-7b · 30-7) — NO · 측점 · 비고 열 · 전구간 줄(본문 줄) · @@ -274,16 +270,12 @@ export const columnHead = (col: StructureColumn): string => export const structureLabel = (number: string, col: StructureColumn): string => [number, columnHead(col)].filter(Boolean).join(" "); -/** 면적산출 · 재료산출 편집 화면 위 알약 막대 — 면적 = 그 열 일위대가 입력값(물량) · 재료 = 자재DB 찾기. +/** 면적산출 편집 화면 위 알약 막대 — 그 열 일위대가 입력값(물량). * 알약이 없으면 까닭 글(`notice`)을 같이 돌려줌 */ export async function pillsFor( - kind: SheetKind, + _kind: SheetKind, // 종류는 면적산출 하나 — 부르는 쪽 모양 그대로 column: string, ): Promise<{ links: LinkOptions; notice?: string }> { - if (kind === "material") { - const own = await inputPills(column, "재료"); - return { links: { 항목: own, 모드: "자재", 자재찾기: searchMaterials } }; - } const own = await inputPills(column, "면적"); // 선택 입력 카드가 면적산출로 고른 입력 const none = (notice: string) => ({ links: { 항목: own, 모드: "입력" as const }, notice }); const table = await readTemplate(getActiveLayer(), "table", STRUCTURE_TABLE, null).catch( @@ -312,7 +304,7 @@ async function cardLinks( export interface StructurePanelOptions { /** 시스템 관리자만 저장함(구조물 도면은 서버가 미리 만들어 둠) */ isAdmin: boolean; - /** [도면 수정] · [면적산출 수정] · [재료산출 수정] — 그 열을 CAD · 스프레드시트 전체 편집으로 엶 + /** [도면 수정] · [면적산출 수정] — 그 열을 CAD · 스프레드시트 전체 편집으로 엶 * (페이지의 `open`) · label 은 화면 제목에 씀 */ onEdit: (column: string, label: string, kind: EditKind) => void; /** 집계표에 저장 안 한 고침이 있으면 버릴지 물음 — 취소면 false */ @@ -325,13 +317,13 @@ export interface StructurePanelOptions { export interface StructurePanelHandle { root: HTMLElement; - /** `{열 id: {도번, 면적번, 재료번}}` — 집계표 `drawingLabel` · `areaLabel` · `materialLabel` 이 씀 */ + /** `{열 id: {도번, 면적번}}` — 집계표 `drawingLabel` · `areaLabel` 이 씀 */ numbers: () => ColumnNumbers; /** 번호 목록 다시 받기 */ reload: () => Promise; /** 「선택 입력」 카드에서 고른 자리를 서버에 저장 — 집계표 [저장] 뒤에 부름(실패하면 던짐) */ saveInputs: () => Promise; - /** 카드에 저장 안 한 고침(도면 31-4 · 면적산출 · 재료산출 31-3) — 페이지가 떠날 때 함께 물음 */ + /** 카드에 저장 안 한 고침(도면 31-4 · 면적산출 31-3) — 페이지가 떠날 때 함께 물음 */ dirty: () => boolean; /** 고친 카드 저장(도면 · 엑셀) — 집계표 [저장] 이 카드를 다시 읽기 전에 부름(실패하면 던짐) */ saveCards: () => Promise; @@ -340,7 +332,7 @@ export interface StructurePanelHandle { /** 보던 열을 다시 읽음 — 집계표 [저장] 뒤(서버가 새 열 파일 · 번호를 만듦) */ refresh: (columns: StructureColumn[]) => void; /** 집계표 조회 후 선택(`onPick`) — 일위대가 = 고른 키(「비움」 = 빈 글 · 닫으면 null) · - * 도면 · 면적산출 · 재료산출 = 다른 열 것을 복사해 가져옴(돌려주는 글 없음) */ + * 도면 · 면적산출 = 다른 열 것을 복사해 가져옴(돌려주는 글 없음) */ pick: (row: string, colId: string | null, columns: StructureColumn[]) => Promise; destroy: () => void; } @@ -364,7 +356,7 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane // 도번 목록에 있는 열인데 못 읽으면 알림 — 없는 열은 그냥 「도면 없음」 warnOnFail: (id) => !!numbers[id], extraCards: [inputs.root], - // 면적산출 · 재료산출 카드 미리보기 수정(31-3) — 전체 편집기와 같은 알약 · 저장 길 + // 면적산출 카드 미리보기 수정(31-3) — 전체 편집기와 같은 알약 · 저장 길 sheetEdit: { can: () => opts.isAdmin, links: (kind, ctl) => cardLinks(kind, ctl), @@ -407,7 +399,7 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane return got ? got.key : null; }; - /** 다른 열의 도면 · 면적산출 · 재료산출을 골라 내용만 복사 — 덮어쓰기 확인 · 보고 있던 열이면 다시 읽음 */ + /** 다른 열의 도면 · 면적산출을 골라 내용만 복사 — 덮어쓰기 확인 · 보고 있던 열이면 다시 읽음 */ const pickCopy = async ( kind: EditKind, colId: string, diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts index cccb494e2..837758a78 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts @@ -57,7 +57,6 @@ type Mods = Record Promise>; const NUMBER_FIELD: Partial> = { structure: "도번", area: "면적번", - material: "재료번", }; // 표 부품(sub3) · 도면 부품(sub2) — 내보낸 이름으로 찾음 // 표는 진입 파일만 — 폴더 통째(`*.ts`)면 분수 모듈이 따로 늦게 받는 입구가 되어 번들러 도우미를 @@ -130,7 +129,7 @@ export function createMain( const title = el("h2", { className: "m02-main__title", text: L("M02_PickTemplate") }); const reload = createButton({ label: L("M02_Reload"), variant: "ghost" }); const save = createButton({ label: L("M02_Save"), variant: "filled" }); - // 구조물 도면 · 면적산출 · 재료산출 상세 화면 → 구조물집계표(그 열 고른 채) + // 구조물 도면 · 면적산출 상세 화면 → 구조물집계표(그 열 고른 채) const back = createButton({ label: L("M02_Back"), variant: "ghost", size: "sm" }); const slot = el("div", { className: "m02-main__slot" }); // 도면 양식의 작도 영역 칸이 들어옴 const notice = el("div", { className: "m02-main__notice", attrs: { hidden: "" } }); @@ -161,7 +160,7 @@ export function createMain( title.classList.toggle("m02-main__title--fold", combo); title.title = combo ? L("M02_TableFold") : ""; if (!combo) { - host.hidden = false; // 다른 상세(도면 · 면적 · 재료)는 늘 펼침 + host.hidden = false; // 다른 상세(도면 · 면적)는 늘 펼침 title.classList.remove("is-folded"); } }; @@ -182,7 +181,7 @@ export function createMain( let cpanel: ComboPanel | null = null; // 면적산출 오른쪽 조합 패널(프로젝트 층 아님) let seq = 0; let dirty = false; - /** 저장 안 한 고침 — 이 화면 + 카드(도면 31-4 · 면적산출 · 재료산출 31-3) */ + /** 저장 안 한 고침 — 이 화면 + 카드(도면 31-4 · 면적산출 31-3) */ const busy = (): boolean => dirty || !!panel?.dirty(); const guard = (event: BeforeUnloadEvent): void => { @@ -232,7 +231,7 @@ export function createMain( slot.replaceChildren(); }; - /** 구조물집계표 아래 설계 컨테이너 — 고른 열의 도면 정보 · 면적산출 · 재료산출 카드 */ + /** 구조물집계표 아래 설계 컨테이너 — 고른 열의 도면 정보 · 면적산출 카드 */ function buildPanel(layer: Layer): StructurePanelHandle { const made = createStructurePanel({ isAdmin, @@ -248,11 +247,11 @@ export function createMain( return made; } - /** 면적산출 · 재료산출 스프레드시트 — 위 알약 막대(sub7 `links`) · 면적 = 그 열 일위대가 입력값 · - * 재료 = 자재DB 찾기(`pillsFor`) · 시스템 층은 면적 = 조합 줄 + [조합 고치기] */ + /** 면적산출 스프레드시트 — 위 알약 막대(sub7 `links`) = 그 열 일위대가 입력값(`pillsFor`) · + * 시스템 층은 조합 줄 + [조합 고치기] */ async function mountSheet( doc: unknown, - kind: "area" | "material", + kind: "area", at: Selection, readOnly: boolean, ): Promise { @@ -279,7 +278,7 @@ export function createMain( ); editor = handle; sheetHandle = handle; - if (at.layer === "project" || kind !== "area") return; + if (at.layer === "project") return; // 오른쪽 조합 패널 — 한 번 만들고 시트를 다시 붙여도 그대로(늘 시트 뒤) cpanel ??= buildComboPanel({ kind, @@ -305,11 +304,7 @@ export function createMain( } /** 시트만 다시 붙임(알약을 새로 받음) — 저장 안 한 시트 고침은 그대로 옮김 */ - async function remount( - kind: "area" | "material", - at: Selection, - readOnly: boolean, - ): Promise { + async function remount(kind: "area", at: Selection, readOnly: boolean): Promise { const kept = sheetHandle?.getDoc(); if (sel !== at || !kept) return; editor?.destroy(); @@ -326,8 +321,8 @@ export function createMain( "createSheet", ); if (!create) return empty(L("M02_NoSheet")); - // 구조물집계표(프로젝트 층 아님)만 — 도면 · 면적산출 · 재료산출 줄에 번호 · 칸을 고르면 아래 컨테이너 · - // 고칠 수 있으면 일위대가 · 도면 · 면적산출 · 재료산출 칸은 조회 후 선택 + // 구조물집계표(프로젝트 층 아님)만 — 도면 · 면적산출 줄에 번호 · 칸을 고르면 아래 컨테이너 · + // 고칠 수 있으면 일위대가 · 도면 · 면적산출 칸은 조회 후 선택 const struct = at.layer !== "project" && at.name === STRUCTURE_TABLE ? buildPanel(at.layer) : null; if (struct) await struct.reload(); @@ -347,7 +342,6 @@ export function createMain( ? { drawingLabel: (colId: string) => struct.numbers()[colId]?.도번 ?? "", areaLabel: (colId: string) => struct.numbers()[colId]?.면적번 ?? "", - materialLabel: (colId: string) => struct.numbers()[colId]?.재료번 ?? "", combo: { ...(readOnly || !extras.onHeadRename ? {} @@ -378,8 +372,8 @@ export function createMain( editor = { getDoc: () => comboMerge(loaded, shown.getDoc()), destroy: shown.destroy }; return; } - if (at.kind === "area" || at.kind === "material") { - // 면적산출 · 재료산출 = 구조물집계표 열마다 한 스프레드시트 — 페이지 번들에 안 섞이게 이 화면을 + if (at.kind === "area") { + // 면적산출 = 구조물집계표 열마다 한 스프레드시트 — 페이지 번들에 안 섞이게 이 화면을 // 열 때만 `import()` 로 받음(계약 `spreadsheet_types.ts` 머리) return mountSheet(doc, at.kind, at, readOnly); } @@ -435,7 +429,7 @@ export function createMain( ) : got.문서; if (mine !== seq) return; - // 구조물 도면 · 면적산출 · 재료산출은 열 id 로 열리니 머리 맨 앞에 번호(「SD01010100 돌기슭막이 …」) + // 구조물 도면 · 면적산출은 열 id 로 열리니 머리 맨 앞에 번호(「SD01010100 돌기슭막이 …」) const field = NUMBER_FIELD[next.kind]; const number = field && (got.문서 as Record | null)?.[field]; if (number) title.textContent = `${String(number)} ${next.label ?? next.name}`; @@ -457,11 +451,11 @@ export function createMain( try { const edited = editor ? await editor.getDoc() : loaded; // 구조물 도면 저장은 `도면` 칸만 갈아 끼움 — 도번은 읽은 그대로. - // 면적산출 · 재료산출은 스프레드시트 문서로 통째 갈아 끼우되 번호는 계약 밖(엔진이 모름)이라 옮겨 붙임. + // 면적산출은 스프레드시트 문서로 통째 갈아 끼우되 번호는 계약 밖(엔진이 모름)이라 옮겨 붙임. const doc = at.kind === "structure" && loaded ? { ...(loaded as StructureDoc), 도면: edited as StructureDoc["도면"] } - : (at.kind === "area" || at.kind === "material") && loaded + : at.kind === "area" && loaded ? { ...(loaded as Record), ...(edited as Record) } : edited; const info = await saveTemplate(at.layer, at.kind, at.name, at.projectId, version, doc); diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts index 52b42c186..294df0d9e 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts @@ -1,9 +1,9 @@ /* ============================================================================= * M02_MasterTemplete_UI_Side.ts - * 마스터 템플릿 좌측 패널 — 「구조물 조합」(구조물집계표 줄 하나 + 하위 구조물 도면 · 면적산출 · - * 재료산출) · 「도면 양식」(새로 · 본떠 · 지우기). 서버 길은 계약 `6_계약.md`. - * 구조물 도면 · 면적산출 · 재료산출 줄은 집계표 구조물 열(꼴=수)마다 하나씩 — - * 서버가 미리 만들어 둠(도번 SD · 면적번 SA · 재료번 SM + 같은 8자리) · + * 마스터 템플릿 좌측 패널 — 「구조물 조합」(구조물집계표 줄 하나 + 하위 구조물 도면 · 면적산출) · + * 「도면 양식」(새로 · 본떠 · 지우기). 서버 길은 계약 `6_계약.md`. + * 구조물 도면 · 면적산출 줄은 집계표 구조물 열(꼴=수)마다 하나씩 — + * 서버가 미리 만들어 둠(도번 SD · 면적번 SA + 같은 8자리) · * 줄 글은 번호가 맨 앞(「SD01010100 돌기슭막이 …」). * 화면 이름 「구조물 조합」(PLAN 26-7a) — 파일 이름 · 양식 키 · API 길은 「구조물집계표」 그대로. * 구조물 라이브러리는 좌측이 아니라 「구조물 조합」 상세 화면 왼쪽(PLAN 26-7c · 페이지가 붙임). @@ -47,11 +47,10 @@ const KINDS: { kind: Kind; label: () => string }[] = [ { kind: "drawing", label: () => L("M02_KindDrawing") }, ]; /** 구조물 열마다 한 줄씩인 목록 셋 — 종류 · 번호 칸 · 제목 */ -type ColumnKind = "structure" | "area" | "material"; +type ColumnKind = "structure" | "area"; const COLUMN_KINDS: { kind: ColumnKind; key: keyof StructureNumbers; label: () => string }[] = [ { kind: "structure", key: "도번", label: () => L("M02_KindStructure") }, { kind: "area", key: "면적번", label: () => L("M02_KindArea") }, - { kind: "material", key: "재료번", label: () => L("M02_KindMaterial") }, ]; /** 새 도면 양식의 첫 문서 — 빈 웹캐드 문서 */ const STUB_DRAWING = { @@ -215,7 +214,7 @@ export function buildSide(opt: SideOptions): SideHandle { } } - /** 구조물 도면 · 면적산출 · 재료산출 줄 — 구조물 열마다 하나 · 「번호 이름 변수값」(번호가 없으면 「없음」) */ + /** 구조물 도면 · 면적산출 줄 — 구조물 열마다 하나 · 「번호 이름 변수값」(번호가 없으면 「없음」) */ function paintRows(body: HTMLElement, kind: ColumnKind, key: keyof StructureNumbers): void { body.replaceChildren( ...(columns.length diff --git a/M02_MasterTemplete/M02_Project_Items_Router.py b/M02_MasterTemplete/M02_Project_Items_Router.py index 59aa24d72..8c12c9a27 100644 --- a/M02_MasterTemplete/M02_Project_Items_Router.py +++ b/M02_MasterTemplete/M02_Project_Items_Router.py @@ -5,11 +5,11 @@ POST …/items {structure, layer, owner, key} 미리 가져오기 → {폴더, 만듦, 가져온날} POST …/items/new {structure, 경로, 이름, 단위} 새 항목(개인 층에도) · 회사 없음 409 GET …/items/{폴더}/{종류} {종류, 판, 내용} - PUT …/items/{폴더}/{종류} {판, 내용} — 판이 다르면 409 · 면적 · 재료는 서버 계산값 + PUT …/items/{폴더}/{종류} {판, 내용} — 판이 다르면 409 · 면적은 서버 계산값 POST …/items/{폴더}/reapply 재적용(입력값 · 고른자재는 같은 키면 살림 · `덮임` 알림) POST …/items/{폴더}/save-as {to, 경로, 이름} 개인 층에 새 항목으로(단가 묶음은 안 가져감) POST …/items/{폴더}/calc 복사본만으로 계산(세 비목 · 계 · 빠진 입력) -종류 = 도면 · 면적 · 재료 · 입력연결 · 입력값 · 고른자재 · `refresh` = 옛 이름(재적용). +종류 = 도면 · 면적 · 입력연결 · 입력값 · 고른자재 · `refresh` = 옛 이름(재적용). ⚠ 등록은 `main.py`(로그인만) — 프로젝트 권한(같은 회사)은 여기서 `_project` 로 봄(403 · 404). """ @@ -31,7 +31,7 @@ from M02_MasterTemplete.M02_Item_Source import item_name router = APIRouter(prefix="/api/projects", tags=["M02 Project Items"]) -Kind = Literal["도면", "면적", "재료", "입력연결", "입력값", "고른자재"] +Kind = Literal["도면", "면적", "입력연결", "입력값", "고른자재"] class WriteBody(BaseModel): diff --git a/M02_MasterTemplete/M02_Template_Layers.py b/M02_MasterTemplete/M02_Template_Layers.py index f56c48822..c5577c752 100644 --- a/M02_MasterTemplete/M02_Template_Layers.py +++ b/M02_MasterTemplete/M02_Template_Layers.py @@ -1,9 +1,9 @@ """M02 양식 층 — 네 층의 자리 · 읽기 · 쓰기 · 복사 · manifest. 층 넷 (PLAN 10-5): - system `resources/master_template/{table,drawing,structure,area,material}/<이름>.json` (git) - company `storage/{회사}/templates/{table,drawing,structure,area,material}/` - personal `storage/{회사}/{사용자}/templates/{table,drawing,structure,area,material}/` + system `resources/master_template/{table,drawing,structure,area}/<이름>.json` (git) + company `storage/{회사}/templates/{table,drawing,structure,area}/` + personal `storage/{회사}/{사용자}/templates/{table,drawing,structure,area}/` project `storage/{회사}/{사용자}/{프로젝트}/templates/{…같은 다섯}/` + 초기 사본 `templates/_initial/` @@ -26,8 +26,8 @@ from common_util.common_util_json import atomic_write_json from config import config_system LAYERS = ("system", "company", "personal", "project") -# area = 면적산출 · material = 재료산출 — 통합문서(구조물집계표 열마다 · `A00_Common/spreadsheet/`) -KINDS = ("table", "drawing", "structure", "area", "material") +# area = 면적산출 — 통합문서(구조물집계표 열마다 · `A00_Common/spreadsheet/`) +KINDS = ("table", "drawing", "structure", "area") TEMPLATES_DIRNAME = "templates" INITIAL_DIRNAME = "_initial" MANIFEST_NAME = "manifest.json" @@ -169,7 +169,6 @@ _SKELETON = { "drawing": ("entities",), "structure": ("도면.entities",), "area": ("시트",), - "material": ("시트",), } diff --git a/resources/master_template/material/item1.json b/resources/master_template/material/item1.json deleted file mode 100644 index 56b1753fb..000000000 --- a/resources/master_template/material/item1.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "종류": "통합문서", - "판": 1, - "열": "item1", - "서식": [ - {} - ], - "시트": [ - { - "id": "s1", - "이름": "재료산출", - "칸": {} - } - ], - "활성": "s1", - "재료번": "SM-000001" -} diff --git a/resources/master_template/material/item2.json b/resources/master_template/material/item2.json deleted file mode 100644 index 070827ebc..000000000 --- a/resources/master_template/material/item2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "종류": "통합문서", - "판": 1, - "열": "item2", - "서식": [ - {} - ], - "시트": [ - { - "id": "s1", - "이름": "재료산출", - "칸": {} - } - ], - "활성": "s1", - "재료번": "SM-000002" -} diff --git a/resources/master_template/material/item3.json b/resources/master_template/material/item3.json deleted file mode 100644 index a75069ccd..000000000 --- a/resources/master_template/material/item3.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "종류": "통합문서", - "판": 1, - "열": "item3", - "서식": [ - {} - ], - "시트": [ - { - "id": "s1", - "이름": "재료산출", - "칸": {} - } - ], - "활성": "s1", - "재료번": "SM-000003" -} diff --git a/resources/tester/fixtures/m02_master_sample/material/c4.json b/resources/tester/fixtures/m02_master_sample/material/c4.json deleted file mode 100644 index c07721f53..000000000 --- a/resources/tester/fixtures/m02_master_sample/material/c4.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "종류": "통합문서", - "판": 1, - "열": "c4", - "서식": [ - {} - ], - "시트": [ - { - "id": "s1", - "이름": "재료산출", - "칸": { - "I5": { - "값": 6 - } - }, - "연결": [ - { - "키": "MT003810", - "이름": "모 래, 서울, 도착도 강모래", - "칸": "I5", - "단위": "㎥", - "자재": { - "코드": "MT003810", - "이름": "모 래, 서울, 도착도", - "규격": "강모래", - "단위": "㎥" - } - } - ] - } - ], - "활성": "s1", - "재료번": "SM01010101", - "계산값": {} -} diff --git a/resources/tester/fixtures/m02_master_sample/material/ms.json b/resources/tester/fixtures/m02_master_sample/material/ms.json deleted file mode 100644 index 72d3af7f4..000000000 --- a/resources/tester/fixtures/m02_master_sample/material/ms.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "종류": "통합문서", - "판": 1, - "열": "ms", - "서식": [ - {} - ], - "시트": [ - { - "id": "s1", - "이름": "재료산출", - "칸": {} - } - ], - "활성": "s1", - "재료번": "SM10010100" -} diff --git a/resources/tester/fixtures/m02_master_sample/material/pp_len.json b/resources/tester/fixtures/m02_master_sample/material/pp_len.json deleted file mode 100644 index 648bedb86..000000000 --- a/resources/tester/fixtures/m02_master_sample/material/pp_len.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "종류": "통합문서", - "판": 1, - "열": "pp_len", - "서식": [ - {} - ], - "시트": [ - { - "id": "s1", - "이름": "재료산출", - "칸": {} - } - ], - "활성": "s1", - "재료번": "SM03010100" -} diff --git a/resources/tester/fixtures/m02_master_sample/material/rv.json b/resources/tester/fixtures/m02_master_sample/material/rv.json deleted file mode 100644 index 2709b17b8..000000000 --- a/resources/tester/fixtures/m02_master_sample/material/rv.json +++ /dev/null @@ -1,1425 +0,0 @@ -{ - "종류": "통합문서", - "판": 1, - "열": "rv", - "서식": [ - {}, - { - "가로": "center", - "테두리": { - "위": { - "선": "hair" - }, - "왼": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "Fixed", - "테두리": { - "위": { - "선": "hair" - } - } - }, - { - "가로": "center", - "테두리": { - "위": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "0.0", - "테두리": { - "위": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "0", - "테두리": { - "위": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "_ * #,##0.00_ ", - "테두리": { - "위": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "_-* #,##0.00_-", - "테두리": { - "위": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "0%", - "테두리": { - "위": { - "선": "hair" - } - } - }, - { - "테두리": { - "위": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "0.00_)", - "테두리": { - "위": { - "선": "hair" - } - } - }, - { - "가로": "center", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - }, - "왼": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "Fixed", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "_-* #,##0_-", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "_-* #,##0.0_-", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "0.0", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "_-* #,##0.00_-", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "형식": "_-* #,##0.00_-", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "가로": "left", - "형식": "0.0", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "형식": "0.0", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "0.00_)", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "테두리": { - "아래": { - "선": "hair" - }, - "왼": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "Fixed", - "테두리": { - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "테두리": { - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "0.0", - "테두리": { - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "0", - "테두리": { - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "_ * #,##0.00_ ", - "테두리": { - "아래": { - "선": "hair" - } - } - }, - { - "테두리": { - "아래": { - "선": "hair" - } - } - }, - { - "가로": "center", - "형식": "0.00_)", - "테두리": { - "아래": { - "선": "hair" - } - } - }, - { - "가로": "left", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - }, - "왼": { - "선": "thin" - }, - "오른": { - "선": "hair" - } - } - }, - { - "가로": "center", - "테두리": { - "위": { - "선": "hair" - }, - "오른": { - "선": "hair" - } - } - }, - { - "테두리": { - "위": { - "선": "hair" - }, - "오른": { - "선": "thin" - } - } - }, - { - "가로": "center", - "테두리": { - "아래": { - "선": "hair" - }, - "왼": { - "선": "thin" - }, - "오른": { - "선": "hair" - } - } - }, - { - "가로": "center", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - }, - "왼": { - "선": "hair" - }, - "오른": { - "선": "hair" - } - } - }, - { - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "hair" - }, - "오른": { - "선": "thin" - } - } - }, - { - "테두리": { - "아래": { - "선": "hair" - }, - "오른": { - "선": "thin" - } - } - }, - { - "가로": "center" - }, - { - "가로": "center", - "형식": "Fixed" - }, - { - "가로": "center", - "형식": "0.0" - }, - { - "가로": "center", - "형식": "0" - }, - { - "가로": "center", - "형식": "_ * #,##0.00_ " - }, - { - "가로": "center", - "형식": "_-* #,##0.00_-" - }, - { - "테두리": { - "오른": { - "선": "thin" - } - } - }, - { - "가로": "left", - "테두리": { - "아래": { - "선": "thin" - }, - "왼": { - "선": "thin" - }, - "오른": { - "선": "hair" - } - } - }, - { - "가로": "center", - "테두리": { - "아래": { - "선": "thin" - }, - "오른": { - "선": "hair" - } - } - }, - { - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "thin" - } - } - }, - { - "가로": "center", - "형식": "Fixed", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "thin" - } - } - }, - { - "가로": "center", - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "thin" - } - } - }, - { - "가로": "center", - "테두리": { - "아래": { - "선": "thin" - } - } - }, - { - "가로": "center", - "형식": "0.00_)", - "테두리": { - "아래": { - "선": "thin" - } - } - }, - { - "테두리": { - "위": { - "선": "hair" - }, - "아래": { - "선": "thin" - }, - "오른": { - "선": "thin" - } - } - } - ], - "시트": [ - { - "id": "s1", - "이름": "재료산출", - "칸": { - "C9": { - "값": "{ (", - "서식": 1 - }, - "D9": { - "값": 2, - "서식": 2 - }, - "E9": { - "값": "+", - "서식": 3 - }, - "F9": { - "값": 4.2, - "서식": 2 - }, - "G9": { - "값": ")", - "서식": 3 - }, - "H9": { - "값": "÷", - "서식": 4 - }, - "I9": { - "값": 2, - "서식": 5 - }, - "J9": { - "값": "}", - "서식": 3 - }, - "K9": { - "값": " × ", - "서식": 6 - }, - "L9": { - "값": 2.2, - "서식": 7 - }, - "N9": { - "값": "×", - "서식": 2 - }, - "O9": { - "값": 1, - "서식": 4 - }, - "P9": { - "값": "×", - "서식": 3 - }, - "Q9": { - "값": "100%", - "서식": 8 - }, - "S9": { - "값": " ", - "서식": 9 - }, - "T9": { - "값": " ", - "서식": 9 - }, - "U9": { - "값": " =", - "서식": 3 - }, - "V9": { - "값": 6.82, - "서식": 10 - }, - "W9": { - "값": "㎥", - "서식": 3 - }, - "C10": { - "값": "{ (", - "서식": 1 - }, - "D10": { - "값": 2, - "서식": 2 - }, - "E10": { - "값": "+", - "서식": 3 - }, - "F10": { - "값": 4.2, - "서식": 2 - }, - "G10": { - "값": ")", - "서식": 3 - }, - "H10": { - "값": "÷", - "서식": 4 - }, - "I10": { - "값": 2, - "서식": 5 - }, - "J10": { - "값": "}", - "서식": 3 - }, - "K10": { - "값": " × ", - "서식": 6 - }, - "L10": { - "값": 2.2, - "서식": 7 - }, - "N10": { - "값": "×", - "서식": 2 - }, - "O10": { - "값": 1, - "서식": 4 - }, - "P10": { - "값": "×", - "서식": 3 - }, - "Q10": { - "값": "0%", - "서식": 8 - }, - "S10": { - "값": " ", - "서식": 9 - }, - "T10": { - "값": " ", - "서식": 9 - }, - "U10": { - "값": " =", - "서식": 3 - }, - "V10": { - "값": 0, - "서식": 10 - }, - "W10": { - "값": "㎥", - "서식": 3 - }, - "C11": { - "값": "[ { (", - "서식": 11 - }, - "D11": { - "값": 2, - "서식": 12 - }, - "E11": { - "값": "+", - "서식": 13 - }, - "F11": { - "값": 3.7, - "서식": 12 - }, - "G11": { - "값": ")", - "서식": 13 - }, - "H11": { - "값": "÷", - "서식": 13 - }, - "I11": { - "값": 2, - "서식": 13 - }, - "J11": { - "값": " × ", - "서식": 14 - }, - "K11": { - "값": 1.7, - "서식": 15 - }, - "L11": { - "값": "} - {", - "서식": 16 - }, - "M11": { - "값": 3.14, - "서식": 17 - }, - "N11": { - "값": "×", - "서식": 16 - }, - "O11": { - "값": 0.6, - "서식": 18 - }, - "P11": { - "값": "²} ", - "서식": 19 - }, - "Q11": { - "값": "]", - "서식": 20 - }, - "R11": { - "값": "×", - "서식": 13 - }, - "S11": { - "값": 1, - "서식": 16 - }, - "T11": { - "값": " ", - "서식": 21 - }, - "U11": { - "값": " =", - "서식": 13 - }, - "V11": { - "값": 3.71, - "서식": 22 - }, - "W11": { - "값": "㎥", - "서식": 13 - }, - "C12": { - "값": "{ (", - "서식": 23 - }, - "D12": { - "값": 3.7, - "서식": 24 - }, - "E12": { - "값": "+", - "서식": 25 - }, - "F12": { - "값": 4.2, - "서식": 24 - }, - "G12": { - "값": ")", - "서식": 25 - }, - "H12": { - "값": "÷", - "서식": 26 - }, - "I12": { - "값": 2, - "서식": 27 - }, - "J12": { - "값": "}", - "서식": 25 - }, - "K12": { - "값": " × ", - "서식": 28 - }, - "L12": { - "값": 0.5, - "서식": 17 - }, - "N12": { - "값": "×", - "서식": 24 - }, - "O12": { - "값": 1, - "서식": 26 - }, - "P12": { - "값": " ", - "서식": 29 - }, - "Q12": { - "값": " ", - "서식": 29 - }, - "R12": { - "값": " ", - "서식": 26 - }, - "S12": { - "값": " ", - "서식": 29 - }, - "T12": { - "값": " ", - "서식": 29 - }, - "U12": { - "값": " =", - "서식": 25 - }, - "V12": { - "값": 1.97, - "서식": 30 - }, - "W12": { - "값": "㎥", - "서식": 25 - }, - "C17": { - "값": " 1.터파기", - "서식": 31 - }, - "D17": { - "값": "토사(100%)", - "서식": 32 - }, - "E17": { - "값": "{ (", - "서식": 3 - }, - "F17": { - "식": "K22/1000", - "서식": 2 - }, - "G17": { - "값": "+", - "서식": 3 - }, - "H17": { - "식": "K5/1000", - "서식": 2 - }, - "I17": { - "값": ")", - "서식": 3 - }, - "J17": { - "값": "÷", - "서식": 4 - }, - "K17": { - "값": 2, - "서식": 5 - }, - "L17": { - "값": "}", - "서식": 3 - }, - "M17": { - "값": "×", - "서식": 6 - }, - "N17": { - "식": "R15/1000", - "서식": 7 - }, - "P17": { - "값": "×", - "서식": 2 - }, - "Q17": { - "값": 1, - "서식": 4 - }, - "R17": { - "값": "×", - "서식": 3 - }, - "S17": { - "값": 1, - "서식": 8 - }, - "U17": { - "값": " ", - "서식": 9 - }, - "V17": { - "값": " ", - "서식": 9 - }, - "W17": { - "값": " =", - "서식": 3 - }, - "X17": { - "식": "ROUNDDOWN((D26+F26)/2*L26*Q26,2)", - "서식": 10 - }, - "Y17": { - "값": "㎥", - "서식": 3 - }, - "Z17": { - "값": " ", - "서식": 33 - }, - "D18": { - "값": "암(0%)", - "서식": 32 - }, - "E18": { - "값": "{ (", - "서식": 3 - }, - "F18": { - "식": "D26", - "서식": 2 - }, - "G18": { - "값": "+", - "서식": 3 - }, - "H18": { - "식": "F26", - "서식": 2 - }, - "I18": { - "값": ")", - "서식": 3 - }, - "J18": { - "값": "÷", - "서식": 4 - }, - "K18": { - "값": 2, - "서식": 5 - }, - "L18": { - "값": "}", - "서식": 3 - }, - "M18": { - "값": "×", - "서식": 6 - }, - "N18": { - "식": "L26", - "서식": 7 - }, - "P18": { - "값": "×", - "서식": 2 - }, - "Q18": { - "값": 1, - "서식": 4 - }, - "R18": { - "값": "×", - "서식": 3 - }, - "S18": { - "값": 0, - "서식": 8 - }, - "U18": { - "값": " ", - "서식": 9 - }, - "V18": { - "값": " ", - "서식": 9 - }, - "W18": { - "값": " =", - "서식": 3 - }, - "X18": { - "식": "ROUNDDOWN((D27+F27)/2*L27*Q27,2)", - "서식": 10 - }, - "Y18": { - "값": "㎥", - "서식": 3 - }, - "Z18": { - "값": " ", - "서식": 33 - }, - "C19": { - "값": "2.되메우기", - "서식": 34 - }, - "D19": { - "값": "일반토사", - "서식": 35 - }, - "E19": { - "값": "[ { (", - "서식": 13 - }, - "F19": { - "식": "D26", - "서식": 12 - }, - "G19": { - "값": "+", - "서식": 13 - }, - "H19": { - "식": "K7/1000", - "서식": 12 - }, - "I19": { - "값": ")", - "서식": 13 - }, - "J19": { - "값": "÷", - "서식": 13 - }, - "K19": { - "값": 2, - "서식": 13 - }, - "L19": { - "값": "×", - "서식": 14 - }, - "M19": { - "식": "D16/1000", - "서식": 15 - }, - "N19": { - "값": "} - {", - "서식": 16 - }, - "O19": { - "값": 3.14, - "서식": 17 - }, - "P19": { - "값": "×", - "서식": 16 - }, - "Q19": { - "식": "K20/2000", - "서식": 18 - }, - "R19": { - "값": "²} ", - "서식": 19 - }, - "S19": { - "값": "]", - "서식": 20 - }, - "T19": { - "값": "×", - "서식": 13 - }, - "U19": { - "값": 1, - "서식": 16 - }, - "V19": { - "값": " ", - "서식": 21 - }, - "W19": { - "값": " =", - "서식": 13 - }, - "X19": { - "식": "ROUNDDOWN(((D28+F28)/2*K28-(3.14*O28*O28))*1,2)", - "서식": 22 - }, - "Y19": { - "값": "㎥", - "서식": 13 - }, - "Z19": { - "값": " ", - "서식": 36 - }, - "E20": { - "값": "{ (", - "서식": 25 - }, - "F20": { - "식": "K7/1000", - "서식": 24 - }, - "G20": { - "값": "+", - "서식": 25 - }, - "H20": { - "식": "F26", - "서식": 24 - }, - "I20": { - "값": ")", - "서식": 25 - }, - "J20": { - "값": "÷", - "서식": 26 - }, - "K20": { - "값": 2, - "서식": 27 - }, - "L20": { - "값": "}", - "서식": 25 - }, - "M20": { - "값": "×", - "서식": 28 - }, - "N20": { - "식": "D13/1000", - "서식": 17 - }, - "P20": { - "값": "×", - "서식": 24 - }, - "Q20": { - "값": 1, - "서식": 26 - }, - "R20": { - "값": " ", - "서식": 29 - }, - "S20": { - "값": " ", - "서식": 29 - }, - "T20": { - "값": " ", - "서식": 26 - }, - "U20": { - "값": " ", - "서식": 29 - }, - "V20": { - "값": " ", - "서식": 29 - }, - "W20": { - "값": " =", - "서식": 25 - }, - "X20": { - "식": "ROUNDDOWN((D29+F29)/2*L29,2)", - "서식": 30 - }, - "Y20": { - "값": "㎥", - "서식": 25 - }, - "Z20": { - "값": " ", - "서식": 37 - }, - "E21": { - "값": "계 : ", - "서식": 38 - }, - "W21": { - "값": " =", - "서식": 25 - }, - "X21": { - "식": "V28+V29", - "서식": 30 - }, - "Y21": { - "값": "㎥", - "서식": 25 - }, - "Z21": { - "값": " ", - "서식": 44 - }, - "C22": { - "값": " 4.잔 토", - "서식": 45 - }, - "D22": { - "값": " ", - "서식": 46 - }, - "E22": { - "값": " ", - "서식": 47 - }, - "F22": { - "값": "1식", - "서식": 48 - }, - "G22": { - "값": " -", - "서식": 49 - }, - "H22": { - "값": "3식", - "서식": 48 - }, - "I22": { - "값": " ", - "서식": 49 - }, - "J22": { - "값": " ", - "서식": 48 - }, - "L22": { - "값": " ", - "서식": 49 - }, - "M22": { - "값": " ", - "서식": 49 - }, - "N22": { - "값": " ", - "서식": 49 - }, - "O22": { - "값": " ", - "서식": 49 - }, - "P22": { - "값": " ", - "서식": 49 - }, - "Q22": { - "값": " ", - "서식": 49 - }, - "R22": { - "값": " ", - "서식": 49 - }, - "S22": { - "값": " ", - "서식": 47 - }, - "T22": { - "값": " ", - "서식": 47 - }, - "U22": { - "값": " ", - "서식": 47 - }, - "V22": { - "값": " ", - "서식": 47 - }, - "W22": { - "값": " =", - "서식": 50 - }, - "X22": { - "식": "(V26+V27)-V29", - "서식": 51 - }, - "Y22": { - "값": "㎥", - "서식": 50 - }, - "Z22": { - "값": " ", - "서식": 52 - }, - "F21": { - "서식": 39 - }, - "G21": { - "서식": 38 - }, - "H21": { - "서식": 39 - }, - "I21": { - "서식": 38 - }, - "J21": { - "서식": 40 - }, - "K21": { - "서식": 41 - }, - "L21": { - "서식": 38 - }, - "M21": { - "서식": 42 - }, - "N21": { - "서식": 43 - }, - "O21": { - "서식": 43 - }, - "P21": { - "서식": 39 - }, - "Q21": { - "서식": 40 - }, - "T21": { - "서식": 40 - } - }, - "병합": [ - "L12:M12", - "Q10:R10", - "L10:M10", - "Q9:R9", - "L9:M9", - "C17:C18", - "N17:O17", - "S17:T17", - "N18:O18", - "S18:T18", - "C19:C21", - "D19:D21", - "N20:O20", - "J22:K22" - ] - } - ], - "활성": "s1", - "재료번": "SM01010100", - "계산값": { - "s1": { - "F17": { - "수": "0" - }, - "H17": { - "수": "0" - }, - "N17": { - "수": "0" - }, - "X17": { - "수": "0" - }, - "F18": { - "수": "0" - }, - "H18": { - "수": "0" - }, - "N18": { - "수": "0" - }, - "X18": { - "수": "0" - }, - "F19": { - "수": "0" - }, - "H19": { - "수": "0" - }, - "M19": { - "수": "0" - }, - "Q19": { - "수": "0.001" - }, - "X19": { - "수": "0" - }, - "F20": { - "수": "0" - }, - "H20": { - "수": "0" - }, - "N20": { - "수": "0" - }, - "X20": { - "수": "0" - }, - "X21": { - "수": "0" - }, - "X22": { - "수": "0" - } - } - } -} diff --git a/resources/tester/helper_m02_master_sample.py b/resources/tester/helper_m02_master_sample.py index a12185196..12e8807fa 100644 --- a/resources/tester/helper_m02_master_sample.py +++ b/resources/tester/helper_m02_master_sample.py @@ -1,7 +1,7 @@ """시험 안 마스터 견본(PLAN 26-5a) — 초기화 전 마스터 층을 줄여 얼린 것 (`fixtures/m02_master_sample`). -집계표(바인딩 열 54) · SS 장부(51 줄) · 자재조합 · 세 파일은 rv · c4 · pp_len · ms 넷(도면 줄임) — +집계표(바인딩 열 54) · SS 장부(51 줄) · 도면 · 면적 파일은 rv · c4 · pp_len · ms 넷(도면 줄임) — 시험은 실제 `resources/master_template` 을 안 읽고 이 견본을 임시 폴더에 복사해 씀. """ diff --git a/resources/tester/spreadsheet/test_spreadsheet_saved_values.py b/resources/tester/spreadsheet/test_spreadsheet_saved_values.py index 0d165bb75..38baa3f46 100644 --- a/resources/tester/spreadsheet/test_spreadsheet_saved_values.py +++ b/resources/tester/spreadsheet/test_spreadsheet_saved_values.py @@ -1,4 +1,4 @@ -"""저장 뒤 식 · 값 유지 — M02 면적산출 · 재료산출 저장본을 서버 엔진으로 다시 풀면 저장된 `계산값` 과 같음. +"""저장 뒤 식 · 값 유지 — M02 면적산출 저장본을 서버 엔진으로 다시 풀면 저장된 `계산값` 과 같음. 시험 안 견본 저장본(`fixtures/m02_master_sample` · PLAN 26-5a)을 읽기만 함. 저장 때 서버가 적은 값과 지금 엔진 풀이가 어긋나면 화면을 다시 열 때 @@ -18,7 +18,7 @@ pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 def _books() -> list[tuple[str, dict]]: out = [] - for kind in ("area", "material"): + for kind in ("area",): for path in sorted((FOLDER / kind).glob("[!.]*.json")): book = json.loads(path.read_text(encoding="utf-8")) if isinstance(book, dict) and book.get("시트"): diff --git a/resources/tester/spreadsheet/test_spreadsheet_store.py b/resources/tester/spreadsheet/test_spreadsheet_store.py index edac0a183..3b3a185df 100644 --- a/resources/tester/spreadsheet/test_spreadsheet_store.py +++ b/resources/tester/spreadsheet/test_spreadsheet_store.py @@ -1,4 +1,4 @@ -"""F — 면적산출(area) · 재료산출(material) 저장 · 옛 산출근거(basis) 옮김 · [저장] 때 서버 계산값 · +"""F — 면적산출(area) 저장 · 옛 산출근거(basis) 옮김 · [저장] 때 서버 계산값 · 알약 연결(`연결`)은 저장 · 복사 때 문서와 같이. 서버 Node 풀이는 `test_spreadsheet_node.py` 가 따로 봄 — 여기는 `with_server_values` 를 바꿔 끼워 @@ -42,7 +42,7 @@ def _write(folder, kind, column, doc): def test_옛_산출근거를_면적산출로_옮기고_번호_8자리(client, tmp_path): - """12장 파일(SD · SC 6자리 · basis/) → SA 로 옮김 · 끝에 00 · 재료산출 새로 · 내용 그대로.""" + """12장 파일(SD · SC 6자리 · basis/) → SA 로 옮김 · 끝에 00 · 재료산출은 안 만듦 · 내용 그대로.""" structure = {"양식": "구조물도면", "종류": "structure", "판": 1, "열": "rv", "도번": "SD010101"} _write(tmp_path, "structure", "rv", {**structure, "도면": {"entities": [{"type": "line"}]}}) old = {**store._blank("area", "rv"), "표번": "SC010101", "연결": LINKS} @@ -56,30 +56,26 @@ def test_옛_산출근거를_면적산출로_옮기고_번호_8자리(client, tm "A2": {"값": "터파기"} } assert store.read("structure", "rv")["문서"]["도번"] == "SD01010100" - material = store.read("material", "rv")["문서"] - assert (material["재료번"], material["시트"][0]["이름"]) == ("SM01010100", "재료산출") - assert material["시트"][0]["칸"] == {} and material["서식"] == [{}] # 진짜 빈 시트 + assert not (tmp_path / "material").exists() # 재료산출은 없앰 before = {p: p.read_bytes() for p in tmp_path.rglob("*.json")} assert store.renumber() == {"옮김": [], "지움": [], "바꿈": [], "새로": []} assert before == {p: p.read_bytes() for p in tmp_path.rglob("*.json")} # 두 번 돌려도 같음 -def test_새_구조물_도면은_면적_재료_진짜_빈_시트_같은_8자리(client): +def test_새_구조물_도면은_면적_진짜_빈_시트_같은_8자리(client): made1 = client.post("/api/m02/structures/rv").json()["문서"] made2 = client.post("/api/m02/structures/pv_b").json()["문서"] # 새 항목 번호 = 자리 뜻 없는 일련번호(PLAN 26-1) assert made1["도번"] == "SD-000001" and made2["도번"] == "SD-000002" - for kind, key, head in (("area", "면적번", "SA"), ("material", "재료번", "SM")): + for kind, key, head in (("area", "면적번", "SA"),): doc = client.get(f"/api/m02/templates/{kind}/pv_b").json()["문서"] assert doc[key] == f"{head}-000002" assert doc["시트"][0]["칸"] == {} and doc["서식"] == [{}] # 머리 줄 없음 numbers = client.get("/api/m02/structures/numbers").json() - assert numbers["pv_b"] == {"도번": "SD-000002", "면적번": "SA-000002", "재료번": "SM-000002"} + assert numbers["pv_b"] == {"도번": "SD-000002", "면적번": "SA-000002"} -@pytest.mark.parametrize( - ("kind", "key", "head"), [("area", "면적번", "SA"), ("material", "재료번", "SM")] -) +@pytest.mark.parametrize(("kind", "key", "head"), [("area", "면적번", "SA")]) def test_다른_열_내용을_복사해_가져옴_연결도_같이(client, kind, key, head): store.renumber() got = client.get(f"/api/m02/templates/{kind}/rv").json() @@ -117,21 +113,21 @@ def test_저장은_서버_계산값만_적음_연결_두고_번호는_서버_것 def test_못_풀면_저장을_막음(client, monkeypatch): - store._ensure("material", "rv") - got = client.get("/api/m02/templates/material/rv").json() + store._ensure("area", "rv") + got = client.get("/api/m02/templates/area/rv").json() def broken(_book): raise spreadsheet.RecalcError("스프레드시트 계산 실패 — 시험") monkeypatch.setattr(spreadsheet, "with_server_values", broken) - r = client.put("/api/m02/templates/material/rv", json={"판": got["판"], "문서": got["문서"]}) + r = client.put("/api/m02/templates/area/rv", json={"판": got["판"], "문서": got["문서"]}) assert r.status_code == 503 - assert client.get("/api/m02/templates/material/rv").json()["판"] == got["판"] + assert client.get("/api/m02/templates/area/rv").json()["판"] == got["판"] def test_층_규칙과_프로젝트_복사(client, tmp_path, monkeypatch): client.post("/api/m02/structures/rv") - for kind in ("area", "material"): + for kind in ("area",): doc = store.read(kind, "rv")["문서"] layers.check_skeleton(kind, doc) for bad in ({}, {**doc, "시트": {}}): @@ -140,5 +136,6 @@ def test_층_규칙과_프로젝트_복사(client, tmp_path, monkeypatch): layers.check_skeleton("structure", {"도면": {"entities": []}}) # 산출근거 없어도 됨 monkeypatch.setattr(layers, "SYSTEM_ROOT", tmp_path) copied = layers.seed_project(tmp_path / "project") - for kind in ("area", "material"): + assert not any(k.startswith("material/") for k in copied["작업본"]) # 재료산출 없앰 + for kind in ("area",): assert f"{kind}/rv" in copied["작업본"] and f"{kind}/rv" in copied["초기"] diff --git a/resources/tester/test_item_copy_complete.py b/resources/tester/test_item_copy_complete.py index 3f244c1f6..ba23d9e8a 100644 --- a/resources/tester/test_item_copy_complete.py +++ b/resources/tester/test_item_copy_complete.py @@ -54,7 +54,7 @@ def world(tmp_path: Path, monkeypatch, master_copy: Path) -> dict[str, Any]: system.mkdir() for name in (layers.COMBO_FILE,): shutil.copy2(REAL / name, system / name) - for kind in ("table", "structure", "area", "material"): + for kind in ("table", "structure", "area"): shutil.copytree(REAL / kind, system / kind) monkeypatch.setattr(layers, "SYSTEM_ROOT", system) monkeypatch.setattr(m01, "FOLDER", master_copy) diff --git a/resources/tester/test_m02_combo_branch.py b/resources/tester/test_m02_combo_branch.py index 0a8d060c4..091b9a316 100644 --- a/resources/tester/test_m02_combo_branch.py +++ b/resources/tester/test_m02_combo_branch.py @@ -196,7 +196,7 @@ def test_분류_더하기로_복사한_열은_원본_키_세_파일_내용을_ numbers = client.get("/api/m02/structures/numbers").json() assert numbers["c9"]["도번"] != numbers["rv"]["도번"] # 새 번호 assert numbers["c9"]["면적번"][2:] == numbers["c9"]["도번"][2:] - for kind in ("area", "material"): + for kind in ("area",): assert client.get(f"/api/m02/templates/{kind}/c9").json()["문서"]["열"] == "c9" # 그 뒤로는 열마다 따로 — 원본을 고쳐도 복사본은 그대로 drawing = client.get("/api/m02/templates/structure/rv").json() diff --git a/resources/tester/test_m02_empty_master.py b/resources/tester/test_m02_empty_master.py index 24a96ac8f..571848174 100644 --- a/resources/tester/test_m02_empty_master.py +++ b/resources/tester/test_m02_empty_master.py @@ -80,7 +80,7 @@ def test_지운_뒤_항목_0_틀은_남음(empty): combos = client.get("/api/m02/structure-combos").json() assert combos["줄"] == [] and combos["다음"] == 52 assert len(combos["지운번호"]) == 4 # 견본에 세 파일이 있던 넷(rv · c4 · pp_len · ms) - for kind in ("structure", "area", "material", "inputs"): + for kind in ("structure", "area", "inputs"): assert not list((system / kind).glob("*.json")), kind diff --git a/resources/tester/test_m02_item_keys.py b/resources/tester/test_m02_item_keys.py index f6c6a82ee..2e25025ca 100644 --- a/resources/tester/test_m02_item_keys.py +++ b/resources/tester/test_m02_item_keys.py @@ -164,7 +164,7 @@ def test_새_번호는_옛_번호와_모양이_달라_안_겹침(world): doc = json.loads(path.read_bytes()) path.write_text(json.dumps({**doc, key: f"{head}{code}"}), encoding="utf-8") old = {cid: _numbers(system, cid) for cid in store._column_ids()} - assert old["rv"] == {"SD01010100", "SA01010100", "SM01010100"} + assert old["rv"] == {"SD01010100", "SA01010100"} first = max(int(n[3:]) for nums in old.values() for n in nums if NEW.match(n)) + 1 with store.at(system): got = store.read("table", store.STRUCTURE_TABLE) @@ -191,7 +191,7 @@ def test_새_번호는_옛_번호와_모양이_달라_안_겹침(world): got["문서"]["열"].append({"id": "y", "머리": ["y"], "꼴": "수"}) store.write("table", store.STRUCTURE_TABLE, got["판"], got["문서"]) last = f"-{first + 120:06d}" # 지운 x119 번호(first + 119) 다음 - assert _numbers(system, "y") == {f"SD{last}", f"SA{last}", f"SM{last}"} + assert _numbers(system, "y") == {f"SD{last}", f"SA{last}"} def test_층마다_번호는_따로_파일은_열_id_로(world): diff --git a/resources/tester/test_m02_layers_store.py b/resources/tester/test_m02_layers_store.py index 6a15f6993..ab7551876 100644 --- a/resources/tester/test_m02_layers_store.py +++ b/resources/tester/test_m02_layers_store.py @@ -68,7 +68,7 @@ def test_시스템_층은_그대로_항목_목록은_SS_줄(world): ss = store.read_combos()["줄"] assert [r["key"] for r in rows] == [r["키"] for r in ss] and rows[0]["key"] == "SS000001" assert set(rows[0]) == {"key", "names", *items_router.items_store.ITEM_FIELDS} - assert set(rows[0]["names"]) == {"일위대가", "도번", "면적번", "재료번"} + assert set(rows[0]["names"]) == {"일위대가", "도번", "면적번"} assert client.get("/api/m02/layers/system/structure-combos").json() == store.read_combos() # 시스템 층 쓰기는 SYSTEM_ADMIN 만 assert client.post("/api/m02/layers/system/structures/rv").status_code == 403 @@ -145,7 +145,6 @@ def test_회사_항목을_개인으로_복사(world): "일위대가": ua_name, "도번": "돌기슭막이 찰쌓기 회사형", "면적번": "돌기슭막이 찰쌓기 회사형", - "재료번": "돌기슭막이 찰쌓기 회사형", } user = _as(world) diff --git a/resources/tester/test_m02_structure.py b/resources/tester/test_m02_structure.py index c532c67ca..da92265cb 100644 --- a/resources/tester/test_m02_structure.py +++ b/resources/tester/test_m02_structure.py @@ -1,7 +1,7 @@ """M02 구조물 도면(structure) — 새로 · 도번 SD+8자리 · 409 · 저장 · 지운 뒤 번호 · 번호 매김(새 항목 = 자리 뜻 없는 일련번호 · PLAN 26-1) · 집계표 저장 때 만들기/지우기 · 복사 가져오기 · 프로젝트 복사. -면적산출(area) · 재료산출(material)은 따로 파일 — +면적산출(area)은 따로 파일(재료산출은 없앰) — `resources/tester/spreadsheet/test_spreadsheet_store.py`.""" import json @@ -64,8 +64,8 @@ def client(tmp_path, monkeypatch): def _num(code: str) -> dict: - """세 번호 — 같은 8자리.""" - return {"도번": f"SD{code}", "면적번": f"SA{code}", "재료번": f"SM{code}"} + """두 번호 — 같은 8자리.""" + return {"도번": f"SD{code}", "면적번": f"SA{code}"} def test_새로_도번_자동_409_없는_열(client): @@ -82,10 +82,10 @@ def test_새로_도번_자동_409_없는_열(client): assert doc["도면"]["format"] == 6 assert doc["도면"]["entities"] == [] assert len(doc["도면"]["layers"]) == 1 - # 면적산출 · 재료산출은 구조물 문서 밖 — 같은 열 id 로 따로 한 통합문서씩 + # 면적산출은 구조물 문서 밖 — 같은 열 id 로 따로 한 통합문서 · 재료산출은 안 만듦 assert "산출근거" not in doc - for kind in ("area", "material"): - assert client.get(f"/api/m02/templates/{kind}/pp_len").json()["문서"]["종류"] == "통합문서" + assert client.get("/api/m02/templates/area/pp_len").json()["문서"]["종류"] == "통합문서" + assert client.get("/api/m02/templates/material/pp_len").status_code == 404 assert client.post("/api/m02/structures/rv").json()["문서"]["도번"] == "SD-000002" assert client.post("/api/m02/structures/pp_len").status_code == 409 @@ -127,7 +127,7 @@ def test_지워도_번호를_당기지_않음_짝은_같은_번호(client): client.post(f"/api/m02/structures/{column}") assert client.delete("/api/m02/templates/structure/rv").status_code == 200 numbers = client.get("/api/m02/structures/numbers").json() - assert numbers["rv"] == {**_num("-000002"), "도번": ""} # 면적 · 재료는 남아 번호를 쥠 + assert numbers["rv"] == {**_num("-000002"), "도번": ""} # 면적은 남아 번호를 쥠 assert numbers["ms"] == _num("-000003") # 다른 열 번호는 그대로 # 새 열 = 다음 일련번호(자리 뜻 없음) · 다시 만든 도면은 짝(면적산출) 번호를 이어 받음 assert client.post("/api/m02/structures/pp_cp").json()["문서"]["도번"] == "SD-000004" @@ -181,7 +181,7 @@ def test_집계표_저장_때_새_열은_만들고_지운_열은_같이_지움(c assert saved.status_code == 200 numbers = client.get("/api/m02/structures/numbers").json() assert "pv_a" not in numbers and "note2" not in numbers - for kind_ in ("structure", "area", "material"): + for kind_ in ("structure", "area"): assert client.get(f"/api/m02/templates/{kind_}/pv_a").status_code == 404 # 새 열 = 열 차례로 다음 일련번호 · 지운 pv_a(5) 는 비워 둠(다른 번호를 안 당김) assert numbers["pv_x"] == _num("-000014") diff --git a/resources/tester/test_m02_structure_combo.py b/resources/tester/test_m02_structure_combo.py index 4333b2140..56ceb47b5 100644 --- a/resources/tester/test_m02_structure_combo.py +++ b/resources/tester/test_m02_structure_combo.py @@ -84,9 +84,9 @@ def test_집계표_저장_때_새_열_지운_열_고친_열을_맞춤_키_유지 assert (rv["분류1"], rv["분류3"]) == (old_rv["분류1"], old_rv["분류3"]) assert (rv["단위"], rv["일위대가"]) == ("m", "UA000004") assert (rv["바인딩"], rv["펼침"]) == ({"종류": "revetment"}, True) - # 번호는 세 파일과 같음 — 새 열도 [저장] 한 번에 SD · SA · SM 이 섬 + # 번호는 파일과 같음 — 새 열도 [저장] 한 번에 SD · SA 가 섬 numbers = client.get("/api/m02/structures/numbers").json() - assert {k: rows["new1"][k] for k in ("도번", "면적번", "재료번")} == numbers["new1"] + assert {k: rows["new1"][k] for k in ("도번", "면적번")} == numbers["new1"] assert rows["new1"]["도번"].startswith("SD") # 또 저장해도(바뀐 것 없음) 키 · 다음 그대로 diff --git a/resources/tester/test_m02_structure_panel.py b/resources/tester/test_m02_structure_panel.py index ea853da66..d05b30a4d 100644 --- a/resources/tester/test_m02_structure_panel.py +++ b/resources/tester/test_m02_structure_panel.py @@ -51,8 +51,8 @@ def test_소스가_700줄을_넘지_않음(): ), # 산출근거 저장은 컨테이너가 안 함 — [산출근거 수정] 이 여는 메인 칸 전체 편집이 함 ("M02_MasterTemplete_Api_Fetch.ts", ["saveTemplate"], MAIN), - # 좌측 구조물 도면 · 면적산출 · 재료산출 줄 번호는 한 번에 받음 - # (`{열 id: {도번, 면적번, 재료번}}`) + # 좌측 구조물 도면 · 면적산출 줄 번호는 한 번에 받음 + # (`{열 id: {도번, 면적번}}`) ("M02_MasterTemplete_Api_Fetch.ts", ["fetchStructureNumbers"], SIDE), ("M02_MasterTemplete_Structure.ts", ["structureLabel", "isStructureColumn"], SIDE), # 도면 카드(31-4)는 카드 파일에서 뗀 `DrawingCard` 가 부름 @@ -72,15 +72,13 @@ def test_컨테이너가_쓰는_이름이_내보내진_것(source: str, names: l def test_sub2_가_부르는_이름과_모양(): """브레인 확정 이름 · 모양 — sub2 가 이대로 부름(바꾸면 브레인에게).""" body = _text(M02 / "M02_MasterTemplete_Api_Fetch.ts") - for name in ("copyStructure", "copyArea", "copyMaterial"): + for name in ("copyStructure", "copyArea"): assert re.search( rf"export const {name} = async \(column: string, from: string\): Promise", body ), name assert "fetchStructureNumbers = (): Promise>" in body - assert re.search( - r"interface StructureNumbers \{\s*도번: string;\s*면적번: string;\s*재료번: string;", body - ) - assert re.search(r'export type Kind = [^;]*"area" \| "material"', body) + assert re.search(r"interface StructureNumbers \{\s*도번: string;\s*면적번: string;\s*\}", body) + assert re.search(r'export type Kind = [^;]*"area";', body) def test_집계표만_컨테이너를_붙임(): @@ -154,7 +152,7 @@ def test_좌측_구조물_집계_양식_아래_하위_넷_새로_본떠_지우 assert "createSelectField" not in side # 새로 = 종류 고르기 없이 이름만 assert "cur?.종류 !== kind" in side # 본떠 · 지우기 = 그 컨테이너에서 고른 것이 있을 때만 assert "i.이름 === STRUCTURE_TABLE" in side # 표는 구조물집계표 한 줄만 - # 구조물 도면 · 면적산출 · 재료산출 = 구조물 집계 양식 컨테이너 안(같이 접힘) · 자재 조합 없앰 + # 구조물 도면 · 면적산출 = 구조물 집계 양식 컨테이너 안(같이 접힘) · 자재 조합 없앰 summary = side.split("const summary = section(")[1].split(");")[0] assert "COLUMN_KINDS.map" in summary and "matset" not in side assert 'M02_KindTable: ["구조물 조합"' in _text( # 화면 이름(26-7a) @@ -214,7 +212,7 @@ def test_표_도구_막대에_줄_더하기_줄_지우기_손_입력_열_없음( def test_상세_화면_돌아가기는_집계표로_그_열_고른_채(): main = _text(MAIN) assert "children: [title, back, slot, reload, save, " in main # 제목 바로 오른쪽 - assert "!(sel.kind in NUMBER_FIELD)" in main # 구조물 도면 · 면적산출 · 재료산출만 + assert "!(sel.kind in NUMBER_FIELD)" in main # 구조물 도면 · 면적산출만 body = main.split('back.addEventListener("click"')[1] assert "confirmLeave()" in body and "STRUCTURE_TABLE" in body assert "sheet?.selectColumn(column)" in body diff --git a/resources/tester/test_m02_tree.py b/resources/tester/test_m02_tree.py index 90bce3ee5..b6ec3fb80 100644 --- a/resources/tester/test_m02_tree.py +++ b/resources/tester/test_m02_tree.py @@ -188,7 +188,7 @@ def test_빈_층에서_폴더_항목_깊이_1_2_5_이름_옮기기_지우기(wor (root / "inputs" / f"{gone['열']}.json").write_text('{"판": 1, "줄": []}', encoding="utf-8") note = t.do(op="delete", 키=gone["key"])["알림"] assert note["지운항목"] == [gone["열"]] - for part in ("structure", "area", "material", "inputs"): + for part in ("structure", "area", "inputs"): assert not (root / part / f"{gone['열']}.json").is_file() t.do(op="add-item", 경로=["돌쌓기"], 이름="새것") # 지운 번호는 다시 안 줌 assert _numbers(root, t.item(["돌쌓기", "새것"])["열"]) == {"-000004"} diff --git a/resources/tester/test_m02_tree_paste.py b/resources/tester/test_m02_tree_paste.py index 46ee0bb58..56a3d2a72 100644 --- a/resources/tester/test_m02_tree_paste.py +++ b/resources/tester/test_m02_tree_paste.py @@ -50,7 +50,7 @@ def test_회사_항목_폴더를_개인에_붙임_새_키_파일_이름2(world): table = json.loads((root / "table/구조물집계표.json").read_bytes()) made = next(c for c in table["열"] if c.get("경로") == ["옹벽"]) assert made["본뜬"] == {"layer": "company", "owner": "7", "key": wall} - for kind in ("structure", "area", "material"): + for kind in ("structure", "area"): assert (root / kind / f"{made['id']}.json").is_file() # 다시 붙이면 같은 이름 「(2)」 · 붙일 폴더 안으로 again = client.post(PASTE.format("personal"), json={**body, "경로": []}).json() diff --git a/resources/tester/test_sheet_drawing_select.py b/resources/tester/test_sheet_drawing_select.py index 957e4915f..2909841c4 100644 --- a/resources/tester/test_sheet_drawing_select.py +++ b/resources/tester/test_sheet_drawing_select.py @@ -1,6 +1,6 @@ """표 도면 줄 · 칸 고르기 신호 — `ui_template_sheet_ops.ts` 를 Node 로 바로 돌려 봄. -마스터 줄 = 일위대가 · 도면 `s:drawing` · 면적산출 `s:area` · 재료산출 `s:material`(식 · 들어갈 것 줄 없음) · +마스터 줄 = 일위대가 · 도면 `s:drawing` · 면적산출 `s:area`(식 · 들어갈 것 줄 없음) · master 만 · 단위 줄 바로 아래 차례(키보드) · project 에는 없음. `selectionOf` = 격자가 `onSelect` 로 넘기는 모양({row, col, colId} · 모르는 열은 col -1 · colId null). """ @@ -26,7 +26,7 @@ console.log(JSON.stringify({ master: navRows(doc, "master"), project: navRows(doc, "project"), pick: [selectionOf(doc, "s:drawing", "c2"), selectionOf(doc, "d:r1", "c1"), selectionOf(doc, "s:unit", "zz")], - blank: ["s:price", "s:drawing", "s:area", "s:material", "s:unit", "d:r1"].map((k) => [isBlankPick(k, { id: "t", 머리: [], 꼴: "글" }), isBlankPick(k, { id: "n", 머리: [], 꼴: "수" })]), + blank: ["s:price", "s:drawing", "s:area", "s:unit", "d:r1"].map((k) => [isBlankPick(k, { id: "t", 머리: [], 꼴: "글" }), isBlankPick(k, { id: "n", 머리: [], 꼴: "수" })]), })); """ @@ -53,7 +53,7 @@ def _run() -> dict: def test_drawing_row_under_price_master_only(): got = _run() assert got["master"] == [ - "s:unit", "s:price", "s:drawing", "s:area", "s:material", "d:r1", "t:t1", + "s:unit", "s:price", "s:drawing", "s:area", "d:r1", "t:t1", ] # fmt: skip assert got["project"] == ["s:unit", "d:r1", "t:t1"] @@ -68,5 +68,5 @@ def test_selection_shape(): def test_text_column_pick_rows_blank(): assert _run()["blank"] == [ - [True, False], [True, False], [True, False], [True, False], [False, False], [False, False], + [True, False], [True, False], [True, False], [False, False], [False, False], ] # fmt: skip diff --git a/resources/tester/test_structures_summary_snapshot.py b/resources/tester/test_structures_summary_snapshot.py index 8903f7ce8..188d46d24 100644 --- a/resources/tester/test_structures_summary_snapshot.py +++ b/resources/tester/test_structures_summary_snapshot.py @@ -36,7 +36,7 @@ def _copy_layer(target: Path) -> None: target.mkdir(parents=True) for name in (layers.COMBO_FILE,): shutil.copy2(REAL / name, target / name) - for kind in ("table", "structure", "area", "material"): + for kind in ("table", "structure", "area"): shutil.copytree(REAL / kind, target / kind) @@ -131,7 +131,12 @@ def test_지정하면_폴더가_생기고_내용이_원본과_같음(world): assert result == {"만듦": ["pipe@40.0", "st0"], "지움": [], "못만듦": [], "옮겨받음": []} folder = _items(root) / "st0" got = {path.name for path in folder.iterdir()} - assert {"집계.json", "도면.json", "면적.json", "재료.json", "manifest.json"} <= got + assert { + "집계.json", + "도면.json", + "면적.json", + "manifest.json", + } <= got and "재료.json" not in got total = json.loads((folder / "집계.json").read_text(encoding="utf-8")) assert total["줄"]["키"] == "SS000022" and total["열"]["id"] == "ms" drawing = json.loads((world["system"] / "structure/ms.json").read_text(encoding="utf-8")) diff --git a/resources/tester/ui/item-picker-data.test.ts b/resources/tester/ui/item-picker-data.test.ts index 057c13c14..256912023 100644 --- a/resources/tester/ui/item-picker-data.test.ts +++ b/resources/tester/ui/item-picker-data.test.ts @@ -43,7 +43,6 @@ const row = ( 일위대가: "UA000004", 도번: "SD01010100", 면적번: "SA01010100", - 재료번: "SM01010100", layer, owner: layer === "company" ? "샘플회사" : layer === "personal" ? "샘플회사/사용자" : null, ...extra, @@ -55,7 +54,6 @@ const rows: PickerRow[] = [ 일위대가: "돌기슭막이 메쌓기 일위대가", 도번: "돌기슭막이 도면", 면적번: "돌기슭막이 면적산출", - 재료번: "돌기슭막이 재료산출", }, }), row("SS000002", "rv2", "돌기슭막이", ["메쌓기", "1.5m", "기초유"], "system"), @@ -219,20 +217,20 @@ assert.deepEqual(pathIds(rows[0], labels), [ "system|돌기슭막이|메쌓기|1.2m", ]); -// 폴더 모양 — 네 줄 · 이름은 서버 names · 없으면 키만(작은 글) · 둘 다 없으면 빈 키 +// 폴더 모양 — 세 줄 · 이름은 서버 names · 없으면 키만(작은 글) · 둘 다 없으면 빈 키 const lines = folderLines(rows[0]); assert.deepEqual( lines.map((l) => l.kind), - ["drawing", "area", "material", "unitPrice"], + ["drawing", "area", "unitPrice"], ); assert.deepEqual(lines[0], { kind: "drawing", name: "돌기슭막이 도면", key: "SD01010100" }); -assert.deepEqual(lines[3], { +assert.deepEqual(lines[2], { kind: "unitPrice", name: "돌기슭막이 메쌓기 일위대가", key: "UA000004", }); assert.deepEqual(folderLines(rows[1])[0], { kind: "drawing", name: null, key: "SD01010100" }); // names 자체가 없음 -assert.deepEqual(folderLines({ key: "SS1", 열: "x" })[3], { +assert.deepEqual(folderLines({ key: "SS1", 열: "x" })[2], { kind: "unitPrice", name: null, key: "", diff --git a/ui_template/sheet/ui_template_sheet.ts b/ui_template/sheet/ui_template_sheet.ts index 34435f640..35372ff8a 100644 --- a/ui_template/sheet/ui_template_sheet.ts +++ b/ui_template/sheet/ui_template_sheet.ts @@ -1,12 +1,12 @@ /* ============================================================================= * ui_template_sheet.ts - * 엑셀처럼 도는 표 부품 — `createSheet(칸, 문서, {mode, onChange, drawingLabel, areaLabel, materialLabel, onSelect, onPick})` + * 엑셀처럼 도는 표 부품 — `createSheet(칸, 문서, {mode, onChange, drawingLabel, areaLabel, onSelect, onPick})` * → `{getDoc, setDoc, recalc, selectColumn, destroy}`. * * master = 시스템 관리자 양식 고치기 — 머리 · 층 이름 · 단위 · 일위대가 · * 층마다 열 더하기(「분류1 더하기」 … 층 이름 + 더하기) · 열 지우기. - * 도면 · 면적산출 · 재료산출 줄 = `drawingLabel` · `areaLabel` · `materialLabel` 글 · - * 칸을 고르면 `onSelect` · `onPick` 을 주면 일위대가 · 도면 · 면적산출 · 재료산출 칸은 + * 도면 · 면적산출 줄 = `drawingLabel` · `areaLabel` 글 · + * 칸을 고르면 `onSelect` · `onPick` 을 주면 일위대가 · 도면 · 면적산출 칸은 * 치지 않고 조회 후 선택. * `readOnly` = 볼 수만 있음 — 단추 줄 · 안내 글 숨김 · 칸 고치기 · 머리 고치기 · 열 너비 끌기 막음 · * 열이 하나도 없으면 [열 더하기] 가 켜져 첫 열을 만듦(머리 글 바로 고치기). @@ -56,12 +56,11 @@ export interface SheetOptions { onChange?: (doc: SheetDoc) => void; /** 도면 줄 칸 글(마스터 첫 쪽 · 열 id) — 없거나 빈 글이면 「없음」 · 글이 바뀌면 `recalc()` 로 다시 그림. */ drawingLabel?: (colId: string) => string; - /** 면적산출 · 재료산출 줄 칸 글(번호) — `drawingLabel` 과 같음. */ + /** 면적산출 줄 칸 글(번호) — `drawingLabel` 과 같음. */ areaLabel?: (colId: string) => string; - materialLabel?: (colId: string) => string; /** 칸을 고름 — 누르기 · 방향키 · Tab. */ onSelect?: (sel: SheetSelection) => void; - /** 조회 후 선택 — master 일위대가 · 도면 · 면적산출 · 재료산출 칸을 두 번 누르기 · Enter · F2 로 부름. + /** 조회 후 선택 — master 일위대가 · 도면 · 면적산출 칸을 두 번 누르기 · Enter · F2 로 부름. * 글을 돌려주면 일위대가 칸에 넣음 · 번호 글 줄은 부른 쪽이 가져온 뒤 `recalc()`. * 없으면 일위대가는 글로 고침 · 번호 글 줄은 읽기만. */ onPick?: (sel: SheetSelection) => Promise | void; @@ -90,7 +89,6 @@ export function createSheet(host: HTMLElement, input: SheetDoc, opts: SheetOptio sel: null, drawingLabel: opts.drawingLabel, areaLabel: opts.areaLabel, - materialLabel: opts.materialLabel, pick: !!opts.onPick, headLocked: !!opts.lockHead, combo: !!opts.combo, diff --git a/ui_template/sheet/ui_template_sheet_ops.ts b/ui_template/sheet/ui_template_sheet_ops.ts index 1dc2db7e8..a3d807da6 100644 --- a/ui_template/sheet/ui_template_sheet_ops.ts +++ b/ui_template/sheet/ui_template_sheet_ops.ts @@ -22,16 +22,15 @@ export const KEY_UNIT = "s:unit"; export const KEY_PRICE = "s:price"; export const KEY_DRAWING = "s:drawing"; export const KEY_AREA = "s:area"; -export const KEY_MATERIAL = "s:material"; -/** 마스터 전용 줄(첫 쪽 단위 줄 아래 차례) — 도면 · 면적산출 · 재료산출 줄은 손으로 못 침(번호 글). */ -export const MASTER_ROWS = [KEY_PRICE, KEY_DRAWING, KEY_AREA, KEY_MATERIAL] as const; +/** 마스터 전용 줄(첫 쪽 단위 줄 아래 차례) — 도면 · 면적산출 줄은 손으로 못 침(번호 글). */ +export const MASTER_ROWS = [KEY_PRICE, KEY_DRAWING, KEY_AREA] as const; export type MasterRowKey = (typeof MASTER_ROWS)[number]; /** 번호 글 줄 — 부른 쪽이 준 글(`refLabel`)만 보임. */ -export const REF_ROWS: readonly string[] = [KEY_DRAWING, KEY_AREA, KEY_MATERIAL]; +export const REF_ROWS: readonly string[] = [KEY_DRAWING, KEY_AREA]; /** 조회 후 선택 줄 — `onPick` 을 주면 이 줄 칸은 치지 않고 고름. */ export const PICK_ROWS: readonly string[] = MASTER_ROWS; -/** 글 열(NO · 측점 · 비고 같은 꼴=글)의 일위대가 · 도면 · 면적산출 · 재료산출 칸 — 빈칸 · 잠김. */ +/** 글 열(NO · 측점 · 비고 같은 꼴=글)의 일위대가 · 도면 · 면적산출 칸 — 빈칸 · 잠김. */ export const isBlankPick = (key: string, col: SheetColumn): boolean => PICK_ROWS.includes(key) && col.꼴 === "글"; diff --git a/ui_template/sheet/ui_template_sheet_render.ts b/ui_template/sheet/ui_template_sheet_render.ts index f42b007a1..3b5113938 100644 --- a/ui_template/sheet/ui_template_sheet_render.ts +++ b/ui_template/sheet/ui_template_sheet_render.ts @@ -3,8 +3,8 @@ * 표 그리기 — 문서 + 풀이 결과 → 쪽마다 ``(머리 층 · 단위 줄 · 본문 · 합계 줄). * * 칸마다 `data-r`(줄 열쇠) · `data-c`(열 id) — 고치기 · 키보드는 격자(`ui_template_sheet.ts`)가 위임으로 받음. - * 줄 열쇠 — 본문 `d:<줄id>` · 합계 `t:<합계id>` · 마스터 줄 `s:unit|price|drawing|area|material`. - * master = 본문 위에 열마다 일위대가 · 도면 · 면적산출 · 재료산출 줄 · project = 쪽줄마다 쪽을 나눔(쪽마다 머리 · 합계는 마지막 쪽). + * 줄 열쇠 — 본문 `d:<줄id>` · 합계 `t:<합계id>` · 마스터 줄 `s:unit|price|drawing|area`. + * master = 본문 위에 열마다 일위대가 · 도면 · 면적산출 줄 · project = 쪽줄마다 쪽을 나눔(쪽마다 머리 · 합계는 마지막 쪽). * 머리 층 줄 머리 칸은 `data-layer`(층 차례) — master 에서 층 이름을 고침. * ========================================================================== */ @@ -17,7 +17,6 @@ import { isBlankPick, isCalcColumn, KEY_AREA, - KEY_MATERIAL, KEY_UNIT, MASTER_ROWS, type MasterRowKey, @@ -39,10 +38,9 @@ export interface RenderState { mode: SheetMode; result: SheetResult; sel: { r: string; c: string } | null; - /** 도면 · 면적산출 · 재료산출 줄 칸 글(열 id) — 없거나 빈 글이면 「없음」. */ + /** 도면 · 면적산출 줄 칸 글(열 id) — 없거나 빈 글이면 「없음」. */ drawingLabel?: (colId: string) => string; areaLabel?: (colId: string) => string; - materialLabel?: (colId: string) => string; /** 조회 후 선택 줄(`PICK_ROWS`)을 고름으로 받나 — 칸 겉모양만. */ pick?: boolean; /** 머리 · 층 이름을 못 고침(`lockHead`) — 칸 겉모양만. */ @@ -53,13 +51,9 @@ export interface RenderState { headEditable?: boolean; } -/** 도면 · 면적산출 · 재료산출 줄 칸 글. */ +/** 도면 · 면적산출 줄 칸 글. */ export const refText = (state: RenderState, key: string, colId: string): string => - (key === KEY_AREA - ? state.areaLabel - : key === KEY_MATERIAL - ? state.materialLabel - : state.drawingLabel)?.(colId)?.trim() || st("Drawing_None"); + (key === KEY_AREA ? state.areaLabel : state.drawingLabel)?.(colId)?.trim() || st("Drawing_None"); /** 열 폭 — 저장 폭(없으면 기본) · 머리 글이 안 잘리는 최소 폭보다 좁지 않게. */ const columnWidth = (doc: SheetDoc, col: SheetColumn, mins: Map): number => @@ -68,7 +62,7 @@ const columnWidth = (doc: SheetDoc, col: SheetColumn, mins: Map) mins.get(col.id) ?? 0, ); -/** 구조물 조합 — 일위대가 · 도면 · 면적산출 · 재료산출 줄 글(키 · 번호)이 줄바꿈 없이 들게 최소 폭을 넓힘 +/** 구조물 조합 — 일위대가 · 도면 · 면적산출 줄 글(키 · 번호)이 줄바꿈 없이 들게 최소 폭을 넓힘 * (`.ui-sheet__meta td` 글꼴과 같게 잼). */ function fitMeta(state: RenderState, cols: SheetColumn[], mins: Map): void { const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16; @@ -115,7 +109,7 @@ export function renderSheet(state: RenderState): HTMLElement { const mins = headMinWidths(cols, depth, font); if (state.combo) fitMeta(state, cols, mins); const widthDoc = state.combo ? { ...doc, 보기: { ...doc.보기, 열너비: {} } } : doc; - // 줄 머리 칸 폭 — 층 이름 · 단위 · 일위대가 · 도면 · 면적산출 · 재료산출 · 합계 이름이 안 잘리게 + // 줄 머리 칸 폭 — 층 이름 · 단위 · 일위대가 · 도면 · 면적산출 · 합계 이름이 안 잘리게 const GUTTER = Math.min( GUTTER_MAX, Math.max( @@ -126,7 +120,6 @@ export function renderSheet(state: RenderState): HTMLElement { st("Row_Unit_Price"), st("Row_Drawing"), st("Row_Area"), - st("Row_Material"), ...(doc.합계줄 ?? []).map((t) => t.이름), ].map((label) => Math.ceil(textWidth(label, font)) + 16), ), @@ -193,7 +186,6 @@ export function renderSheet(state: RenderState): HTMLElement { "s:price": "Row_Unit_Price", "s:drawing": "Row_Drawing", "s:area": "Row_Area", - "s:material": "Row_Material", } as const )[key]; const tr = el("tr", { className: "ui-sheet__meta", children: [gutter("th", st(label))] }); diff --git a/ui_template/sheet/ui_template_sheet_text.ts b/ui_template/sheet/ui_template_sheet_text.ts index 164722376..3ed27f142 100644 --- a/ui_template/sheet/ui_template_sheet_text.ts +++ b/ui_template/sheet/ui_template_sheet_text.ts @@ -13,7 +13,6 @@ const TEXT = { Row_Unit_Price: ["일위대가", "Unit price"], Row_Drawing: ["도면", "Drawing"], Row_Area: ["면적산출", "Area"], - Row_Material: ["재료산출", "Material"], Add_Suffix: [" 추가", " +"], New_Prefix: ["새 ", "New "], Drawing_None: ["없음", "None"], diff --git a/ui_template/sheet/ui_template_sheet_types.ts b/ui_template/sheet/ui_template_sheet_types.ts index 993133a52..ae5ca98b8 100644 --- a/ui_template/sheet/ui_template_sheet_types.ts +++ b/ui_template/sheet/ui_template_sheet_types.ts @@ -80,7 +80,7 @@ export interface SheetDoc { 보기?: SheetView; } -/** 고른 칸 — `row` = 줄 열쇠(`d:<줄id>` · `t:<합계id>` · `s:unit|price|drawing|area|material`) · +/** 고른 칸 — `row` = 줄 열쇠(`d:<줄id>` · `t:<합계id>` · `s:unit|price|drawing|area`) · * `col` = 열 차례(0부터 · 없으면 -1) · `colId` = 열 id(없으면 null). */ export interface SheetSelection { row: string; diff --git a/ui_template/ui_template_item_picker_data.ts b/ui_template/ui_template_item_picker_data.ts index b7224e981..598364652 100644 --- a/ui_template/ui_template_item_picker_data.ts +++ b/ui_template/ui_template_item_picker_data.ts @@ -15,7 +15,7 @@ export interface SummaryItem { } /** 항목 줄이 가리키는 키 칸 — 서버가 `names` 로 이름을 함께 주면 이름을 보임 */ -export type NameField = "일위대가" | "도번" | "면적번" | "재료번"; +export type NameField = "일위대가" | "도번" | "면적번"; /** 목록 한 줄(21-1) — 서버는 `?all=1` 을 `[{layer, owner, items}]` 로 주고, 줄마다 `layer` · `owner` 를 붙여 폄. * `열` 은 집계표 열 id(rv …) — 보이는 이름은 종류 · 분류를 이은 `itemName`. */ @@ -30,7 +30,6 @@ export interface PickerRow { 일위대가?: string; 도번?: string; 면적번?: string; - 재료번?: string; /** 폴더 이름 배열(깊이 자유 · 26-1) — 없으면 종류 · 분류1~3 중 있는 것으로 이음 */ 경로?: string[] | null; /** 위 키 칸의 이름(21-9 서버가 더함) — 없으면 키를 작은 글로 보임 */ @@ -167,7 +166,7 @@ const ancestorIds = (layer: PickerLayer, path: string[]): string[] => [ export const pathIds = (row: PickerRow, _labels?: TreeLabels): string[] => ancestorIds(layerOf(row), pathOf(row).slice(0, -1)); -export type FolderKind = "drawing" | "area" | "material" | "unitPrice"; +export type FolderKind = "drawing" | "area" | "unitPrice"; export interface FolderLine { kind: FolderKind; @@ -180,11 +179,10 @@ export interface FolderLine { const FOLDER_FIELDS: readonly [FolderKind, NameField][] = [ ["drawing", "도번"], ["area", "면적번"], - ["material", "재료번"], ["unitPrice", "일위대가"], ]; -/** 오른쪽 폴더 모양 — 네 줄 모두 서버의 키 칸 + `names` 의 이름 */ +/** 오른쪽 폴더 모양 — 세 줄 모두 서버의 키 칸 + `names` 의 이름 */ export function folderLines(row: PickerRow): FolderLine[] { return FOLDER_FIELDS.map(([kind, field]) => ({ kind, diff --git a/ui_template/ui_template_item_picker_detail.ts b/ui_template/ui_template_item_picker_detail.ts index f5b34eaa8..b98b60ce8 100644 --- a/ui_template/ui_template_item_picker_detail.ts +++ b/ui_template/ui_template_item_picker_detail.ts @@ -1,6 +1,6 @@ /* ============================================================================= * ui_template_item_picker_detail.ts - * 항목 고르기 모달 오른쪽 상세(계획서 25-2d · 26-3b) — **카드 넷**(도면 · 면적산출 · 재료산출 · 선택 입력). + * 항목 고르기 모달 오른쪽 상세(계획서 25-2d · 26-3b) — **카드 셋**(도면 · 면적산출 · 선택 입력). * 카드는 M02 와 같은 한 벌(`M02_MasterTemplete_ItemCards` · `_Inputs`)을 늦게 받아 씀 * (B05 · B06 번들에 CAD · 스프레드시트가 안 섞이게 `import()`). * @@ -89,7 +89,7 @@ const TEXT = { reapplyTip: "원본을 다시 가져옴 — 이 구조물 복사본에서 고친 것(도면 · 시트 · 선택 입력 자리)이 원본 것으로 덮임", reapplyAsk: - "원본을 다시 가져옴 — 이 구조물 복사본에서 고친 도면 · 면적산출 · 재료산출 · 선택 입력 자리가 원본 것으로 덮임(넣은 값은 같은 키면 남음). 재적용할까요?", + "원본을 다시 가져옴 — 이 구조물 복사본에서 고친 도면 · 면적산출 · 선택 입력 자리가 원본 것으로 덮임(넣은 값은 같은 키면 남음). 재적용할까요?", reapplied: "재적용했음 — 덮인 것: {value}", reappliedNone: "재적용했음 — 덮인 것 없음(원본과 같았음)", reapplyFailed: "재적용하지 못함 — {value}", @@ -114,7 +114,6 @@ const TEXT = { parts: { 도면: "도면", 면적: "면적산출", - 재료: "재료산출", 입력연결: "선택 입력 자리", } as Record, }; @@ -361,7 +360,7 @@ export function createItemDetail(opts: ItemDetailOptions): ItemDetail { await ensureCards(); if (mine !== seq || !cards || !inputs) return; const label = !info && canCopy ? TEXT.importEdit : null; - cards.relabel({ structure: label, area: label, material: label }); + cards.relabel({ structure: label, area: label }); inputs.setEditLabel(label ?? "선택 입력 수정"); cards.root.hidden = false; paintStatus(row, mine); diff --git a/ui_template/ui_template_item_picker_edit.ts b/ui_template/ui_template_item_picker_edit.ts index 9aa0b4b62..7c68863b7 100644 --- a/ui_template/ui_template_item_picker_edit.ts +++ b/ui_template/ui_template_item_picker_edit.ts @@ -1,7 +1,7 @@ /* ============================================================================= * ui_template_item_picker_edit.ts * 항목 고르기 모달의 **편집 화면**(계획서 25-2d) — 카드 [수정] 이 모달 안에서 이 화면으로 바뀜. - * 도면 = M02 CAD 부품 · 면적산출 · 재료산출 = M02 스프레드시트 · 선택 입력 = 값 받기 카드. + * 도면 = M02 CAD 부품 · 면적산출 = M02 스프레드시트 · 선택 입력 = 값 받기 카드. * 고침은 **프로젝트 복사본**에만 씀(`ProjectItems.write`) — 마스터 · 회사 · 개인 원본은 안 바뀜. * [뒤로] = 선택지(나무 + 카드)로 · 저장 안 한 고침이 있으면 버릴지 물음. * 편집 부품은 이 화면을 열 때만 `import()`(B05 · B06 번들에 안 섞임). @@ -40,7 +40,6 @@ const TEXT = { "면적산출 알약 없음 — 선택 입력에서 자리를 면적산출로 이으면 여기 뜸(마스터 · 회사 · 개인 층에서)", drawing: "도면", area: "면적산출", - material: "재료산출", inputs: "선택 입력", suffix: "{value} 수정", }; @@ -61,7 +60,6 @@ export function createEditView(req: EditRequest, onBack: (saved: boolean) => voi const word = { structure: TEXT.drawing, area: TEXT.area, - material: TEXT.material, inputs: TEXT.inputs, }[kind]; const back = createButton({ label: TEXT.back, variant: "ghost", size: "sm" }); @@ -96,28 +94,21 @@ export function createEditView(req: EditRequest, onBack: (saved: boolean) => voi body.replaceChildren(el("p", { className: "ui-item-picker__hint", text })); }; - /** 복사본의 항목 연결 — 자리가 면적 · 재료인 입력이 그 시트 알약(25-1b · 키 모양은 `pillsOf`) */ + /** 복사본의 항목 연결 — 자리가 면적인 입력이 그 시트 알약(25-1b · 키 모양은 `pillsOf`) */ const linkOf = async (): Promise => (await items.read(info.폴더, "입력연결")) as InputLinks | null; - async function sheetLinks(kindOf: "area" | "material"): Promise { - const [list, link, { searchMaterials }] = await Promise.all([ - loadInputs(row), - linkOf(), - import("../M01_MasterData/M01_MasterData_Api_Fetch"), - ]); - const 항목 = pillsOf(list, link, kindOf === "area" ? "면적" : "재료"); - if (kindOf === "area") { - if (!항목.length) note.textContent = TEXT.noAreaPill; - return { 항목, 모드: "입력" }; - } - return { 항목, 모드: "자재", 자재찾기: searchMaterials }; + async function sheetLinks(): Promise { + const [list, link] = await Promise.all([loadInputs(row), linkOf()]); + const 항목 = pillsOf(list, link, "면적"); + if (!항목.length) note.textContent = TEXT.noAreaPill; + return { 항목, 모드: "입력" }; } - async function mountSheet(kindOf: "area" | "material"): Promise { + async function mountSheet(): Promise { const [{ createSpreadsheet }, links] = await Promise.all([ import("../A00_Common/spreadsheet/spreadsheet"), - sheetLinks(kindOf), + sheetLinks(), ]); const host = el("div", { className: "ui-item-picker__edit-sheet" }); body.replaceChildren(host); @@ -168,7 +159,7 @@ export function createEditView(req: EditRequest, onBack: (saved: boolean) => voi } if (kind === "structure") await mountDrawing(); else if (kind === "inputs") await mountInputs(); - else await mountSheet(kind); + else await mountSheet(); } catch (error) { fail(fill(TEXT.loadFailed, why(error))); } diff --git a/ui_template/ui_template_item_picker_project.ts b/ui_template/ui_template_item_picker_project.ts index 37a4186b1..6651261cc 100644 --- a/ui_template/ui_template_item_picker_project.ts +++ b/ui_template/ui_template_item_picker_project.ts @@ -21,13 +21,12 @@ import { import { layerOf, ownerOf, type PickerRow } from "./ui_template_item_picker_data"; /** 복사본 폴더 안 부분 — 25-1b 약속 그대로 */ -export type ProjectPart = "도면" | "면적" | "재료" | "입력연결" | "입력값" | "고른자재"; +export type ProjectPart = "도면" | "면적" | "입력연결" | "입력값" | "고른자재"; /** 원본 종류 → 복사본 부분 */ -export const PART_OF: Record<"structure" | "area" | "material", ProjectPart> = { +export const PART_OF: Record<"structure" | "area", ProjectPart> = { structure: "도면", area: "면적", - material: "재료", }; export interface ProjectItemInfo { diff --git a/ui_template/ui_template_locale_m2.ts b/ui_template/ui_template_locale_m2.ts index 37b450087..1fd8dce53 100644 --- a/ui_template/ui_template_locale_m2.ts +++ b/ui_template/ui_template_locale_m2.ts @@ -13,7 +13,6 @@ export const ui_locales_m2 = { M02_KindDrawing: ["도면 양식", "Drawing templates"], M02_KindStructure: ["구조물 도면", "Structure drawings"], M02_KindArea: ["면적산출", "Area takeoff"], - M02_KindMaterial: ["재료산출", "Material takeoff"], M02_NoTemplates: ["양식 없음", "None"], M02_New: ["새로", "New"], M02_Copy: ["본떠 만들기", "Copy as new"], @@ -87,7 +86,6 @@ export const ui_locales_m2 = { M02_PickerLoadFailed: ["항목을 못 읽음 — {value}", "Failed to load items — {value}"], M02_PickerDrawing: ["도면", "Drawing"], M02_PickerArea: ["면적산출", "Area takeoff"], - M02_PickerMaterial: ["재료산출", "Material takeoff"], M02_PickerUnitPrice: ["일위대가", "Unit price"], M02_PickerUnit: ["단위", "Unit"], M02_PickerNone: ["없음", "None"],