- 인력 · 기계 · 재료 · 단가산출 줄 자원 이름 = 단추(이름 · 규격) → 찾아 넣기 창 → 바꿈 · [찾아 바꾸기] 걷음 - 항목 머리 = 띠(바탕 · 왼쪽 굵은 띠 · 번호) · 줄 = 들여쓰기 + 왼쪽 세로 선 · 가나다 한 단 더 · 접기 - 순서 = 손잡이 끌기(줄 = 항목 안 · 사이 · 항목 끌기 · 자리 선 표시) · [↑][↓] 걷음 - 지움 = X 아이콘 · 확인 없이 지우고 알림 눌러 되돌리기(그 뒤 고침 있으면 못 함) - 산출근거 율 줄 「번지.복사N.노무비」 → 그 조각 이름 · 값 식 = 번지 값 · 합계 덧셈 항 넷 넘으면 항목 소계끼리 - 알림 부품: 누름 동작 알림은 같은 문구 버림 안 함(두 번째 지움 되돌리기 안 뜨던 흠) - 글 · 근거 카드 높이 늘던 흠 고침 · 시험 test_804_4 끌어옮김 · 번지 이름 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
443 lines
18 KiB
TypeScript
443 lines
18 KiB
TypeScript
/* =============================================================================
|
|
* common_util_line_stack.ts
|
|
* 줄 쌓기 모델(PLAN 804-2 · 요구 670~677) — 수식 문서 ↔ 줄 쌓기 한 벌 왕복 · 화면 · DOM · fetch 없음(시험이 Node 로 돌림)
|
|
* 정본 = 수식 문서 그대로(변수 · 식 · 금액 · 끝수 …) · 줄 쌓기는 그 위의 차림 — 항목(제목 · 가나다) 안 줄:
|
|
* 금액 줄(문서 `금액` 차례) · 공식 묶음(기호 목록 · 틀) · 글 줄
|
|
* 차림 저장 = 문서 칸 `쌓기`(기본 차림과 다를 때만 · 계산기는 안 봄) — 문서 → 줄 → 문서 = 같은 문서(1,518 전수 시험)
|
|
* 기본 차림 = 금액 줄을 `묶음` 이 같은 이어진 줄끼리 항목으로 · 기호는 처음 쓰는 항목의 공식 묶음(쓰는 차례 = 입력 먼저)
|
|
* 옵션(선택 · 입력 열쇠) · 금액 줄 단가 하나만 쓰는 연결 기호(요소 · 단가산출)는 공식 묶음에 안 넣음(옵션 가로 · 그 줄이 보임)
|
|
* 금액 차례 = 줄 쌓기 차례(율 줄 「노무비」 = 앞 줄 누계라 차례가 셈을 바꿈)
|
|
* ========================================================================== */
|
|
|
|
import {
|
|
DocError,
|
|
names,
|
|
parse,
|
|
rename,
|
|
splitId,
|
|
symId,
|
|
textOf,
|
|
linkNames,
|
|
varId,
|
|
type DocVar,
|
|
type FormulaDoc,
|
|
type Link,
|
|
type MoneyLine,
|
|
type Node,
|
|
} from "./common_util_formula_doc";
|
|
import { evalNode } from "./common_util_formula_doc_view";
|
|
|
|
export type MoneyKind = "인력" | "재료" | "기계" | "단가산출" | "율";
|
|
export type StackLine =
|
|
| { 꼴: "금액"; i: number }
|
|
| { 꼴: "공식"; 기호: string[]; 틀?: string; 틀꼴?: string }
|
|
| { 꼴: "글"; 글: string };
|
|
export interface StackItem {
|
|
제목: string;
|
|
가나다?: boolean;
|
|
줄: StackLine[];
|
|
}
|
|
type SaveLine =
|
|
{ 금액: number; 이름: string } | { 공식: string[]; 틀?: string; 틀꼴?: string } | { 글: string };
|
|
export interface SaveItem {
|
|
제목?: string;
|
|
가나다?: boolean;
|
|
줄: SaveLine[];
|
|
}
|
|
export type StackDoc = FormulaDoc & { 쌓기?: SaveItem[] };
|
|
|
|
const clone = <T>(v: T): T => JSON.parse(JSON.stringify(v)) as T;
|
|
|
|
/** 식 글 → 나무(못 풀면 null) */
|
|
export function nodeOf(text: unknown): Node | null {
|
|
if (text === null || text === undefined || String(text).trim() === "") return null;
|
|
try {
|
|
return parse(text);
|
|
} catch (e) {
|
|
if (e instanceof DocError) return null;
|
|
throw e;
|
|
}
|
|
}
|
|
const namesIn = (text: unknown): string[] => {
|
|
const n = nodeOf(text);
|
|
return n ? names(n) : [];
|
|
};
|
|
|
|
/** 문서 기호 전부 — 변수 차례 · 변수 없는 식 줄 기호 */
|
|
export function symbols(doc: FormulaDoc): string[] {
|
|
const out = (doc.변수 ?? []).map(varId);
|
|
for (const l of doc.식 ?? []) {
|
|
const id = String(l.기호 ?? "");
|
|
if (id && !out.includes(id)) out.push(id);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export const isOption = (v: DocVar | undefined): boolean =>
|
|
!!v?.열쇠 && (v.연결?.갈래 === "선택" || v.연결?.갈래 === "입력");
|
|
|
|
/** 기호가 부르는 기호 — 식 줄 · 연결(조건 · 넘길 입력 · 구멍) */
|
|
export function usesOf(doc: FormulaDoc): Map<string, string[]> {
|
|
const out = new Map<string, string[]>();
|
|
const add = (id: string, more: string[]): void => {
|
|
const now = out.get(id) ?? [];
|
|
for (const n of more) if (!now.includes(n)) now.push(n);
|
|
out.set(id, now);
|
|
};
|
|
for (const v of doc.변수 ?? []) add(varId(v), v.연결 ? linkNames(v.연결) : []);
|
|
for (const l of doc.식 ?? []) add(String(l.기호 ?? ""), namesIn(l.식));
|
|
return out;
|
|
}
|
|
|
|
/** 단가 식이 기호 하나(또는 그 칸 꺼냄)면 그 기호 */
|
|
export function priceSym(line: MoneyLine): string | null {
|
|
const n = nodeOf(line.단가);
|
|
if (!n) return null;
|
|
if (n[0] === "var") return n[1];
|
|
if (n[0] === "field" && n[1][0] === "var") return n[1][1];
|
|
return null;
|
|
}
|
|
|
|
/** 금액 줄 하나만 단가로 쓰는 연결 기호(요소 · 단가산출) — {줄 차례: 기호} */
|
|
export function ownedOf(doc: FormulaDoc): Map<number, string> {
|
|
const count = new Map<string, number>();
|
|
const bump = (ns: string[]): void => ns.forEach((n) => count.set(n, (count.get(n) ?? 0) + 1));
|
|
for (const l of doc.금액 ?? []) bump([...namesIn(l.수량), ...namesIn(l.단가)]);
|
|
for (const ns of usesOf(doc).values()) bump(ns);
|
|
if (doc.결과) bump([doc.결과]);
|
|
const vars = new Map((doc.변수 ?? []).map((v) => [varId(v), v]));
|
|
const lines = new Set((doc.식 ?? []).map((l) => String(l.기호 ?? "")));
|
|
const out = new Map<number, string>();
|
|
(doc.금액 ?? []).forEach((l, i) => {
|
|
const s = priceSym(l);
|
|
const kind = s ? vars.get(s)?.연결?.갈래 : undefined;
|
|
if (s && (kind === "요소" || kind === "단가산출") && count.get(s) === 1 && !lines.has(s))
|
|
out.set(i, s);
|
|
});
|
|
return out;
|
|
}
|
|
|
|
/** 금액 줄 갈래 — 인력 · 재료(요소) · 기계(기계 시간당 경비 단가산출) · 단가산출 · 율(식) */
|
|
export function kindOf(
|
|
doc: FormulaDoc,
|
|
i: number,
|
|
look?: (key: string) => FormulaDoc | null,
|
|
): MoneyKind {
|
|
const line = (doc.금액 ?? [])[i] ?? {};
|
|
const s = priceSym(line);
|
|
const link = s ? (doc.변수 ?? []).find((v) => varId(v) === s)?.연결 : undefined;
|
|
if (link?.갈래 === "단가산출") {
|
|
const sub = link.키 && look ? look(link.키) : null;
|
|
const machine =
|
|
(link.입력 && "기계" in link.입력) ||
|
|
!!sub?.변수?.some((v) => v.열쇠 === "기계" && isOption(v));
|
|
return machine ? "기계" : "단가산출";
|
|
}
|
|
if (link?.갈래 === "요소") {
|
|
const lb = link.키 ? String(link.키).startsWith("LB") : line.비목 === "노무비"; // 고르기 전 = 비목으로
|
|
return lb ? "인력" : "재료";
|
|
}
|
|
return "율";
|
|
}
|
|
|
|
/** 차림에 안 넣는 기호 — 옵션 · 금액 줄이 가진 연결 */
|
|
export function hiddenOf(doc: FormulaDoc): Set<string> {
|
|
const out = new Set(ownedOf(doc).values());
|
|
for (const v of doc.변수 ?? []) if (isOption(v)) out.add(varId(v));
|
|
return out;
|
|
}
|
|
|
|
/** 쓰는 차례(입력 먼저) — 기호마다 부르는 기호를 먼저 */
|
|
function postOrder(doc: FormulaDoc, roots: string[], uses: Map<string, string[]>): string[] {
|
|
const out: string[] = [];
|
|
const seen = new Set<string>();
|
|
const go = (id: string): void => {
|
|
if (seen.has(id)) return;
|
|
seen.add(id);
|
|
for (const n of uses.get(id) ?? []) go(n);
|
|
out.push(id);
|
|
};
|
|
roots.forEach(go);
|
|
symbols(doc).forEach(go);
|
|
return out;
|
|
}
|
|
|
|
/** 금액 줄 i 가 (거쳐서) 부르는 기호 */
|
|
function reachOf(line: MoneyLine, uses: Map<string, string[]>): Set<string> {
|
|
const out = new Set<string>();
|
|
const go = (id: string): void => {
|
|
if (out.has(id)) return;
|
|
out.add(id);
|
|
(uses.get(id) ?? []).forEach(go);
|
|
};
|
|
[...namesIn(line.수량), ...namesIn(line.단가)].forEach(go);
|
|
return out;
|
|
}
|
|
|
|
/** 문서 → 줄 쌓기(저장 차림 + 안 놓인 것은 기본 자리) */
|
|
export function stackOf(doc: StackDoc): StackItem[] {
|
|
const money = doc.금액 ?? [];
|
|
const all = new Set(symbols(doc));
|
|
const hidden = hiddenOf(doc);
|
|
const items: StackItem[] = [];
|
|
const where = new Map<number, StackItem>();
|
|
const placed = new Set<string>();
|
|
const saved = Array.isArray(doc.쌓기) ? doc.쌓기 : [];
|
|
const taken = new Set<number>();
|
|
for (const s of saved) {
|
|
const it: StackItem = { 제목: String(s?.제목 ?? ""), 줄: [] };
|
|
if (s?.가나다) it.가나다 = true;
|
|
for (const l of Array.isArray(s?.줄) ? s.줄 : []) {
|
|
if (l && "금액" in l) {
|
|
let i = Number(l.금액);
|
|
if (!(money[i] && (money[i].이름 ?? "") === l.이름 && !taken.has(i)))
|
|
i = money.findIndex((m, k) => !taken.has(k) && (m.이름 ?? "") === l.이름);
|
|
if (i < 0 || taken.has(i)) continue;
|
|
taken.add(i);
|
|
where.set(i, it);
|
|
it.줄.push({ 꼴: "금액", i });
|
|
} else if (l && "공식" in l) {
|
|
const ids = (Array.isArray(l.공식) ? l.공식 : []).filter(
|
|
(x) => all.has(x) && !hidden.has(x) && !placed.has(x),
|
|
);
|
|
ids.forEach((x) => placed.add(x));
|
|
const one: StackLine = { 꼴: "공식", 기호: ids };
|
|
if (l.틀) Object.assign(one, { 틀: String(l.틀) }, l.틀꼴 ? { 틀꼴: String(l.틀꼴) } : {});
|
|
it.줄.push(one);
|
|
} else if (l && "글" in l) it.줄.push({ 꼴: "글", 글: String(l.글 ?? "") });
|
|
}
|
|
items.push(it);
|
|
}
|
|
// 안 놓인 금액 줄 — 앞 줄 항목(묶음 같으면) › 같은 제목 항목 › 새 항목
|
|
money.forEach((m, i) => {
|
|
if (taken.has(i)) return;
|
|
const title = String(m.묶음 ?? "");
|
|
const prev = where.get(i - 1);
|
|
let it = prev && prev.제목 === title ? prev : undefined;
|
|
if (it && prev) {
|
|
const at = prev.줄.findIndex((x) => x.꼴 === "금액" && x.i === i - 1);
|
|
prev.줄.splice(at + 1, 0, { 꼴: "금액", i });
|
|
} else {
|
|
it = saved.length ? items.find((x) => x.제목 === title) : undefined;
|
|
if (!it) items.push((it = { 제목: title, 줄: [] }));
|
|
it.줄.push({ 꼴: "금액", i });
|
|
}
|
|
taken.add(i);
|
|
where.set(i, it);
|
|
});
|
|
// 안 놓인 기호 — 처음 쓰는 금액 줄 항목의 첫 공식 묶음(없으면 맨 앞에 새로) · 아무도 안 쓰면 첫 항목
|
|
const uses = usesOf(doc);
|
|
const reach = money.map((m) => reachOf(m, uses));
|
|
const order = postOrder(doc, doc.결과 ? [doc.결과] : [], uses);
|
|
for (const id of order) {
|
|
if (!all.has(id) || hidden.has(id) || placed.has(id)) continue;
|
|
const i = reach.findIndex((r) => r.has(id));
|
|
let it = i >= 0 ? where.get(i) : items[0];
|
|
if (!it) items.push((it = { 제목: "", 줄: [] }));
|
|
let block = it.줄.find((x) => x.꼴 === "공식");
|
|
if (!block) it.줄.unshift((block = { 꼴: "공식", 기호: [] }));
|
|
(block as { 기호: string[] }).기호.push(id);
|
|
placed.add(id);
|
|
}
|
|
return items;
|
|
}
|
|
|
|
const saveOf = (items: StackItem[], at: (i: number) => number, money: MoneyLine[]): SaveItem[] =>
|
|
items.map((it) => ({
|
|
...(it.제목 ? { 제목: it.제목 } : {}),
|
|
...(it.가나다 ? { 가나다: true } : {}),
|
|
줄: it.줄.map((l): SaveLine => {
|
|
if (l.꼴 === "금액") return { 금액: at(l.i), 이름: money[l.i]?.이름 ?? "" };
|
|
if (l.꼴 === "글") return { 글: l.글 };
|
|
return {
|
|
공식: [...l.기호],
|
|
...(l.틀 ? { 틀: l.틀 } : {}),
|
|
...(l.틀꼴 ? { 틀꼴: l.틀꼴 } : {}),
|
|
};
|
|
}),
|
|
}));
|
|
|
|
/** 줄 쌓기 → 문서 — 금액 = 쌓은 차례(안 쌓인 금액 줄은 지움) · 차림이 기본과 다를 때만 `쌓기` */
|
|
export function docOf(doc: StackDoc, items: StackItem[]): StackDoc {
|
|
const money = doc.금액 ?? [];
|
|
const order: number[] = [];
|
|
for (const it of items)
|
|
for (const l of it.줄)
|
|
if (l.꼴 === "금액" && money[l.i] && !order.includes(l.i)) order.push(l.i);
|
|
const out = clone(doc);
|
|
delete out.쌓기;
|
|
if (doc.금액 !== undefined || order.length) out.금액 = order.map((i) => clone(money[i]));
|
|
const pos = new Map(order.map((i, k) => [i, k]));
|
|
const live = new Set(symbols(out));
|
|
const kept = items.map((it) => ({
|
|
...it,
|
|
줄: it.줄
|
|
.filter((l) => l.꼴 !== "금액" || pos.has(l.i))
|
|
.map((l) => (l.꼴 === "공식" ? { ...l, 기호: l.기호.filter((x) => live.has(x)) } : l)),
|
|
}));
|
|
const save = saveOf(kept, (i) => pos.get(i)!, money);
|
|
const plain = saveOf(stackOf(out), (i) => i, out.금액 ?? []);
|
|
if (JSON.stringify(save) !== JSON.stringify(plain)) out.쌓기 = save;
|
|
return out;
|
|
}
|
|
|
|
/** 줄 쌓기가 못 고치는 문서 칸(보존은 됨 · 수식 탭에서 고침) */
|
|
export function notesOf(doc: FormulaDoc): string[] {
|
|
const out: string[] = [];
|
|
if (doc.단계?.length) out.push("단계");
|
|
if (doc.조정 && Object.keys(doc.조정).length) out.push("조정");
|
|
const money = doc.금액 ?? [];
|
|
if (money.some((l) => l.번지 && !/^복사\d+$/.test(l.번지))) out.push("번지"); // 복사N = 복사해 펼친 조각(804-4)
|
|
if (money.some((l) => l.합밖)) out.push("합밖");
|
|
if (money.some((l) => l.자리)) out.push("줄 자리");
|
|
if (money.some((l) => l.기호)) out.push("금액 줄 기호");
|
|
return out;
|
|
}
|
|
|
|
// ── 고침 ───────────────────────────────────────────────────────────────
|
|
|
|
/** 안 쓰는 새 기호 — `기호_아래N` */
|
|
export function freshId(doc: FormulaDoc, sign: string, low = ""): string {
|
|
const have = new Set(symbols(doc));
|
|
if (low && !have.has(symId(sign, low))) return symId(sign, low);
|
|
for (let n = 1; ; n++) {
|
|
const id = symId(sign, `${low}${n}`);
|
|
if (!have.has(id)) return id;
|
|
}
|
|
}
|
|
|
|
export function newVar(id: string, link: DocVar["연결"], name = ""): DocVar {
|
|
const [기호, 아래, 위] = splitId(id);
|
|
return { 기호, 아래, 위, 이름: name, 단위: "", 연결: link };
|
|
}
|
|
|
|
/** 기호 지움 — 변수 · 식 줄(다른 식이 부르면 「정의 필요」 표지 · 오류 없음) */
|
|
export function dropSyms(doc: FormulaDoc, ids: string[]): void {
|
|
const gone = new Set(ids);
|
|
doc.변수 = (doc.변수 ?? []).filter((v) => !gone.has(varId(v)));
|
|
doc.식 = (doc.식 ?? []).filter((l) => !gone.has(String(l.기호 ?? "")));
|
|
}
|
|
|
|
/** 끌어 옮김(804-5) — 줄 `[i, j]` → 항목 k 의 p 자리(빼기 전 차례 · 같은 항목 아래로면 하나 당김) */
|
|
export function moveLine(items: StackItem[], i: number, j: number, k: number, p: number): void {
|
|
const [line] = items[i].줄.splice(j, 1);
|
|
if (!line || !items[k]) return void (line && items[i].줄.splice(j, 0, line));
|
|
items[k].줄.splice(Math.min(i === k && p > j ? p - 1 : p, items[k].줄.length), 0, line);
|
|
}
|
|
|
|
/** 끌어 옮김(804-5) — 항목 i → p 자리(빼기 전 차례) */
|
|
export function moveItem<T>(items: T[], i: number, p: number): void {
|
|
const [it] = items.splice(i, 1);
|
|
if (it !== undefined) items.splice(Math.min(p > i ? p - 1 : p, items.length), 0, it);
|
|
}
|
|
|
|
/** 공식 줄 값 · 식 고침 — 수 = 숫자 연결 · 식 = 식 줄 */
|
|
export function setRow(doc: FormulaDoc, id: string, text: string): void {
|
|
const t = text.trim().replace(/,/g, "");
|
|
const n = nodeOf(t);
|
|
const plain = n && (n[0] === "num" || (n[0] === "neg" && n[1][0] === "num"));
|
|
let v = (doc.변수 ?? []).find((x) => varId(x) === id);
|
|
if (plain || !t) {
|
|
doc.식 = (doc.식 ?? []).filter((l) => l.기호 !== id);
|
|
if (!v) (doc.변수 ??= []).push((v = newVar(id, { 갈래: "숫자" })));
|
|
const value = t ? Number(t) : null;
|
|
v.연결 = { 갈래: "숫자", 값: Number.isFinite(value) ? value : t };
|
|
return;
|
|
}
|
|
if (v && v.연결?.갈래 !== "식") v.연결 = { 갈래: "식" };
|
|
const line = (doc.식 ?? []).find((l) => l.기호 === id);
|
|
if (line) line.식 = text.trim();
|
|
else (doc.식 ??= []).push({ 기호: id, 식: text.trim() });
|
|
}
|
|
|
|
/** 기호 이름 바꿈 — 문서 전부(`rename`) + 차림 */
|
|
export function renameSym(doc: StackDoc, old: string, now: string): void {
|
|
const [s, lo, up] = splitId(now);
|
|
rename(doc, old, s, lo, up);
|
|
for (const it of doc.쌓기 ?? [])
|
|
for (const l of it.줄) if ("공식" in l) l.공식 = l.공식.map((x) => (x === old ? now : x));
|
|
}
|
|
|
|
/** 공식 묶음 꼴 — 틀과 견줌(이름 · 식 · 연결 갈래 · 숫자 값은 안 봄) */
|
|
export function shapeOf(doc: FormulaDoc, ids: string[]): string {
|
|
const vars = new Map((doc.변수 ?? []).map((v) => [varId(v), v]));
|
|
const lines = new Map((doc.식 ?? []).map((l) => [String(l.기호 ?? ""), l.식]));
|
|
return JSON.stringify(
|
|
ids.map((id) => {
|
|
const t = lines.get(id);
|
|
const n = t === undefined ? null : nodeOf(t);
|
|
return [id, n ? textOf(n) : t !== undefined ? String(t) : (vars.get(id)?.연결?.갈래 ?? "식")];
|
|
}),
|
|
);
|
|
}
|
|
|
|
/** 틀 펼침 — 다른 문서(틀 · 단가산출 결과)의 기호 · 식을 이 문서로 옮김(겹치는 기호 = 아래첨자 끝 수)
|
|
* `target` = 결과를 받을 이 문서 기호(단가산출 연결 → 식) · 없으면 결과 기호 그대로 · 돌려줌 = 옮긴 기호(입력 먼저) */
|
|
export function unfold(doc: FormulaDoc, src: FormulaDoc, target?: string): string[] {
|
|
const sub = clone(src);
|
|
const result = sub.결과 ?? "";
|
|
const mine = new Set(symbols(doc).filter((x) => x !== target));
|
|
const own = new Set(symbols(sub));
|
|
const taken = new Set([...mine, ...own]);
|
|
for (const id of symbols(sub)) {
|
|
if (id === result && target) continue;
|
|
if (!mine.has(id) && id !== target) continue;
|
|
const [s, lo, up] = splitId(id);
|
|
let n = 2;
|
|
while (taken.has(symId(s, `${lo}${n}`, up))) n++;
|
|
rename(sub, id, s, `${lo}${n}`, up);
|
|
taken.add(symId(s, `${lo}${n}`, up));
|
|
}
|
|
const given = target ? ((doc.변수 ?? []).find((v) => varId(v) === target)?.연결?.입력 ?? {}) : {};
|
|
let resultId = sub.결과 ?? "";
|
|
if (target && resultId && resultId !== target) {
|
|
const [s, lo, up] = splitId(target);
|
|
rename(sub, resultId, s, lo, up);
|
|
resultId = target;
|
|
}
|
|
const keys = new Set((doc.변수 ?? []).map((v) => v.열쇠).filter(Boolean));
|
|
const moved: string[] = [];
|
|
for (const v of sub.변수 ?? []) {
|
|
const id = varId(v);
|
|
if (id === resultId && target) continue;
|
|
if (v.열쇠 && v.열쇠 in given) {
|
|
v.연결 = { 갈래: "식" };
|
|
(sub.식 ??= []).push({ 기호: id, 식: String(given[v.열쇠]) });
|
|
delete v.열쇠;
|
|
} else if (v.열쇠 && keys.has(v.열쇠)) delete v.열쇠;
|
|
(doc.변수 ??= []).push(v);
|
|
}
|
|
for (const l of sub.식 ?? []) (doc.식 ??= []).push(l);
|
|
if (target) {
|
|
const t = (doc.변수 ?? []).find((v) => varId(v) === target);
|
|
const r = (src.변수 ?? []).find((v) => varId(v) === src.결과);
|
|
if (t) {
|
|
t.연결 = { 갈래: "식" };
|
|
if (!t.단위) t.단위 = r?.단위 || src.결과단위 || "";
|
|
}
|
|
}
|
|
const uses = usesOf(sub);
|
|
for (const id of postOrder(sub, resultId ? [resultId] : [], uses))
|
|
if (
|
|
symbols(sub).includes(id) &&
|
|
!moved.includes(id) &&
|
|
!isOption(sub.변수?.find((v) => varId(v) === id))
|
|
)
|
|
moved.push(id);
|
|
return moved;
|
|
}
|
|
|
|
/** 단가산출 연결이 넘길 입력 값 — 부른 쪽 값으로 셈(펼침이 같은 계산기로 그 문서를 셈) */
|
|
export function subInputs(
|
|
link: Link | undefined,
|
|
vals: Record<string, unknown>,
|
|
): Record<string, unknown> {
|
|
const out: Record<string, unknown> = {};
|
|
for (const [name, t] of Object.entries(link?.입력 ?? {})) {
|
|
const n = nodeOf(t);
|
|
const v = n ? evalNode(n, vals) : undefined;
|
|
if (v !== undefined) out[name] = v;
|
|
}
|
|
return out;
|
|
}
|