/* ============================================================================= * formula_doc_copy.ts * 수식 문서 사본 몫(PLAN 802-4b-2 · 요구 655-2 · 656 · 659-3) — M02 단가산출(SK) 수식 탭 머리 * 「M01 새 판 있음」 배지(값 줄 · 부름 문서가 지금 M01 과 다름) · [단가 갱신](자원 단가만 · 식 그대로) · * [재생성](지금 M01 문서로 다시 찍음 · 버리는 것 알림) — 누를 때만(자동 갱신 없음) * 흐름 = 미리 셈(`{길}/preview` {할일}) → 확인 창(계 전 → 후 · 바뀐 자원 · 버리는 것) → 저장(`{길}/save` {할일, 판}) * 층 쓰기 권한 없으면 「읽기만」 배지 * ========================================================================== */ import { createBadge } from "@ui/ui_template_badge"; import { createButton } from "@ui/ui_template_button"; import { eqText } from "@ui/ui_template_equation"; import { showConfirmDialog } from "@ui/ui_template_elements"; import type { Basis } from "../common_util/common_util_formula_doc_import"; import { ft } from "./formula_doc_text"; export type CopyJob = "단가갱신" | "재생성"; /** 사본 표지(서버 `M02_Doc_Copy.state` + 시점 · 따로) */ export interface CopyMark { M01새판?: boolean; 지운키?: string[]; 단가새값?: boolean; 문서새판?: string[]; 따로?: boolean; /** 가져온 식(엑셀 불러오기 · 807-1) 단가기준 — 엑셀 = 엑셀 값 기억 · 우리 = 연결 값 */ 단가기준?: Basis; } export interface CopyOptions { /** SK 키 · 꼬리(`""` · `/preview` · `/table?key=…`) → 길(`/m02/layers/{층}/sangeun/{키}/doc…` · 주인 물음) */ route: (key: string, path: string) => string; /** 저장 뒤 그 SK 새 판(페이지 [저장] 409 막음) */ onSaved?: (version: string) => void; } type Money = Record | null; export interface JobReply { 전?: Money; 후?: Money; 바뀐자원?: unknown[]; 버림?: { 문서고침?: boolean; 끊음?: string[] }; } /** 「M01 새 판 있음」 — 값 줄 · 단가 · 부름 문서 어느 하나라도 지금 M01 과 다름 */ export function isStale(m: CopyMark | undefined): boolean { return !!m && (!!m.M01새판 || !!m.단가새값 || !!m.지운키?.length || !!m.문서새판?.length); } export function copyBadges(m: CopyMark | undefined, edit: boolean): HTMLElement[] { const out: HTMLElement[] = []; if (isStale(m)) { const b = createBadge(ft("New_M01"), "danger"); b.dataset.copyStale = ""; out.push(b); } if (!edit) out.push(createBadge(ft("Read_Only"), "neutral")); return out; } export function copyTools(edit: boolean, run: (job: CopyJob) => void): HTMLElement[] { if (!edit) return []; return (["단가갱신", "재생성"] as const).map((job) => { const b = createButton({ label: job === "단가갱신" ? ft("Price") : ft("Regen"), variant: "ghost", size: 28, onClick: () => run(job), }); b.dataset.copyJob = job; return b; }); } const sum = (m: Money | undefined): string => typeof m?.계 === "number" ? `${eqText(m.계)} 원` : "—"; /** 할일 알림 — 계 전 → 후 · 바뀐 자원 수 · 재생성이 버리는 것(이름) → 확인이면 true */ export function askJob(job: CopyJob, got: JobReply, names: Map): Promise { const bits = [ job === "단가갱신" ? ft("Price_Ask") : ft("Regen_Ask"), `${ft("Before_After")} ${sum(got.전)} → ${sum(got.후)}`, ]; if (job === "단가갱신") bits.push(`${ft("Changed")} ${got.바뀐자원?.length ?? 0}`); const lost = [ ...(got.버림?.문서고침 ? [ft("Regen_Edit")] : []), ...(got.버림?.끊음 ?? []).map((k) => `${ft("Cut")} ${names.get(k) || k}`), ]; if (job === "재생성" && lost.length) bits.push(`${ft("Regen_Lost")} — ${lost.join(" · ")}`); return showConfirmDialog(bits.join(" · "), job === "단가갱신" ? ft("Price") : ft("Regen")); }