Files
Aislo/A00_Common/formula_doc_copy.ts
eomsangdonandClaude Opus 5.5 9c803024e0 feat(807-1): 엑셀 불러오기 = 수식 문서 · 단가산출 · 일위대가 하나씩 개인 층으로 · 원문 패널 기초 찾기 + 「어디서 왔나」 카드
- 임도품셈 판 대응표 CSV 새 자리(STmate/old/30_원자료) — 불러오기 시험 26 실패 → 0
- 가져온 식 SK = 수식 문서(멈춘 wip/802-4b-1 M02_Doc_Import 를 메인 판에 다시 얹음 · 빈 산근 = 엑셀 0 원 줄) —
  줄 쌓기 「수식」 탭 길 열림 · 셈 = 단가기준(엑셀 값 기억 | 연결 값) · 화면 캐시 기록 · 수식 길 저장 `단가기준`
- 1,021 블록 전수(실무 여덟 + 표본 밖 셋) 엑셀 검산 · 엑셀 전후 · 우리 전후 모두 같음 · 셈됨 줄지 않음
- 옮기기 도구 「가져온식」 상태 걷음(가져온 줄 문서로 대조)
- [내 일위대가로] = 호표 하나 + 하위 → 개인 층 SC · 부르는 단산 = 가져온 식 SK(같은 내용 = 있는 것 · 못 이은 줄 = 고정)
- [내 단가산출로] = 단산 블록 판 → 개인 층 가져온 식 SK(개인 층이면 [가져온 식으로 열기] 숨김)
- 원문 패널(M01 로직 · M02 단가산출) 맨 위 「단가산출 기초 찾기」 · 가져온 식 SK 첫 화면 = 「어디서 왔나」 카드
  (프로그램 · 파일 · 블록 · 행 · 가져온 때 · 표지 · 엑셀 글의 품셈 절 글만)
- 수식 탭 [엑셀 단가 그대로 | 우리 단가로](단가산출 화면 · 저장 뒤 페이지 문서 단가기준 맞춤)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 17:11:09 +09:00

93 lines
3.9 KiB
TypeScript

/* =============================================================================
* 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<string, number | null> | 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<string, string>): Promise<boolean> {
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"));
}