fix(est): propagate text subtotals and bid rates
This commit is contained in:
@@ -41,6 +41,7 @@ const EstText = (() => {
|
||||
variables = Object.create(null),
|
||||
changed = new Map(),
|
||||
currentSource = null,
|
||||
finalOverride = null,
|
||||
totals = {},
|
||||
finalLevel = -1,
|
||||
plainTotals = {},
|
||||
@@ -48,6 +49,12 @@ const EstText = (() => {
|
||||
hasAutoTotals = false;
|
||||
function finish() {
|
||||
if (!head) return;
|
||||
if (finalOverride) {
|
||||
components.forEach((key) => (head[key] = num(finalOverride[key])));
|
||||
head.TC = sum(head);
|
||||
titles.push({ ...head });
|
||||
return;
|
||||
}
|
||||
components.forEach(
|
||||
(key) => (head[key] = !hasAutoTotals ? floating(num(plainTotals[key])) : num(totals[key])),
|
||||
);
|
||||
@@ -56,12 +63,14 @@ const EstText = (() => {
|
||||
}
|
||||
for (let index = 0; index < rows.length; index++) {
|
||||
const row = rows[index];
|
||||
delete row.Error;
|
||||
if (num(row.Index) > 0) {
|
||||
finish();
|
||||
head = row;
|
||||
variables = Object.create(null);
|
||||
changed = new Map();
|
||||
currentSource = null;
|
||||
finalOverride = null;
|
||||
totals = {};
|
||||
plainTotals = {};
|
||||
for (const [key, tag] of [
|
||||
@@ -87,6 +96,32 @@ const EstText = (() => {
|
||||
continue;
|
||||
}
|
||||
if (!head || !row.LineData) continue;
|
||||
if (num(row.Level) > 0) finalOverride = null;
|
||||
if ([51, 52].includes(num(row.LinkIndex))) {
|
||||
if (num(row.LinkIndex) === 52) {
|
||||
const rate = String(row.LineData).match(/\x15([-\d.,]+)\x15/);
|
||||
const multiplier = rate ? numeric(rate[1]) / 100 : 0;
|
||||
const base = Object.fromEntries(components.map((key) => [key, 0]));
|
||||
for (let j = index - 1; j >= 0; j--) {
|
||||
const previous = rows[j],
|
||||
key = component[num(previous.SCheck)];
|
||||
if (num(previous.Level) > 0 || num(previous.Index) > 0) break;
|
||||
if (!key) continue;
|
||||
const values = [
|
||||
...String(previous.LineData || "").matchAll(/([\x12\x0c])([-\d.,]+)\1/g),
|
||||
];
|
||||
const value = values.length ? numeric(values.at(-1)[2]) : num(previous.TC);
|
||||
// f2c680/f2c74e overwrites NC while walking backward; the other components add.
|
||||
base[key] = key === "NC" ? value : base[key] + value;
|
||||
}
|
||||
components.forEach(
|
||||
(key) => (row[key] = EstMath.even(base[key] * multiplier * 10000) / 10000),
|
||||
);
|
||||
row.TC = sum(row);
|
||||
}
|
||||
finalOverride = row;
|
||||
continue;
|
||||
}
|
||||
const original = String(row.LineData),
|
||||
matched = original.match(marker),
|
||||
sc = num(row.SCheck);
|
||||
@@ -101,6 +136,71 @@ const EstText = (() => {
|
||||
}
|
||||
}
|
||||
let text = original.replace(/\{([+-])\}/g, "$1");
|
||||
let selectionError = "";
|
||||
if (num(row.LinkIndex) === 62) {
|
||||
text = text.replace(/(\x07([A-Z])\x07\s*)\x1a[^\x1a]*\x1a/g, (whole, reference, group) => {
|
||||
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,
|
||||
))
|
||||
if (match[1] === group) values.unshift(numeric(match[3]));
|
||||
if (!values.length) selectionError = `합계 구간 ${group}에 지정된 결과가 없습니다.`;
|
||||
return values.length ? `${reference}\x1a${values.join(" + ")}\x1a` : whole;
|
||||
});
|
||||
}
|
||||
if (num(row.LinkIndex) === 61 && text.includes("\x0c")) {
|
||||
const values = [];
|
||||
for (let j = index - 1; j >= 0; j--) {
|
||||
const previous = rows[j];
|
||||
if (num(previous.Index) !== -1) {
|
||||
const results = [
|
||||
...String(previous.LineData || "").matchAll(/([\x12\x0c])([-\d.,]+)\1/g),
|
||||
];
|
||||
if (results.length) values.push(numeric(results.at(-1)[2]));
|
||||
}
|
||||
if (num(previous.Level) > 0 || num(previous.Index) > 0 || num(previous.Index) === -20)
|
||||
break;
|
||||
}
|
||||
const precision =
|
||||
num(row.DigetIndex) && num(row.Diget) > 0
|
||||
? EstMath.precision(row)
|
||||
: { digits: 1, method: 2 };
|
||||
const subtotal = floating(
|
||||
sumCurrency(values),
|
||||
options.CostValueTrunc && sc < 0 ? 0 : precision.digits,
|
||||
precision.method,
|
||||
);
|
||||
text = text.replace(/\x0c([-\d.,]+)\x0c/g, (whole, value) => {
|
||||
changed.set(numeric(value), subtotal);
|
||||
return `\x0c${subtotal}\x0c`;
|
||||
});
|
||||
row.TC = subtotal;
|
||||
row.LineData = text;
|
||||
}
|
||||
if (num(row.LinkIndex) === 58 && text.includes("\x0c")) {
|
||||
let sourceKind = num(row.OCheck);
|
||||
if (/노무|인건|품/.test(clean(text))) sourceKind = -1;
|
||||
else if (/재료|자재/.test(clean(text))) sourceKind = -2;
|
||||
else if (/경비/.test(clean(text))) sourceKind = -3;
|
||||
let value;
|
||||
for (let j = index - 1; j >= 0; j--) {
|
||||
const previous = rows[j],
|
||||
kind = num(previous.SCheck);
|
||||
if ((!sourceKind && kind < 0 && kind > -4) || (sourceKind && kind === sourceKind)) {
|
||||
const results = [
|
||||
...String(previous.LineData || "").matchAll(/([\x12\x0c])([-\d.,]+)\1/g),
|
||||
];
|
||||
if (results.length) {
|
||||
value = numeric(results.at(-1)[2]);
|
||||
row.OCheck = kind;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (num(previous.Level) > 0 || num(previous.Index) > 0) break;
|
||||
}
|
||||
if (value !== undefined) text = text.replace(/\x0c[-\d.,]+\x0c/g, `\x0c${value}\x0c`);
|
||||
}
|
||||
if (currentSource && sc < 0 && component[sc])
|
||||
text = text.replace(
|
||||
/\x03([-\d.,]+)\x03/g,
|
||||
@@ -112,10 +212,11 @@ const EstText = (() => {
|
||||
/\x11([-\d.,]+)\x11/g,
|
||||
(whole, value) => `\x11${changed.get(numeric(value)) ?? numeric(value)}\x11`,
|
||||
);
|
||||
text = text.replace(
|
||||
/\x0c([-\d.,]+)\x0c/g,
|
||||
(whole, value) => `\x0c${changed.get(numeric(value)) ?? numeric(value)}\x0c`,
|
||||
);
|
||||
if (![58, 61].includes(num(row.LinkIndex)))
|
||||
text = text.replace(
|
||||
/\x0c([-\d.,]+)\x0c/g,
|
||||
(whole, value) => `\x0c${changed.get(numeric(value)) ?? numeric(value)}\x0c`,
|
||||
);
|
||||
if (text.includes("\x1a") && text.includes(":") && sc !== -5) {
|
||||
const start = text.indexOf(":") + 1,
|
||||
end = text.includes("\x12") ? text.indexOf("\x12") : text.length;
|
||||
@@ -135,6 +236,7 @@ const EstText = (() => {
|
||||
const assignedNames = new Map();
|
||||
let componentDeclaration;
|
||||
try {
|
||||
if (selectionError) throw Error(selectionError);
|
||||
if ((matched || /^\s*[A-Za-z][A-Za-z0-9]*\s*=/.test(text)) && /─/.test(text)) {
|
||||
const above = clean(rows[index - 1]?.LineData),
|
||||
below = clean(rows[index + 1]?.LineData);
|
||||
@@ -374,7 +476,7 @@ const EstText = (() => {
|
||||
} else if (!matched && text.includes("\x1a") && component[sc]) {
|
||||
const value = clean(text).split(":").slice(1).join(":").trim();
|
||||
const calculated = floating(
|
||||
changed.get(numeric(value)) ?? numeric(value),
|
||||
EstExpression.evaluate(expression(value || "0"), (key) => variables[key]),
|
||||
num(row.Level) === finalLevel ? 0 : 1,
|
||||
);
|
||||
row.TC = calculated;
|
||||
@@ -391,6 +493,8 @@ const EstText = (() => {
|
||||
return { rows, titles, errors, checks };
|
||||
}
|
||||
const names = {
|
||||
"\x06": "구간지정",
|
||||
"\x07": "구간합계",
|
||||
"\x03": "단가",
|
||||
"\x0c": "다른줄",
|
||||
"\x0e": "품명",
|
||||
@@ -401,7 +505,10 @@ const EstText = (() => {
|
||||
"\x1a": "합계",
|
||||
};
|
||||
const display = (text) =>
|
||||
String(text || "").replace(/[\x03\x0c\x0e\x11\x12\x13\x15\x1a]/g, (ch) => `⟦${names[ch]}⟧`);
|
||||
String(text || "").replace(
|
||||
/[\x03\x06\x07\x0c\x0e\x11\x12\x13\x15\x1a]/g,
|
||||
(ch) => `⟦${names[ch]}⟧`,
|
||||
);
|
||||
const encode = (text) =>
|
||||
String(text).replace(
|
||||
/⟦([^⟦⟧]+)⟧/g,
|
||||
|
||||
Reference in New Issue
Block a user