fix(est): propagate assigned prices into final totals

This commit is contained in:
2026-11-05 14:23:21 +09:00
parent a6fc896655
commit 3e9fb28714
3 changed files with 108 additions and 11 deletions
+37 -10
View File
@@ -10,6 +10,8 @@ const EstText = (() => {
const numeric = (s) => Number(String(s).replaceAll(",", ""));
const sumCurrency = (values) =>
values.reduce((n, value) => n + EstMath.even(num(value) * 10000), 0) / 10000;
const autoTotal = (row) =>
num(row.LinkIndex) !== 62 && String(row.LineData || "").includes("\x1a");
const stripLabels = (text) =>
String(text)
.replace(/\x0e[^\x0e]*\x0e|\x13[^\x13]*\x13|\x15[^\x15]*\x15|\([가-힣][^()]*\)/g, "")
@@ -87,7 +89,7 @@ const EstText = (() => {
const levels = [],
componentRows = [];
for (let j = index + 1; j < rows.length && num(rows[j].Index) <= 0; j++)
if (String(rows[j].LineData || "").includes("\x1a")) levels.push(num(rows[j].Level));
if (autoTotal(rows[j])) levels.push(num(rows[j].Level));
else if (component[num(rows[j].SCheck)] && marker.test(String(rows[j].LineData || "")))
componentRows.push(j);
finalLevel = levels.length ? Math.min(...levels) : -1;
@@ -153,7 +155,7 @@ const EstText = (() => {
const values = [];
for (let j = index - 1; j >= 0 && !(num(rows[j].Index) > 0); j--)
for (const match of String(rows[j].LineData || "").matchAll(
/\x06([A-Z])\x06\s*([\x12\x0c])([-\d.,]+)\2/g,
/\x06([A-Z])\x06\s*([\x03\x12\x0c])([-\d.,]+)\2/g,
))
if (match[1] === group) values.unshift(numeric(match[3]));
if (!values.length) selectionError = `합계 구간 ${group}에 지정된 결과가 없습니다.`;
@@ -166,7 +168,7 @@ const EstText = (() => {
const previous = rows[j];
if (num(previous.Index) !== -1) {
const results = [
...String(previous.LineData || "").matchAll(/([\x12\x0c])([-\d.,]+)\1/g),
...String(previous.LineData || "").matchAll(/([\x03\x12\x0c])([-\d.,]+)\1/g),
];
if (results.length) values.push(numeric(results.at(-1)[2]));
}
@@ -219,6 +221,7 @@ const EstText = (() => {
);
else if (currentSource && sc === 0 && /\x0e/.test(original))
text = text.replace(/\x03([-\d.,]+)\x03/g, () => `\x03${num(currentSource.TC)}\x03`);
row.LineData = text;
text = text.replace(
/\x11([-\d.,]+)\x11/g,
(whole, value) => `\x11${changed.get(numeric(value)) ?? numeric(value)}\x11`,
@@ -273,8 +276,7 @@ const EstText = (() => {
for (let j = index - 1; j >= 0; j--) {
const previous = rows[j];
if (!previous.LineData) continue;
if (!String(previous.LineData).includes("\x1a") || !component[num(previous.SCheck)])
break;
if (!autoTotal(previous) || !component[num(previous.SCheck)]) break;
section.push(num(previous.TC));
}
calculated = sumCurrency(section);
@@ -316,7 +318,7 @@ const EstText = (() => {
? []
: [
...assignmentText.matchAll(
/(?:^\s*| {2,}|[,,]\s*|(?<=[가-힣])\s+|[::]\s*)([A-Za-z][A-Za-z0-9]*)\s*=/g,
/(?:^\s*| {2,}|[,,]\s*|(?<=[가-힣])\s+|[::]\s*|['"]\s*)([A-Za-z][A-Za-z0-9]*)\s*=/g,
),
];
for (let j = 0; j < assignments.length; j++) {
@@ -325,6 +327,8 @@ const EstText = (() => {
let body = text.slice(assignment.index + assignment[0].length, end);
if (!matched && /^\s*\*.*\*\s*$/.test(text)) body = body.replace(/\*\s*$/, "");
body = body.replace(/\{[^}]*\}/g, "");
if (/['"]\s*[A-Za-z][A-Za-z0-9]*\s*=/.test(assignment[0]))
body = body.replace(/['"]\s*$/, "");
if (!matched) body = body.split("{")[0];
body = body.split(/\s+[::]\s*/)[0].replace(/[,,]\s*$/, "");
if (body.includes("─") && calculated !== null) {
@@ -394,7 +398,7 @@ const EstText = (() => {
row.Precision ??
explicit?.digits ??
(sc < 0 || num(row.LinkIndex) !== 0
? (num(row.Level) === finalLevel && text.includes("\x1a")) ||
? (num(row.Level) === finalLevel && autoTotal(row)) ||
index === singleComponent ||
options.CostValueTrunc
? 0
@@ -475,7 +479,7 @@ const EstText = (() => {
row.LineData = rendered;
changed.set(oldValue, firstValue);
checks.push({ slot: index, stored: oldValue, calculated: firstValue });
if (text.includes("\x1a") && component[sc]) {
if (autoTotal(row) && component[sc]) {
if (num(row.Level) === finalLevel) totals[component[sc]] = calculated;
}
} else if (!matched && component[sc] && text.includes("\x03") && !text.includes("\x1a")) {
@@ -485,13 +489,36 @@ const EstText = (() => {
1,
);
row.LineData = text;
} else if (!matched && text.includes("\x1a") && component[sc]) {
const value = clean(text).split(":").slice(1).join(":").trim();
} else if (!matched && autoTotal(row) && component[sc]) {
let value = clean(text).split(":").slice(1).join(":").trim();
// Bare final totals can contain an already truncated amount rather than an equation.
// Rebuild only when the original component rows establish that subtotal relationship.
const savedValue = clean(original).split(":").slice(1).join(":").trim();
if (num(row.Level) === finalLevel && /^[-\d.,]+$/.test(savedValue)) {
const before = [],
after = [];
for (let j = index - 1; j >= 0 && num(rows[j].Index) <= 0; j--) {
if (num(rows[j].SCheck) !== sc) continue;
if (autoTotal(rows[j])) break;
const results = [...String(input[j].LineData || "").matchAll(/\x12([-\d.,]+)\x12/g)];
if (!results.length && input[j].TC == null) continue;
before.push(results.length ? numeric(results.at(-1)[1]) : num(input[j].TC));
after.push(num(rows[j].TC));
if (num(rows[j].LinkIndex) === 61) break;
}
if (before.length && floating(sumCurrency(before), 0, 2) === numeric(savedValue))
value = String(sumCurrency(after));
}
const calculated = floating(
EstExpression.evaluate(expression(value || "0"), (key) => variables[key]),
num(row.Level) === finalLevel ? 0 : 1,
);
row.TC = calculated;
if (value !== clean(text).split(":").slice(1).join(":").trim()) {
const start = text.indexOf(":") + 1,
end = text.lastIndexOf("\x1a");
row.LineData = text.slice(0, start) + calculated + text.slice(end);
}
if (num(row.Level) === finalLevel) totals[component[sc]] = calculated;
}
} catch (error) {