// REA f1a078, f17c10, f1a8c4, f3242c, f36074, f34970. const EstRows = (() => { const { num, components, units, sum, fixed, floating, amounts, add } = EstMath; const target = { 1: "GC", 2: "NC", 3: "JC", 4: "SC" }; function baseKind(row) { const text = String(row.Size || ""); if (/[노품인]/.test(text)) return "NC"; if (/[재자]/.test(text)) return "GC"; if (text.includes("경")) return "JC"; if (/계|총|합/.test(text)) return "TC"; if (text.includes("비목")) return null; return { 1: "GC", 2: "NC", 3: "JC", 4: "TC" }[num(row.OCheck) % 5] || null; } function percent(row, preceding, digits = 0) { if ([1, 3].includes(num(row.CalcIndex))) return row; // f1a8c4 matches Chr(64 + source.SKind) against the percentage row's Code. const selectedGroups = String(row.Code || "").toUpperCase(); const eligible = num(row.LinkIndex) === 62 ? preceding.filter( (r) => num(r.SKind) > 0 && selectedGroups.includes(String.fromCharCode(64 + num(r.SKind))), ) : preceding; const base = baseKind(row), sc = num(row.SCheck), p = EstMath.precision(row, digits); components.forEach((key) => (row[key] = 0)); if (base) { const amount = eligible.reduce((n, r) => n + num(r[base]), 0); row.TU = amount; row[target[sc] || "JC"] = floating((amount * num(row.GN)) / 100, p.digits, p.method); } else { components.forEach( (key) => (row[key] = floating( (eligible.reduce((n, r) => n + num(r[key]), 0) * num(row.GN)) / 100, p.digits, p.method, )), ); row.TU = eligible.reduce((n, r) => n + num(r.TC), 0); } row.TC = sum(row); return row; } function procurement(row, preceding) { if ([1, 3].includes(num(row.CalcIndex))) return row; const key = { 1: "GC", 2: "NC", 3: "JC", 4: "TC" }[num(row.OCheck)] || "TC"; const base = preceding .filter((r) => num(r.LinkIndex) !== 57) .reduce((n, r) => n + num(r[key]), 0); const fee = Math.trunc((base * num(row.GN)) / 100), step = 10 ** num(row.Gangub), quotient = (base + fee) / step; const rounded = num(row.Index) === 1 ? EstMath.half(quotient) : num(row.Gangub) < 1 ? quotient : num(row.Index) === 2 ? Math.trunc(quotient) : EstMath.half(quotient + 0.5); components.forEach((k) => (row[k] = 0)); row[target[num(row.SCheck)] || "JC"] = rounded * step - base; row.TU = base; row.TC = sum(row); return row; } function subtotal(row, preceding, digits = 0) { const eligible = preceding.filter((r) => num(r.CalcIndex) < 2 && num(r.LinkIndex) !== 51); const values = Object.fromEntries( components.map((k) => [k, eligible.reduce((n, r) => n + num(r[k]), 0)]), ); const kind = num(row.LinkIndex) === 68 ? null : baseKind(row), sc = num(row.LinkIndex) === 68 ? undefined : target[num(row.SCheck)]; units.forEach((key) => (row[key] = 0)); if (sc) row[units[components.indexOf(sc)]] = kind === "TC" ? sum(values) : num(values[kind]); else components.forEach( (key, i) => (row[units[i]] = kind && kind !== "TC" && kind !== key ? 0 : values[key]), ); return amounts(row, digits); } function head(rows, options = {}, resolve = () => null, refreshRows = []) { const output = structuredClone(rows), titles = [], errors = [], refreshErrors = []; let current = null, body = []; function finish() { if (!current) return; Object.assign(current, add(body)); titles.push({ ...current }); } for (const [index, row] of output.entries()) { if (num(row.Index) > 0) { finish(); current = row; body = []; continue; } if (!current) continue; const link = num(row.LinkIndex); if ([58, 62].includes(link)) percent(row, body, options.HeadDigits || 0); else if ([51, 68].includes(link)) { subtotal(row, body, options.HeadDigits || 0); if (link === 68) { // f3242c resets its accumulators to the converted subtotal at LinkIndex 68. body = [row]; continue; } } else if (link === 52) { const before = add(body), ratio = num(row.GN) / 100; components.forEach((key) => (row[key] = floating(before[key] * ratio))); row.TC = sum(row); row.TU = before.TC; body = [row]; continue; } else { const requested = refreshRows.includes(index); if ((options.SourceRefresh || requested) && ![1, 3].includes(num(row.CalcIndex))) { try { const price = resolve(row); if (requested && !price) throw Error("원천 단가를 찾을 수 없습니다."); if (price) units.forEach((key, i) => (row[key] = num(price[components[i]]))); } catch (error) { errors.push(`${current.Name}: ${error.message}`); if (requested) refreshErrors.push(error.message); } } amounts(row, options.HeadDigits || 0, options.TotalFit); } body.push(row); } finish(); return refreshRows.length ? { rows: output, titles, errors, refreshErrors } : { rows: output, titles, errors }; } function estimate(rows, options = {}, resolve = () => null, refreshRows = []) { const output = structuredClone(rows), errors = [], refreshErrors = []; let segment = [], prior = [], boundary = false; for (const [index, row] of output.entries()) { const level = num(row.Level), link = num(row.LinkIndex); if (level > 0) { segment = []; continue; } if (link === 54) { Object.assign(row, add(prior)); boundary = true; continue; } if (link === 59) { boundary = true; continue; } if (boundary && [55, 58, 59].includes(link)) continue; if (link === 57) procurement(row, segment); else if ([62, 67, 58].includes(link)) percent(row, segment, options.EstDigits || 0); else { const requested = refreshRows.includes(index); if ((options.SourceRefresh || requested) && ![1, 3].includes(num(row.CalcIndex))) { try { const price = resolve(row); if (requested && !price) throw Error("원천 단가를 찾을 수 없습니다."); if (price) units.forEach((key, i) => (row[key] = num(price[components[i]]))); } catch (error) { errors.push(`${row.Name}: ${error.message}`); if (requested) refreshErrors.push(error.message); } } amounts(row, options.EstDigits || 0, options.TotalFit); } segment.push(row); if (!boundary) prior.push(row); } for (let i = output.length - 1; i >= 0; i--) { const row = output[i], level = num(row.Level); if (level <= 0) continue; const children = []; for (let j = i + 1; j < output.length; j++) { const next = output[j]; if (num(next.Level) >= level || [54, 58, 59].includes(num(next.LinkIndex))) break; if (num(next.Level) === 0) children.push(next); } Object.assign(row, add(children)); } const direct = output.find((r) => num(r.LinkIndex) === 54) || add(prior); return refreshRows.length ? { rows: output, direct, errors, refreshErrors } : { rows: output, direct, errors }; } return { head, estimate, percent, procurement, subtotal, baseKind }; })();