Files
Aislo/tools/est-test/native-resources.js

221 lines
8.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const EstResources = (() => {
const { num, components } = EstMath;
function calculate(input, byWork = false) {
const project = EstNative.calculate({
...structuredClone(input),
options: { ...input.options, ResourceTracing: true },
});
const tables = project.tables,
heads = EstNative.groups(tables.HeadMainDat),
costs = EstNative.groups(tables.CostMainDat),
output = new Map(),
errors = [...project.calculation.errors];
let work = {},
workKey = "";
function record(kind, code, quantity, amount = null) {
const source = (tables[EstNative.sourceTables[kind]] || []).find((row) => row.Code === code);
if (!source) {
errors.push(`동원 원천 없음: ${kind}/${code}`);
return;
}
const id = `${workKey}${kind}:${code}`;
const row = output.get(id) || {
...work,
Code: code,
Name: source.Name,
Size: source.Size,
Unite: source.Unite,
kind,
quantity: 0,
amount: 0,
amountForHours: 0,
};
row.quantity += quantity;
row.amount += amount ?? num(source.TC) * quantity;
if (kind === 105 && quantity === 0 && amount !== null) row.amountForHours += amount;
output.set(id, row);
}
function bidComponents(row, source) {
const classified = [1, 2, 3].includes(num(row.SCheck));
const whole = row.ApplyNacChal && num(source?.TC) ? num(row.TU) / num(source.TC) : 1;
return Object.fromEntries(
components.map((key, index) => [
key,
classified
? whole
: row.ApplyNacChal && num(source?.[key])
? num(row[EstMath.units[index]]) / num(source[key])
: 1,
]),
);
}
function weightedRate(source, rates) {
return num(source?.TC)
? components.reduce((amount, key) => amount + num(source[key]) * rates[key], 0) /
num(source.TC)
: 1;
}
function money(row, multiplier, rates) {
const labels = {
NC: [6, "LZZZZZZZZZZ", "노무비"],
GC: [5, "MZZZZZZZZZZ", "재료비"],
JC: [105, "EZZZZZZZZZZ", "경비"],
SC: [21, "SZZZZZZZZZZ", "표준시장단가"],
};
for (const key of components) {
const amount = num(row[key]) * multiplier * rates[key];
if (!amount) continue;
const [kind, Code, Name] = labels[key],
id = `${workKey}${kind}:${Code}`;
const item = output.get(id) || {
...work,
Code,
Name,
Size: "금액 입력분",
Unite: key === "JC" ? "제외금액" : "원",
kind,
quantity: 0,
amount: 0,
amountForHours: 0,
};
item.amount += amount;
if (key !== "JC") item.quantity += amount;
output.set(id, item);
}
}
function expand(kind, code, multiplier, path, componentRates = null) {
const rates = componentRates || Object.fromEntries(components.map((key) => [key, 1]));
const id = `${kind}:${code}`;
if (path.includes(id)) {
errors.push(`동원 순환 참조: ${id}`);
return;
}
if (kind === 104) {
const group = costs.get(code);
if (!group) {
errors.push(`산근 없음: ${code}`);
return;
}
for (const row of group.rows) {
if (!row.ResourceCode || row.ResourceFactor == null) {
const key = { [-1]: "NC", [-2]: "GC", [-3]: "JC", [-4]: "SC" }[num(row.SCheck)];
if (key && !String(row.LineData || "").includes("\x1a") && num(row.LinkIndex) !== 61)
money({ [key]: num(row.TC) }, multiplier, rates);
continue;
}
const source = tables[EstNative.sourceTables[num(row.ResourceKind)]]?.find(
(item) => item.Code === row.ResourceCode,
);
const directKey = row.ResourceComponent;
const rate = directKey ? rates[directKey] : weightedRate(source, rates);
if (!rate) continue;
if ([103, 104].includes(num(row.ResourceKind)))
expand(
num(row.ResourceKind),
row.ResourceCode,
multiplier * num(row.ResourceFactor),
[...path, id],
Object.fromEntries(
components.map((key) => [
key,
!row.ResourceComponent || row.ResourceComponent === key ? rates[key] : 0,
]),
),
);
else if (num(row.ResourceKind) === 105)
record(105, row.ResourceCode, 0, multiplier * num(row.TC) * rate);
else
record(
num(row.ResourceKind),
row.ResourceCode,
multiplier * num(row.ResourceFactor) * rate,
multiplier * num(row.TC) * rate,
);
}
return;
}
if (kind === 103) {
const group = heads.get(code);
if (!group) {
errors.push(`일위 없음: ${code}`);
return;
}
let rate = 1;
for (const row of group.rows.slice(1)) {
if (num(row.LinkIndex) === 52) {
rate *= num(row.GN) / 100;
break;
}
if (num(row.LinkIndex) === 68)
rate *= num(row.GN) / (["%", "%"].includes(row.Unite) ? 100 : 1);
}
for (const row of group.rows.slice(1)) {
if (num(row.CalcIndex) >= 2 || [51, 52, 68].includes(num(row.LinkIndex))) continue;
const source = tables[EstNative.sourceTables[num(row.LinkIndex)]]?.find(
(r) => r.Code === row.Code,
);
const bid = bidComponents(row, source);
const classified = { 1: "GC", 2: "NC", 3: "JC" }[num(row.SCheck)];
const childRates = Object.fromEntries(
components.map((key) => [
key,
bid[key] * (classified ? rates[classified] : rates[key]),
]),
);
const quantity =
(multiplier * num(row.GN) * rate) / (["%", "%"].includes(row.Unite) ? 100 : 1);
if ([103, 104].includes(num(row.LinkIndex)))
expand(num(row.LinkIndex), row.Code, quantity, [...path, id], childRates);
else if (EstNative.sourceTables[num(row.LinkIndex)]) {
const count = quantity * weightedRate(source, childRates);
const amount =
multiplier *
rate *
components.reduce((amount, key) => amount + num(row[key]) * rates[key], 0);
if (count || amount) record(num(row.LinkIndex), row.Code, count, amount);
} else money(row, multiplier * rate, rates);
}
return;
}
const source = tables[EstNative.sourceTables[kind]]?.find((row) => row.Code === code);
record(kind, code, multiplier * weightedRate(source, rates));
}
for (const [index, row] of tables.EstMainDat.entries()) {
if (num(row.LinkIndex) === 54) break;
if (
num(row.Level) > 0 ||
num(row.CalcIndex) >= 2 ||
[51, 52, 68].includes(num(row.LinkIndex))
)
continue;
if (byWork) {
workKey = `${index}:`;
work = { WorkCode: row.Code || "", WorkName: row.Name || "" };
}
if (!EstNative.sourceTables[num(row.LinkIndex)]) {
money(row, 1, { NC: 1, GC: 1, JC: 1, SC: 1 });
continue;
}
const source = tables[EstNative.sourceTables[num(row.LinkIndex)]]?.find(
(r) => r.Code === row.Code,
);
expand(num(row.LinkIndex), row.Code, num(row.GN), [], bidComponents(row, source));
}
const rows = [...output.values()];
for (const row of rows)
if (row.kind === 105 && row.amountForHours) {
const price = num(tables.MachTitleDat.find((r) => r.Code === row.Code)?.TC);
if (price) {
row.quantity += row.amountForHours / price;
row.Unite = "HR";
} else {
row.Unite = "제외금액";
row.quantity = 0;
}
}
rows.forEach((row) => delete row.amountForHours);
return { rows, errors: [...new Set(errors)] };
}
return { calculate };
})();