94 lines
3.7 KiB
JavaScript
94 lines
3.7 KiB
JavaScript
// REA c9f8ec / c9d278 / c9f0e4: 수식 참조, 원 미만 절사, 도급액 고정 반복.
|
|
const EstWonga = (() => {
|
|
const { num } = EstMath;
|
|
const compact = (text) => String(text || "").replace(/\s/g, "");
|
|
function calculate(input, options = {}, bases = {}) {
|
|
const rows = structuredClone(input),
|
|
errors = [],
|
|
adjustments = [];
|
|
const key = (row) =>
|
|
String(options.VerFormat === false ? row.NumStr || row.Gubun : row.Gubun || row.NumStr || "");
|
|
const keys = new Map(rows.map((row, index) => [key(row), index]));
|
|
keys.delete("");
|
|
rows.forEach((row, index) => {
|
|
if (compact(row.Name)) keys.set(compact(row.Name), index);
|
|
});
|
|
const overrides = new Map();
|
|
function pass() {
|
|
const done = new Set(),
|
|
active = new Set();
|
|
function resolve(name) {
|
|
if (Object.hasOwn(bases, name)) return bases[name];
|
|
const index = keys.get(name) ?? keys.get(compact(name));
|
|
return index === undefined ? undefined : evaluate(index);
|
|
}
|
|
function range(start, end) {
|
|
let a = keys.get(start),
|
|
b = keys.get(end);
|
|
if (a === undefined || b === undefined) throw Error(`범위 참조 없음: ${start}:${end}`);
|
|
if (a > b) [a, b] = [b, a];
|
|
let sum = 0;
|
|
for (let i = a; i <= b; i++)
|
|
if ([0, 1, 4].includes(num(rows[i].CalcIndex))) sum += evaluate(i);
|
|
return sum;
|
|
}
|
|
function evaluate(index) {
|
|
const row = rows[index];
|
|
if (overrides.has(index)) return (row.Cost = overrides.get(index));
|
|
if (done.has(index)) return num(row.Cost);
|
|
if (active.has(index)) throw Error(`원가 수식 순환 참조: ${key(row)}`);
|
|
active.add(index);
|
|
if (row.BaseField && Object.hasOwn(bases, row.BaseField)) row.Cost = bases[row.BaseField];
|
|
else if (row.VSusic && ![1, 3].includes(num(row.CalcIndex)))
|
|
row.Cost = Math.trunc(
|
|
EstExpression.evaluate(row.VSusic, resolve, { numericReferences: true, range }),
|
|
);
|
|
else row.Cost = num(row.Cost);
|
|
active.delete(index);
|
|
done.add(index);
|
|
return row.Cost;
|
|
}
|
|
for (let i = 0; i < rows.length; i++) evaluate(i);
|
|
}
|
|
const targetIndex = rows.findIndex((row) => num(row.LinkIndex) === 55);
|
|
const target = targetIndex >= 0 ? num(rows[targetIndex].Cost) : 0;
|
|
try {
|
|
pass();
|
|
if (options.TotalFix && targetIndex >= 0) {
|
|
const profit = rows.findIndex(
|
|
(row) => compact(row.Name).includes("이윤") && ![1, 3].includes(num(row.CalcIndex)),
|
|
);
|
|
const vat = rows.findIndex((row) => compact(row.Name).includes("부가"));
|
|
if (profit < 0) throw Error("도급액 고정: 조정할 이윤 행이 없습니다.");
|
|
for (let iteration = 0; iteration < 100; iteration++) {
|
|
const delta = num(rows[targetIndex].Cost) - target;
|
|
adjustments.push({
|
|
iteration,
|
|
profit: num(rows[profit].Cost),
|
|
total: num(rows[targetIndex].Cost),
|
|
difference: delta,
|
|
});
|
|
if (delta === 0) break;
|
|
overrides.set(profit, num(rows[profit].Cost) - delta);
|
|
pass();
|
|
if (iteration === 99 && vat >= 0) {
|
|
overrides.set(vat, num(rows[vat].Cost) + target - num(rows[targetIndex].Cost));
|
|
pass();
|
|
}
|
|
}
|
|
if (num(rows[targetIndex].Cost) !== target)
|
|
throw Error("도급액 고정 계산이 수렴하지 않았습니다.");
|
|
}
|
|
} catch (error) {
|
|
errors.push(error.message);
|
|
}
|
|
return {
|
|
rows,
|
|
errors,
|
|
adjustments,
|
|
total: targetIndex >= 0 ? num(rows[targetIndex].Cost) : num(rows.at(-1)?.Cost),
|
|
};
|
|
}
|
|
return { calculate };
|
|
})();
|