feat(M02): 50-3③ 서고 새로 · 복사를 서버 키 길로 — POST /layers/{층}/library/{종류} · 참조 줄 보존

- 새로 = 서버가 그 층 다음 SP · SC 키 · 복사 = 본뜬 원본 → 고른 이름으로 고쳐 씀
- 일위대가 문서 줄의 참조 {층, 주인, 키} 를 화면 캐시에서 안 버림 · 로직 입력 받기에서 참조 줄 건너뜀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
2026-10-05 18:45:01 +09:00
co-authored by Claude Opus 5.5
parent b1a5f38a16
commit 027ad3da98
4 changed files with 45 additions and 29 deletions
@@ -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<Kind, "promote" | "precombo">;
/** 종류 · 키 머리 · 화면 이름(= 문서 `종류` 글) */
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<string, unknown>): Promise<void> => {
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<void> => {
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<string, unknown> = { ...(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);
});
};