// 줄 쌓기 단가산출 시험 — 왼쪽 줄 쌓기 편집(종류마다 필요한 칸만 · 칸은 data-p 경로로 doc 에 바로 씀)
import { LABOR, MACHINE, MATERIAL, SANGEUN, TEMPLATES } from "./samples.js";
import { esc } from "./render.js";
export const KINDS = {
mach: "① 기계",
ref: "② 다른 단산 참조",
labor: "③ 인력",
rate: "④ 율",
mat: "⑤ 재료",
formula: "⑥ 공식",
text: "⑦ 글 · 근거",
};
const COLSEL = { l: "노무비", m: "재료비", e: "경비" };
const inp = (p, v, cls = "", ph = "") =>
``;
const sel = (p, v, opts) =>
``;
const pick = (list, act, label) =>
``;
export function allTemplates(mine) {
return [...TEMPLATES, ...mine];
}
/** 「+ 줄」 고르개 — 공식은 틀마다 하나씩 */
export function addLineSelect(i, mine) {
const tpls = allTemplates(mine)
.map((t) => ``)
.join("");
return ``;
}
function opsEdit(p, ops) {
const one = (o, k) =>
`${sel(`${p}.ops.${k}.op`, o.op, { "×": "×", "÷": "÷", "/": "/" })}${inp(
`${p}.ops.${k}.v`,
o.v,
"v",
"값 · 이름",
)}${inp(`${p}.ops.${k}.u`, o.u, "u", "단위")}`;
return `
${(ops ?? []).map(one).join("")}
`;
}
const copsNote = (ln) =>
ln.cops
? `비목 따로 꼬리(붙여넣기): ${Object.entries(ln.cops)
.map(
([c, ops]) =>
`${COLSEL[c]} ${ops.map((o) => `${o.op} ${esc(o.v)} ${esc(o.u)}`).join(" ")}`,
)
.join(" · ")}
`
: "";
/** 공식 줄 — 접힘 = 값 칸만 · 풀림 = 「이름 = 식」 글 줄 전부 */
function formulaEdit(p, ln, r, mine) {
const tpl = allTemplates(mine).find((t) => t.id === ln.tpl);
const changed = tpl && JSON.stringify(shape(tpl.rows)) !== JSON.stringify(shape(ln.rows));
const label = !tpl
? ln.tpl === null && !ln.rows?.some((x) => x.x)
? "새 공식"
: "내 공식"
: changed
? `내 틀 (${esc(tpl.name)} 에서 변경)`
: esc(tpl.name);
const rows = (ln.rows ?? [])
.map((row, k) => {
const rr = r?.rows?.[k] ?? {};
const rp = `${p}.rows.${k}`;
const plain = /^-?[\d.]+$/.test(String(row.x ?? "").trim());
const val = `${rr.err ? esc(rr.err) : (rr.val ?? "")}`;
const src = row.src ? `표` : "";
const table = ``;
if (!ln.open) {
if (plain || !row.x)
return `${esc(row.n || "?")} = ${inp(`${rp}.x`, row.x, "num")} ${esc(row.u)} ${table}${src}
`;
return `${esc(row.n)} = ${esc(row.x)} = ${val} ${esc(row.u)}
`;
}
return `${inp(`${rp}.n`, row.n, "name", "이름")} = ${inp(`${rp}.x`, row.x, `expr ${rr.err ? "bad" : ""}`, "값 또는 식")}${inp(
`${rp}.u`,
row.u,
"u",
"단위",
)}${val}${table}${src}
`;
})
.join("");
return `${label}
${rows}
${ln.open ? `` : ""}`;
}
/** 틀과 견줄 꼴 — 값 줄은 이름만 · 식 줄은 이름과 식 */
export function shape(rows) {
return (rows ?? []).map((x) =>
/^-?[\d.]+$/.test(String(x.x).trim()) ? [x.n] : [x.n, String(x.x).replace(/\s/g, "")],
);
}
function lineBody(p, ln, r, mine) {
const price3 = () =>
`노무 ${inp(`${p}.res.l`, ln.res?.l, "num")} 재료 ${inp(`${p}.res.m`, ln.res?.m, "num")} 경비 ${inp(
`${p}.res.e`,
ln.res?.e,
"num",
)}
`;
switch (ln.k) {
case "mach":
return `${pick(MACHINE, `res:mach:${p}`, "견본 기계 ▾")}${inp(`${p}.res.name`, ln.res?.name, "wide", "기계 이름")}
${price3()}${opsEdit(p, ln.ops)}${copsNote(ln)}`;
case "ref":
return `${pick(SANGEUN, `res:ref:${p}`, "견본 단산 ▾")} 단산 ${inp(`${p}.res.no`, ln.res?.no, "num s")}${inp(
`${p}.res.name`,
ln.res?.name,
"wide",
"단가산출 이름",
)}
${price3()}${opsEdit(p, ln.ops)}${copsNote(ln)}`;
case "labor":
case "mat":
return `${pick(ln.k === "labor" ? LABOR : MATERIAL, `res:${ln.k}:${p}`, ln.k === "labor" ? "견본 노임 ▾" : "견본 자재 ▾")}${inp(
`${p}.res.name`,
ln.res?.name,
"wide",
"이름",
)} 단가 ${inp(`${p}.res.price`, ln.res?.price, "num")}${ln.k === "mat" ? ` → ${sel(`${p}.col`, ln.col || "m", COLSEL)}` : ""}
${opsEdit(p, ln.ops)}`;
case "rate":
return `${inp(`${p}.name`, ln.name, "wide", "이름(예: 공구손료 및 경장비)")}
기준 ${sel(`${p}.scope`, ln.scope || "item", { item: "이 항목 위 줄", all: "위 전부" })} 의 ${sel(`${p}.baseCol`, ln.baseCol || "l", COLSEL)}
× ${inp(`${p}.rate`, ln.rate, "num s")} % → ${sel(`${p}.col`, ln.col || "m", COLSEL)}
글 「( ${inp(`${p}.of`, ln.of, "s2", "노무비")}의 % )」 · 소계 이름 ${inp(`${p}.baseName`, ln.baseName, "s2", "비우면 안 찍음")}
`;
case "formula":
return formulaEdit(p, ln, r, mine);
case "text":
return inp(`${p}.text`, ln.text, "full", "< 품셈 근거 > · ※ 메모 · 별산");
}
return "";
}
export function renderEdit(doc, res, mine) {
const items = (doc.items ?? [])
.map((item, i) => {
const lines = (item.lines ?? [])
.map((ln, j) => {
const p = `items.${i}.lines.${j}`;
const r = res.items[i]?.lines[j];
return `⠿${KINDS[ln.k]}${
r?.err ? `${esc(r.err)}` : ""
}
${lineBody(p, ln, r, mine)}
`;
})
.join("");
return `${inp(`items.${i}.title`, item.title, "title", "항목 이름(비우면 번호 없음)")}
${addLineSelect(i, mine)}
${lines || '
줄 없음 — [+ 줄 ▾]
'}
`;
})
.join("");
return `이름 ${inp("name", doc.name, "wide")} 단위 ${inp("unit", doc.unit, "s")}
${items}`;
}