const EstExtra = (() => { function field(section, index, key, value, type = "number") { return ``; } function button(action, text, extra = "") { return ``; } function table(headers, rows) { return `
| ${h} | `).join("")}
|---|
| ${cell} | `).join("")}
${note}
` : ""}`; } function render(state) { if (state.view === "schedule") { EstSchedule.render(); return; } const { project, view } = state, { esc, number, byId } = EstApp; let html = ""; if (view === "quantity") { if (project.quantitySheets?.length && state.quantitySheet !== "manual") { EstQuantity.render(state); return; } const values = Object.create(null), rows = project.quantities.map((row, index) => { let result, error = ""; try { result = EstMath.floating( EstExpression.evaluate(row.formula || "0", (key) => values[key]), row.digits ?? 2, 1, ); values[row.Code] = result; } catch (e) { error = e.message; } return [ field("quantities", index, "Code", row.Code, "text"), field("quantities", index, "Name", row.Name, "text"), field("quantities", index, "TargetCode", row.TargetCode, "text"), field("quantities", index, "formula", row.formula, "text"), field("quantities", index, "digits", row.digits ?? 2), esc(error || number(result)), button("delete-extra", "삭제", `data-section="quantities" data-row="${index}"`), ]; }); html = EstQuantity.selector(state) + box( "수량산출서", button("add-quantity", "산출 항목 추가") + button("apply-quantity", "내역에 수량 반영"), table(["산출코드", "품명", "연결 내역코드", "수식", "소수자리", "결과", ""], rows), "앞선 산출 항목을 [산출코드]로 참조합니다. 내역 반영은 연결된 코드의 산출값을 합산하여 수량에 반영합니다.", ); } else if (view === "change") { const changes = EstBusiness.changes(project); html = box( "설계변경", button("capture-baseline", "현재 내역을 당초로 확정") + button("load-contract", "당초·계약 내역 불러오기") + button("recalculate-original", "당초 단가 재계산") + button("bid-rates", "낙찰률 적용"), table( ["구분", "코드", "품명", "당초 수량", "변경 수량", "당초 금액", "변경 금액", "증감"], changes .filter((row) => row.Code) .map((row) => [ esc({ new: "신규", deleted: "삭제", changed: "변경", unchanged: "동일" }[row.status]), esc(row.Code), esc(row.Name), number(row.originalGN), number(row.GN), number(row.originalTC), number(row.TC), number(row.difference), ]), ), project.baseline ? `당초: ${esc(project.baseline.name)}. 당초 자료는 별도 사본으로 보존합니다.` : changes.some((row) => row.originalSource === "file") ? "원본 파일에 저장된 당초 수량·금액과 비교합니다." : "현재 작업을 저장한 뒤 당초 내역을 확정하세요.", ); } else if (view === "progress") { state.roundId ||= project.progress[0]?.id; const round = project.progress.find((r) => r.id === state.roundId); let body = '회차를 추가하세요.
', tools = button("add-round", "기성 회차 추가"); if (round) { tools += `` + button( round.locked ? "unlock-round" : "lock-round", round.locked ? "확정 해제(이후 포함)" : "회차 확정·저장", ) + button("fill-progress", "전량·비율 일괄 입력") + button("rename-round", "회차 이름") + button("delete-round", "회차 삭제") + `` + ``; let output; try { output = round.locked && round.result ? round.result : EstBusiness.progress(project, round.id); } catch (e) { output = { rows: [], errors: [e.message] }; } state.business = output; body = table( [ "코드", "공종", "도급수량", "전회수량", "금회 입력", "입력방법", "사정 입력", "입력방법", "금회금액", "사정금액", "누계수량", "누계금액", ], output.rows .map((row, index) => ({ row, index })) .filter( ({ row }) => !EstMath.num(row.Level) && (row.Code || row.Name || [54, 55, 57, 58, 62, 67].includes(Number(row.LinkIndex))), ) .map(({ row, index }) => { const editable = ![54, 55, 58, 59, 62, 67].includes(Number(row.LinkIndex)); const key = EstBusiness.rowKey(row, index), input = round.inputs?.[key] || {}; const edit = (prefix) => `` + (input[prefix + "Mode"] === "components" ? EstMath.components .map( (field, i) => ``, ) .join("") : ""); const method = (prefix) => !editable ? "자동계산" : ``; return [ esc(row.Code), esc(row.Name), number(row.GN), number(row.JunGN), edit("Gum"), method("Gum"), edit("Sa"), method("Sa"), number(row.GumTC), number(row.SaTC), number(row.TotalGN), number(row.TotalTC), ]; }), ) + `${esc((output.errors || []).join(" / "))} 금회 도급액 ${number(output.total)} · 기성비율 ${number(output.ratio)}%
`; body += table( ["원가 비목", "도급금액", "전회", "금회", "사정", "누계"], (output.costs || []).map((row) => [ esc(row.Name || row.Gubun), number(row.ContractCost), number(row.JunCost), number(row.GumCost), number(row.SaCost), number(row.TotalCost), ]), ); } html = box( "기성고", tools, body, "전회는 직전 확정 회차 누계를 읽습니다. 금회·사정은 수량·금액·비율 중 하나를 입력해 계산합니다.", ); } else if (view === "escalation") { const output = EstBusiness.escalation(project); state.business = output; const comp = state.escalationComponent || "NU"; const choices = EstMath.units .map( (key, i) => ``, ) .join(""); const rows = project.tables.EstMainDat.map((row, index) => ({ row, index })) .filter( ({ row }) => (row.Code || row.Name) && !EstMath.num(row.Level) && ![54, 55, 58, 59].includes(Number(row.LinkIndex)), ) .map(({ row, index }) => { const old = project.baseline?.tables.EstMainDat.find( (r, i) => EstBusiness.rowKey(r, i) === EstBusiness.rowKey(row, index), ) || row, result = output.rows[index]; return [ esc(row.Code), esc(row.Name), EstViews.cell("EstMainDat", index, `A${comp}`, row[`A${comp}`]), number(project.baseline ? old[comp] : (row[comp + "_"] ?? row[comp])), EstViews.cell("EstMainDat", index, comp, row[comp]), number(result[`D${comp}`]), number(result[`E${comp}`]), number(result[`F${comp}`]), EstViews.cell("EstMainDat", index, "EscPlanned", row.EscPlanned || 0), EstViews.cell("EstMainDat", index, "EscActual", row.EscActual || 0), EstViews.cell("EstMainDat", index, "EscProgress", row.EscProgress || 0), number(result.EscExcluded), number(result.EscApplied), ``, EstViews.cell("EstMainDat", index, "EscManualQuantity", row.EscManualQuantity ?? 0), EstViews.cell("EstMainDat", index, "EscConstruction", row.EscConstruction ?? 0), EstViews.cell("EstMainDat", index, "EscPS", row.EscPS ?? 0), EstViews.cell("EstMainDat", index, "EscBefore", row.EscBefore ?? 0), ]; }); html = box( "물가변동 품목조정", `` + button("capture-bid", "현재 단가를 입찰단가로 기록") + button("load-bid", "입찰단가 불러오기") + button("load-contract", "계약 내역 불러오기") + button("load-progress-exclusion", "기성 누계수량 불러오기") + button("capture-baseline", "계약 내역 확정") + ``, table( [ "코드", "품명", "입찰 당시", "계약 단가", "변동 당시", "등락률 %", "등락폭", "조정단가", "예정공정수량", "실행공정수량", "기성수량", "적용제외", "적용수량", "제외 자동", "수동 제외수량", "시공중 수량(참고)", "PS 수량(참고)", "선시공 수량(참고)", ], rows, ), `조사 도급액 ${number(output.designTotal)} · 계약 도급액 ${number(output.contractTotal)} · 조정 도급액 ${number(output.adjustedTotal)} · 적용제외(간접비 포함) ${number(output.excludedTotal)} · 적용대가 ${number(output.applied)} · 조정률 ${number(output.rate)}% · 조정금액 ${number(output.adjustment)}. 계약·입찰 파일은 동일 행 순서로 불러옵니다. ${esc(output.errors.join(" / "))}`, ); } else if (view === "annual") { const output = EstBusiness.annual(project, false); state.business = output; html = box( "연차공사", button("add-annual", "차수 추가"), table( ["차수", "수량·금액 배분 수식", "순공사비", "도급액", "작업", ""], project.annual.map((year, index) => [ field("annual", index, "name", year.name, "text"), field("annual", index, "formula", year.formula, "text"), number(output.results[index]?.project.calculation.direct.TC), number(output.results[index]?.project.calculation.total), button("open-annual", "차수 내역 열기", `data-row="${index}"`), button("delete-extra", "삭제", `data-section="annual" data-row="${index}"`), ]), ), `수식 예: [기준]*0.5, [기준]-[1차]. 같은 수식을 수량과 각 성분 금액에 따로 적용하고 금액은 원 미만 절사합니다. ${esc(output.errors.join(" / "))}`, ); } else if (view === "resources") { const output = EstResources.calculate(project); html = box( "동원인원·자재·장비", "", table( ["종류", "코드", "품명", "규격", "단위", "소요량 / 환산시간", "금액"], output.rows.map((row) => [ esc({ 5: "자재", 6: "노임", 21: "표준", 101: "견적", 105: "기계" }[row.kind]), esc(row.Code), esc(row.Name), esc(row.Size), esc(row.Unite), number(row.quantity), number(row.amount), ]), ), esc(output.errors.join(" / ")), ); } else if (view === "drawing") { const rows = project.tables.CadMainDat || []; html = box( "원본 도면·문서 자료", "", table( ["번호", "코드", "이름", "보존 필드"], rows.map((row, index) => [ index + 1, esc(row.Code), esc(row.Name), esc( Object.keys(row) .filter((key) => row[key]?.binary) .join(", "), ), ]), ), "가져온 도면 바이너리는 프로젝트에 보존됩니다. VEC0650/CAD 편집·출력의 재현 근거는 현재 자료에 없어 동작을 구현하지 않았습니다.", ); } byId("work-content").innerHTML = html; } return { render }; })();