/* ============================================================================= * formula_doc_edit.ts * 수식 문서 고침(PLAN 802-3 · 802-4b-2) — 식 줄 · 금액 줄 · 값 식 숫자 고침을 문서에 얹음(화면 · fetch 없음) * 수식 화면(`formula_doc_screen.ts`)과 끊은 단가산출 블록(`formula_doc_sub.ts`)이 같은 고침을 씀 * 수식 영역 한 줄 글(803-2a) = `editRow`(식 줄 · 금액 줄 「노무비 형틀목공 = w × Q」) · 빈 줄 걷기 `sweep` * ========================================================================== */ import { goodId, splitId, textOf, tidy, varId, type DocVar, type FormulaDoc, type MoneyLine, } from "../common_util/common_util_formula_doc"; import { swapLit, tryParse } from "./formula_doc_render"; /** 머리 = 식 — 첫 「=」(비교 ==, <=, >=, != 빼고) */ export function splitLine(text: string): [string | null, string] { const m = /^([^=<>!]*?)\s*(?!=])=(?!=)\s*([\s\S]*)$/.exec(text); if (!m) return [null, text.trim()]; return [m[1].trim(), m[2].trim()]; } export const tidyText = (t: string): string => (t.trim() ? tidy(t.trim()) : ""); /** 기호 정의 — 없으면 「식」 갈래로 새로 */ export function ensureVar(d: FormulaDoc, id: string): DocVar { const have = (d.변수 ??= []).find((v) => varId(v) === id); if (have) return have; const [기호, 아래, 위] = splitId(id); const v: DocVar = { 열쇠: id, 기호, 아래, 위, 이름: "", 단위: "", 연결: { 갈래: "식" } }; d.변수.push(v); return v; } /** 식 줄 i 를 글로 — 빈 글 = 줄 지움 · 머리 바뀜 = 식 갈래 정의도 같이 */ export function editLine(d: FormulaDoc, i: number, text: string): void { const lines = (d.식 ??= []); const line = lines[i]; if (!line) return; if (!text.trim()) { lines.splice(i, 1); return; } const [h, body] = splitLine(text); const old = String(line.기호 ?? ""); if (h !== null && h !== old) { line.기호 = h; const was = (d.변수 ?? []).find((v) => varId(v) === old && (v.연결?.갈래 ?? "식") === "식"); if (was && goodId(h) && !(d.변수 ?? []).some((v) => varId(v) === h)) { const [기호, 아래, 위] = splitId(h); Object.assign(was, { 기호, 아래, 위 }); } } line.식 = tidyText(body); } /** 금액 줄 i 고침(null = 지움) — 단가 · 수량 글은 다듬음 · 빈 수량 = 칸 뺌 */ export function editMoney(d: FormulaDoc, i: number, patch: Partial | null): void { const lines = (d.금액 ??= []); if (!patch) { lines.splice(i, 1); return; } const line = lines[i]; if (!line) return; for (const [k, v] of Object.entries(patch)) { const text = typeof v === "string" && (k === "단가" || k === "수량") ? tidyText(v) : v; if (k === "수량" && text === "") delete line.수량; else (line as Record)[k] = text; } } /** 값 식 숫자 고침 — 식 줄 · 금액 줄 단가 · 수량 글의 at 번째 수 */ export function editLit( d: FormulaDoc, target: "line" | "price" | "qty", i: number, at: number, value: number, ): void { if (target === "line") { const line = d.식?.[i]; if (line) line.식 = swapLit(String(line.식 ?? ""), at, value); return; } const line = d.금액?.[i]; if (!line) return; const slot = target === "price" ? "단가" : "수량"; line[slot] = swapLit(String(line[slot] ?? ""), at, value); } const KINDS = ["노무비", "재료비", "경비", "묶음"]; /** 금액 줄 몸 → 단가 · 수량(맨 위 × 의 오른쪽 = 수량 · 없으면 단가만) */ function splitMoney(body: string): { 단가: string; 수량: string } { const n = tryParse(body); if (n && n[0] === "bin" && n[1] === "*") return { 단가: textOf(n[2]), 수량: textOf(n[3]) }; return { 단가: body, 수량: "" }; } /** 수식 영역 한 줄 글 → 문서(803-2a) — 머리 첫 말이 비목이면 금액 줄 · 아니면 식 줄(갈래가 바뀌면 옮김) * 빈 글 = 줄 지움 · 돌려줌 = 고친 뒤 그 줄 객체(지웠으면 null) */ export function editRow( d: FormulaDoc, kind: "line" | "money", i: number, text: string, ): object | null { const lines = (d.식 ??= []); const money = (d.금액 ??= []); const was = kind === "line" ? lines[i] : money[i]; if (!was) return null; if (!text.trim()) { (kind === "line" ? lines : money).splice(i, 1); return null; } const [h, body] = splitLine(text); const words = (h ?? text).trim().split(/\s+/); const cost = KINDS.includes(words[0]) ? words[0] : null; if (!cost) { if (kind === "line") { editLine(d, i, text); return lines[i]; } money.splice(i, 1); lines.push({ 기호: "", 식: "" }); editLine(d, lines.length - 1, text); return lines[lines.length - 1]; } const patch = { 비목: cost, 이름: words.slice(1).join(" "), ...splitMoney(h === null ? "" : body), }; if (kind === "money") { editMoney(d, i, patch); return money[i]; } lines.splice(i, 1); const group = money.length ? money[money.length - 1].묶음 : undefined; money.push({ 이름: "", 비목: cost, 단가: "", ...(group ? { 묶음: group } : {}) }); editMoney(d, money.length - 1, patch); return money[money.length - 1]; } /** 빈 줄 걷기(`keep` 빼고) */ export function sweep(d: FormulaDoc, keep?: object | null): void { d.식 = (d.식 ?? []).filter((l) => l === keep || String(l.기호 ?? "") || String(l.식 ?? "")); d.금액 = (d.금액 ?? []).filter((l) => l === keep || l.이름 || l.단가 || l.수량); }