Files
Aislo/M02_MasterTemplete/logic/M02_Sangeun_Undo.ts
T
eomsangdonandClaude Opus 5.5 fd9cce0b12 feat(778-4): 율 기준 줄 목록 열쇠@비목 — 그 줄 그 비목 몫(절 노무비 · 기계 노무 포함)
- 밑 공용 함수 rate_base ↔ rateBase · 검사 · 짝잃음 · B09 rows:n@labor(파이썬 · TS) · 굳힘 · 화면 열쇠 옮김이 비목 꼬리 그대로
- 여덟 파일 율 줄 152 중 기준 찾은 147 재현(인력 82 · 재료 35 · 절 소계 16 · 노무비 11 · 장비 2 · 자재+율 1)
- 시험 test_778_4_rate_line 29 · 전후 대조 148/148 같음

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

99 lines
4.3 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete/logic/M02_Sangeun_Undo.ts
* 단가산출(SK) 되돌리기 기억(PLAN 744-1 서버 · 748-0 화면) — 더한 기초 · 더한 항목 빼기 → 문서 `뺀더한줄`
* `[{차례, 줄}]`(되살림 = 그 차례에 다시 끼움) · 더한 항목 수량 → `처음수량`(넣을 때 값) 되돌림.
* 더한줄 지움 · 끼움 때 뒤 더한줄 열쇠 `add:#j[/…]`(덮개 · 고름 · 자재표시 · 율 줄 기준 목록 778-4)를
* 한 칸씩 당김 · 밂(지운 줄을 가리킨 기준 열쇠는 버림 — 빈 목록 = 서버 짝잃음 알림).
* 금액은 늘 서버가 셈 — 여기는 문서 칸 옮김만 · 화면 · fetch · sessionStorage 없음(`CLAUDE.md` 4장).
* ========================================================================== */
import type { SkAdded, SkDoc } from "./M02_Sangeun_Cover_Model";
/* ── 순수 셈 ── */
const KEYED = ["덮개", "고름", "자재표시"] as const;
const KEY_RE = /^add:#(\d+)(.*)$/;
/** 문서 열쇠 맵의 `add:#j…` 를 옮김 — `drop` = 지운 j(그 열쇠는 버림) · 뒤 j 는 한 칸 당김 / `push` = 끼운 j 부터 한 칸 밂 */
function moveKeys(doc: SkDoc, mode: { drop: number } | { push: number }): void {
for (const name of KEYED) {
const map = doc[name] as Record<string, unknown> | undefined;
if (!map) continue;
const out: Record<string, unknown> = {};
for (const [key, value] of Object.entries(map)) {
const hit = KEY_RE.exec(key);
if (!hit) {
out[key] = value;
continue;
}
const j = Number(hit[1]);
if ("drop" in mode) {
if (j === mode.drop) continue;
out[j > mode.drop ? `add:#${j - 1}${hit[2]}` : key] = value;
} else out[j >= mode.push ? `add:#${j + 1}${hit[2]}` : key] = value;
}
(doc as Record<string, unknown>)[name] = out;
}
for (const one of doc.더한줄 ?? []) {
const rate = (one.고정 as { 비율?: { 기준?: unknown } } | undefined)?.비율;
if (!rate || !Array.isArray(rate.기준)) continue;
rate.기준 = (rate.기준 as string[]).flatMap((key) => {
const hit = KEY_RE.exec(key);
if (!hit) return [key];
const j = Number(hit[1]);
if ("drop" in mode)
return j === mode.drop ? [] : [j > mode.drop ? `add:#${j - 1}${hit[2]}` : key];
return [j >= mode.push ? `add:#${j + 1}${hit[2]}` : key];
});
}
}
/** 더한줄 k 를 빼고 기억에 둠 — 이름은 되살림 띠 글(서버가 저장 때 다시 적음) */
export function forgetAdd(doc: SkDoc, k: number, name: string): void {
const one = doc.더한줄?.[k];
if (!one) return;
doc.뺀더한줄 = [
...(doc.뺀더한줄 ?? []),
{ 차례: k, 줄: JSON.parse(JSON.stringify({ ...one, ...(name ? { 이름: name } : {}) })) },
];
doc.더한줄 = (doc.더한줄 ?? []).filter((_, i) => i !== k);
moveKeys(doc, { drop: k });
}
/** 기억 n 번째를 그 차례에 다시 끼움(차례가 줄 수보다 크면 맨 끝) */
export function reviveAdd(doc: SkDoc, n: number): void {
const gone = doc.뺀더한줄?.[n];
if (!gone) return;
const list = [...(doc.더한줄 ?? [])];
const at = Math.min(Math.max(gone.차례, 0), list.length);
list.splice(at, 0, gone.줄);
doc.더한줄 = list;
moveKeys(doc, { push: at });
const rest = (doc.뺀더한줄 ?? []).filter((_, i) => i !== n);
if (rest.length) doc.뺀더한줄 = rest;
else delete doc.뺀더한줄;
}
/** 되살림 띠 글 — 줄 이름 · 없으면 로직 · 요소 · 참조 키 */
export function goneName(one: SkAdded): string {
const ref = (one.참조 as { 키?: string } | undefined)?.키;
return String(one.이름 || one.로직 || one.요소 || ref || "");
}
/** 더한줄 수량 고침 — 처음 값(옛 문서엔 없음)을 먼저 적어 둠 */
export function setQty(one: SkAdded, qty: number): void {
one.처음수량 ??= one.수량;
one.수량 = qty;
}
/** 더한줄 수량 처음 값으로 — 처음 값 모르면 그대로 */
export function resetQty(one: SkAdded): void {
if (one.처음수량 != null) one.수량 = one.처음수량;
}
/** 넣을 때 수량 적어 둠 */
export const withFirst = <T extends { 수량: number }>(line: T): T & { 처음수량: number } => ({
...line,
처음수량: line.수량,
});
/* ── 순수 셈 끝 ── */