From 027ad3da9833ea3d6922db5c918dbdd106b7dd1e Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 5 Oct 2026 18:45:01 +0900 Subject: [PATCH] =?UTF-8?q?feat(M02):=2050-3=E2=91=A2=20=EC=84=9C=EA=B3=A0?= =?UTF-8?q?=20=EC=83=88=EB=A1=9C=20=C2=B7=20=EB=B3=B5=EC=82=AC=EB=A5=BC=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=ED=82=A4=20=EA=B8=B8=EB=A1=9C=20=E2=80=94?= =?UTF-8?q?=20POST=20/layers/{=EC=B8=B5}/library/{=EC=A2=85=EB=A5=98}=20?= =?UTF-8?q?=C2=B7=20=EC=B0=B8=EC=A1=B0=20=EC=A4=84=20=EB=B3=B4=EC=A1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새로 = 서버가 그 층 다음 SP · SC 키 · 복사 = 본뜬 원본 → 고른 이름으로 고쳐 씀 - 일위대가 문서 줄의 참조 {층, 주인, 키} 를 화면 캐시에서 안 버림 · 로직 입력 받기에서 참조 줄 건너뜀 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt --- .../M02_MasterTemplete_Api_Fetch.ts | 13 +++++ .../M02_MasterTemplete_Labor_Api.ts | 3 + .../M02_MasterTemplete_Labor_Detail.ts | 2 +- .../M02_MasterTemplete_UI_Side_Precombo.ts | 56 +++++++++---------- 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts b/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts index a56806e1f..a76619d33 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts @@ -126,6 +126,19 @@ export const deleteLayerTemplate = ( method: "DELETE", }); +/** 서고 새로 · 복사(50-3) — 새로 = `문서` · 복사 = `본뜬` {층, 주인, 키} · 키는 서버가 줌(답 `이름`) */ +export type LibraryBody = + { 문서: Record } | { 본뜬: { 층: Layer; 주인: string | null; 키: string } }; +export const newLibrary = ( + layer: Layer, + kind: "promote" | "precombo", + body: LibraryBody, +): Promise => + call(owned(layer, `/layers/${layer}/library/${kind}`), { + method: "POST", + body: JSON.stringify(body), + }); + /* --- 구조물 도면(종류 structure · 이름 = 구조물집계표 열 id) --- * 읽기 · 저장 · 지우기는 위 readTemplate · saveTemplate · deleteTemplate 에 "structure" */ diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Api.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Api.ts index 5e7b50172..21cfd6835 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Api.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Api.ts @@ -126,6 +126,8 @@ export interface LaborRow { 메모: string | null; 입력값: Record; 수량: LaborQty; + /** 서고 참조 줄(50-3 · 사전조합 · 로직 없음) — 단가승격 · 사전조합 {층, 주인, 키} */ + 참조?: { 층: string; 주인: string | null; 키: string }; } export interface LaborMat { 코드: string; @@ -159,6 +161,7 @@ export function normalizeLabor(doc: unknown, column: string): LaborDoc { 메모: r.메모 ?? null, 입력값: r.입력값 ?? {}, 수량: r.수량 ?? null, + ...(r.참조 ? { 참조: r.참조 } : {}), })), 자재: (d.자재 ?? []).map((m) => ({ 코드: m.코드, diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts index c3069f8b5..8176a4bed 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts @@ -283,7 +283,7 @@ export function mountLaborDetail( /** 아직 안 받은 로직 입력 목록만 받음 */ const loadLogics = async (): Promise => { - const want = cur.담은로직.map((r) => r.로직).filter((k) => !logics.has(k)); + const want = cur.담은로직.map((r) => r.로직).filter((k) => k && !logics.has(k)); // 참조 줄 = 로직 없음 if (!want.length) return; try { const got = await fetchLogicInputs(want); diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts index 67c0f220f..8773635dd 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts @@ -20,10 +20,12 @@ import { deleteLayerTemplate, deleteTemplate, getActiveOwner, + newLibrary, readTemplate, saveTemplate, type Kind, type Layer, + type LibraryBody, type TemplateInfo, } from "./M02_MasterTemplete_Api_Fetch"; import { openLogicPicker } from "./M02_MasterTemplete_Labor_Pick"; @@ -31,10 +33,10 @@ import type { Selection } from "./M02_MasterTemplete_UI_Main"; export type PrecomboKind = Extract; -/** 종류 · 키 머리 · 화면 이름(= 문서 `종류` 글) */ -const PARTS: { kind: PrecomboKind; prefix: string; label: string }[] = [ - { kind: "promote", prefix: "SP", label: "단가승격" }, - { kind: "precombo", prefix: "SC", label: "사전조합" }, +/** 종류 · 화면 이름(= 문서 `종류` 글 · 키 SP- · SC- 는 서버가 줌) */ +const PARTS: { kind: PrecomboKind; label: string }[] = [ + { kind: "promote", label: "단가승격" }, + { kind: "precombo", label: "사전조합" }, ]; export const PRECOMBO_TITLE = "일위대가 조합"; export const isPrecombo = (kind: Kind): kind is PrecomboKind => @@ -45,15 +47,6 @@ type Info = TemplateInfo & { 제목?: string }; export const precomboLabel = (info: Info): string => info.제목 ? `${info.이름} ${info.제목}` : info.이름; -/** 그 층 다음 키 — 지금 목록의 가장 큰 수 + 1(서버가 같은 키면 409 로 막음) */ -const nextKey = (prefix: string, items: Info[]): string => { - const top = items.reduce((max, i) => { - const m = new RegExp(`^${prefix}-(\\d+)$`).exec(i.이름); - return m ? Math.max(max, Number(m[1])) : max; - }, 0); - return `${prefix}-${String(top + 1).padStart(6, "0")}`; -}; - export interface PrecomboOptions { layer: Layer; canEdit: boolean; @@ -95,10 +88,9 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle { }); }; - /** 새로 · 복사 — 다음 키로 저장 후 엶 */ - const create = async (kind: PrecomboKind, doc: Record): Promise => { - const key = nextKey(PARTS.find((p) => p.kind === kind)!.prefix, mine(kind)); - await saveTemplate(opt.layer, kind, key, null, "", doc); + /** 새로 — 서버가 그 층 다음 키로 씀 → 엶 */ + const create = async (kind: PrecomboKind, body: LibraryBody): Promise => { + const key = (await newLibrary(opt.layer, kind, body)).이름; showToast(L("M02_Created"), "success"); await opt.refresh(); const info = mine(kind).find((i) => i.이름 === key); @@ -142,10 +134,11 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle { !go ? undefined : create("promote", { - 종류: "단가승격", - 이름: x.이름, - 단위: x.결과단위 || null, - 담은로직: [{ 로직: x.키, 입력값: {} }], + 문서: { + 이름: x.이름, + 단위: x.결과단위 || null, + 담은로직: [{ 로직: x.키, 입력값: {} }], + }, }).catch(failed), ); }); @@ -153,20 +146,27 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle { const newPrecombo = (): void => nameDialog(L("M02_NewTitle"), "", async (name) => { if (!(await opt.confirmLeave())) return; - await create("precombo", { 종류: "사전조합", 이름: name, 단위: null, 담은로직: [] }); + await create("precombo", { 문서: { 이름: name, 단위: null, 담은로직: [] } }); }); - /** 복사 = 같은 층 새 키 · 이름 「(2)」 · 본뜬 = 원래 키 */ + /** 복사 = 같은 층 새 키(서버 · 본뜬 = 원래 키) → 고른 이름으로 고쳐 씀 */ const copy = (kind: PrecomboKind): void => { const from = current(kind); if (!from) return; nameDialog(L("M02_CopyTitle"), `${from.제목 ?? from.이름} (2)`, async (name) => { if (!(await opt.confirmLeave())) return; - const got = await readTemplate(opt.layer, kind, from.이름, null); - const doc: Record = { ...(got.문서 as object), 이름: name }; - doc.본뜬 = { 층: opt.layer, 주인: getActiveOwner(), 키: from.이름 }; - delete doc.판; - await create(kind, doc); + const made = await newLibrary(opt.layer, kind, { + 본뜬: { 층: opt.layer, 주인: getActiveOwner(), 키: from.이름 }, + }); + const got = await readTemplate(opt.layer, kind, made.이름, null); + await saveTemplate(opt.layer, kind, made.이름, null, made.판, { + ...(got.문서 as object), + 이름: name, + }); + showToast(L("M02_Created"), "success"); + await opt.refresh(); + const info = mine(kind).find((i) => i.이름 === made.이름); + if (info) await select(info); }); };