// 원본 「물가변동 적용대가 분류 내역서」(TfrmAppliedCost — spec_pb1 §5·§6) 화면: 품목조정 표(native-extra.js 에서 옮김), // 「적용대가 보기 변경」(cmStyleToggle 0x10b5918 → e43b48: 단가 밴드를 숨기고 계약·구분별 수량 7종만 — 값 변화 없음), // 「수량불러오기」(cmGNLoad 0x10b57a4 → 10b6630·10b5934·10b617c: 고른 내역서의 행 i 수량 GN 을 이 내역 행 i 의 구분 칸에, // 그 파일 원가 행 i 의 Cost 를 원가 행 i 의 Es*Cost 에 — 위치 짝, 원본 행이 없으면 건너뜀), // 「적용대가집계표」(mnuESWonga → TfrmESPWonga: 원가 행마다 계약금액·적용제외금액·적용대가금액 — 계산은 품목조정과 같은 값). const EstAppliedCost = (() => { const num = (v) => Number(v) || 0; const QTY = ["EscConstruction", "EscPlanned", "EscActual", "EscProgress", "EscPS", "EscBefore"]; const QTY_LABELS = [ "시공중 수량", "예정공정 수량", "실행공정 수량", "기성 수량", "PS 수량", "선시공 수량", ]; const KINDS = [ "계약분 수량", "시공중인공사계약분 수량", "예정공정율분 수량", "실행공정율분 수량", "기성율분 수량", "PS분 수량", "신시공부분 수량", ]; const COST = [ "EsContractCost", "EsSiGongCost", "EsSchduleCost", "EsWorkCost", "EsACCost", "EsPSCost", "EsBeforeCost", ]; function view(state, project, { box, button, table }) { const { esc, number } = EstApp; const output = EstBusiness.escalation(project); state.business = output; const comp = state.escalationComponent || "NU"; const choices = EstMath.units .map( (key, i) => ``, ) .join(""); const page = EstBlockActions.escalationPage(state, project.tables.EstMainDat); const qty = state.escQtyView; const rows = page.rows.map(({ row, index }) => { const result = output.rows[index]; if (qty) return [ esc(row.Code), esc(row.Name), number(num(result.EscApplied) + num(result.EscExcluded)), ...QTY.map((k) => EstViews.cell("EstMainDat", index, k, row[k] ?? 0)), number(result.EscExcluded), number(result.EscApplied), ]; return [ esc(row.Code), esc(row.Name), EstViews.cell("EstMainDat", index, `A${comp}`, row[`A${comp}`]), number(result[`Contract${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), ]; }); return box( "물가변동 품목조정", `` + button("capture-bid", "현재 단가를 입찰단가로 기록") + button("load-bid", "입찰단가 불러오기") + button("load-contract", "계약 내역 불러오기") + button("load-progress-exclusion", "기성 누계수량 불러오기") + button( "applied-style-toggle", qty ? "적용대가 보기 변경(풀기)" : "적용대가 보기 변경", qty ? 'aria-pressed="true"' : "", ) + button("applied-gn-load", "수량불러오기") + button("es-wonga", "적용대가집계표") + button("reset-escalation", "물가변동 입력 초기화") + button("capture-baseline", "계약 내역 확정") + button("escalation-prev", "이전", state.escalationPage === 0 ? "disabled" : "") + `${state.escalationPage + 1} / ${page.pages}쪽 · ${page.total}행` + button( "escalation-next", "다음", state.escalationPage + 1 >= page.pages ? "disabled" : "", ) + ``, table( qty ? ["코드", "품명", "계약 수량", ...QTY_LABELS, "적용제외", "적용수량"] : [ "코드", "품명", "입찰 당시", "계약 단가", "변동 당시", "등락률 %", "등락폭", "조정단가", "예정공정수량", "실행공정수량", "기성수량", "적용제외", "적용수량", "제외 자동", "수동 제외수량", "시공중 수량(참고)", "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(" / "))}`, ); } // 수량불러오기 한 벌 적용: kind 0 = 계약 수량(계약 내역 확정본, 없으면 당초 칸 GN_), 1~6 = 구분 칸 function load(project, kind, source) { const rows = project.tables.EstMainDat, from = source.tables?.EstMainDat || [], base = project.baseline?.tables?.EstMainDat; let count = 0; rows.forEach((row, i) => { const src = from[i]; if (!src) return; if (kind === 0) { if (base?.[i]) base[i].GN = num(src.GN); else row.GN_ = num(src.GN); } else row[QTY[kind - 1]] = num(src.GN); count++; }); const wonga = project.tables.Wonga || [], fromWonga = source.tables?.Wonga || []; wonga.forEach((row, i) => fromWonga[i] && (row[COST[kind]] = num(fromWonga[i].Cost))); return count; } function summary(project) { const out = EstBusiness.escalation(project), wonga = project.tables.Wonga || []; return wonga.map((row, i) => { const contract = row.EsContractCost != null ? num(row.EsContractCost) : num(out.contractWonga?.[i]?.Cost), except = num(out.excludedWonga?.[i]?.Cost); return { row, contract, except, apply: contract - except }; }); } function summaryDialog(state) { const { esc, number } = EstApp, list = summary(state.project); if (!list.length) throw Error("원가계산서가 없습니다."); EstApp.showDialog( "적용대가집계표", `
${list .map( ({ row, contract, except, apply }) => ``, ) .join("")}
비목약어계약금액적용제외금액적용대가금액산출근거
${esc([row.Bimog1, row.Bimog2, row.Name].filter(Boolean).join(" "))}${esc(row.SName ?? "")}${number(contract)}${number(except)}${number(apply)}${esc(row.VSusic ?? "")}
`, ); } async function gnLoad(state) { const kind = await new Promise((resolve) => EstActions.form( "수량불러오기", [ { name: "kind", label: "불러올 수량분", value: "", choices: [["", "선택"], ...KINDS.map((k, i) => [i, k])], }, ], (values) => resolve(values.kind), ), ); if (kind === "" || kind == null) throw Error("불러올 수량분을 선택하세요.."); const file = await new Promise((resolve) => { const picker = document.createElement("input"); picker.type = "file"; picker.accept = ".estx,.json"; picker.addEventListener("change", () => resolve(picker.files[0])); picker.click(); }); if (!file) return; const name = KINDS[Number(kind)]; let source; try { source = /\.estx$/i.test(file.name) ? await EstApp.api(`/api/import?name=${encodeURIComponent(file.name)}`, { method: "POST", body: await file.arrayBuffer(), }) : EstModel.legacy(JSON.parse(await file.text())); } catch { throw Error(`${name} 를 불러오지 못했습니다...`); } let count = 0; EstApp.change((project) => (count = load(project, Number(kind), source))); state.notice = `${name} ${count}행을 불러왔습니다.`; EstApp.render(); } function handle(name, state) { if (name === "applied-style-toggle") { state.escQtyView = !state.escQtyView; EstApp.render(); } else if (name === "applied-gn-load") gnLoad(state).catch((error) => { state.notice = error.message; EstApp.render(); }); else if (name === "es-wonga") summaryDialog(state); else return false; return true; } return { view, load, summary, handle, KINDS }; })();