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 }; })();