221 lines
8.0 KiB
JavaScript
221 lines
8.0 KiB
JavaScript
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 };
|
||
})();
|