// 원본 기성 원가계산서(TfrmWongaAccomH 0xd48408 / V 0xd46914) 편집 — 회차별 원가 서식 사본(round.wonga)에만 적용. // 한줄 삽입·추가(0xd49758: 새 행 Index 0·CalcIndex 0·Checked 끔, 앞 행 끝줄 표지 이어받기), 한줄 삭제(확인 뒤, 재계산 없음), // 내용 삭제(LinkIndex·Cost·Biul·Youl·VSusic·Diget 비움), 선택항목 절하(0xd094dc, 기성은 도급액 행도 허용), // 선택데이타 절하(popupTrunc: 직전 자리수를 그대로, CalcBase 행 제외), 공사비역산 자리수(목표 = 10^n 미만 절하 → 이윤 역산), // 초기화(사본 지움 = 도급 원가 서식), 되살리기(직전 조작 전), 산출근거 약어로 표시(A8: 수식칸 편집 잠금만). // 원본의 번호·참조 재작성(0xc97398·0xc971e8)은 하지 않는다 — 웹 수식은 Gubun·이름으로 찾으므로 새 행은 기호를 직접 적는다. const EstProgressWonga = (() => { const num = (value) => Number(value) || 0; function template(project, round) { return round.contract?.wonga || project.tables.Wonga || []; } function edit(project, round, mutate) { if (round.locked) throw Error("확정한 회차는 고칠 수 없습니다."); round.wongaUndo = round.wonga ? structuredClone(round.wonga) : null; round.wonga ||= structuredClone(template(project, round)); return mutate(round.wonga); } function blank(list, at) { const row = { Index: 0, Gubun: "", Name: "", Youl: "", VSusic: "", Cost: 0, CalcIndex: 0 }; const prev = list[at - 1], end = num(prev?.EndLineIndex); row.EndLineIndex = end === 1 ? 1 : end === 3 ? 3 : 0; if (end === 3) prev.EndLineIndex = 0; row.Checked = false; return row; } const ops = { insert(project, round, at) { edit(project, round, (list) => list.splice(at, 0, blank(list, at))); }, append(project, round) { edit(project, round, (list) => list.push(blank(list, list.length))); }, remove(project, round, at) { edit(project, round, (list) => { if (!list[at]) throw Error("삭제할 줄을 고르세요."); list.splice(at, 1); }); }, clear(project, round, at) { edit(project, round, (list) => { if (!list[at]) throw Error("내용을 지울 줄을 고르세요."); Object.assign(list[at], { LinkIndex: 0, Cost: 0, Biul: "", Youl: "", VSusic: "", Diget: 0, Checked: false, }); }); }, position(project, round, at, method, digits) { edit(project, round, (list) => EstWongaTools.position( { tables: { Wonga: list }, options: project.options }, [at], method, digits, true, ), ); }, trunc(project, round, at) { edit(project, round, (list) => { if (!list[at]) throw Error("절하할 줄을 고르세요."); if (!list[at].CalcBase) list[at].Diget = num(project.options.WongaLastDiget); }); }, total(project, round, digits) { if (round.locked) throw Error("확정한 회차는 고칠 수 없습니다."); if (!Number.isInteger(digits) || digits < 0 || digits > 9) throw Error("자리수는 0~9 사이로 입력하세요."); if (digits) round.wongaTotalDigits = digits; else delete round.wongaTotalDigits; }, init(project, round) { if (round.locked) throw Error("확정한 회차는 고칠 수 없습니다."); round.wongaUndo = round.wonga ? structuredClone(round.wonga) : null; delete round.wonga; delete round.wongaTotalDigits; }, undo(project, round) { if (round.locked) throw Error("확정한 회차는 고칠 수 없습니다."); if (!("wongaUndo" in round)) throw Error("되살릴 조작이 없습니다."); if (round.wongaUndo) round.wonga = round.wongaUndo; else delete round.wonga; delete round.wongaUndo; }, field(project, round, at, name, value) { edit(project, round, (list) => { if (!list[at] || !["Gubun", "Name", "Youl", "VSusic", "Cost"].includes(name)) throw Error("고칠 칸이 없습니다."); list[at][name] = name === "Cost" ? num(String(value).replaceAll(",", "")) : value; }); }, }; // 화면 function render(state, round, output) { const { esc, number } = EstApp, project = state.project, list = round.wonga || template(project, round), locked = round.locked, lockFormula = project.options.WongaSNameShow, pick = state.progressWongaRow; const b = (action, label) => ``; const tools = [ b("pw-insert", "한줄 삽입"), b("pw-append", "한줄 추가"), b("pw-delete", "한줄 삭제"), b("pw-clear", "내용 삭제"), b("pw-position", "선택항목 절하하기"), b("pw-trunc", "선택데이타 절하하기"), b("pw-total", "공사비역산 자리수 맞추기"), b("pw-init", "원가계산서 초기화"), b("pw-undo", "되살리기"), ``, round.wonga ? "회차 서식 사용 중" : "", round.wongaTotalDigits ? `역산 ${round.wongaTotalDigits}자리` : "", ].join(""); const input = (i, name, value, disabled) => ``; const body = (output.costs || []) .map((row, i) => { const tpl = list[i] || {}; return `${input(i, "Gubun", tpl.Gubun, false)}${input(i, "Name", tpl.Name, false)}${input(i, "Youl", tpl.Youl, false)}${input(i, "VSusic", tpl.VSusic, lockFormula)}${num(tpl.Diget) ? `${tpl.Diget}/${num(tpl.DigetIndex)}` : ""}${number(row.ContractCost)}${number(row.JunCost)}${number(row.GumCost)}${number(row.SaCost)}${number(row.TotalCost)}`; }) .join(""); return `

기성 원가계산서

${tools}
${body}
기호원가 비목요율산출근거자리도급금액전회금회사정누계
`; } function round(state) { return state.project.progress.find((r) => r.id === state.roundId); } function at(state) { const i = state.progressWongaRow; if (!Number.isInteger(i)) throw Error("기성 원가계산서에서 줄을 고르세요."); return i; } const names = new Set([ "pw-insert", "pw-append", "pw-delete", "pw-clear", "pw-position", "pw-trunc", "pw-total", "pw-init", "pw-undo", ]); function handle(name, state) { if (!names.has(name)) return false; const r = round(state); if (!r) throw Error("기성 회차를 고르세요."); const run = (fn) => EstApp.change((project) => fn( project, project.progress.find((x) => x.id === r.id), ), ); if (name === "pw-insert") run((p, x) => ops.insert(p, x, at(state))); else if (name === "pw-append") run((p, x) => ops.append(p, x)); else if (name === "pw-delete") { const i = at(state); if (confirm("현재 줄을 삭제하시겠습니까?")) { run((p, x) => ops.remove(p, x, i)); state.progressWongaRow = undefined; } } else if (name === "pw-clear") run((p, x) => ops.clear(p, x, at(state))); else if (name === "pw-trunc") run((p, x) => ops.trunc(p, x, at(state))); else if (name === "pw-init") { if (confirm("이 회차의 원가계산서를 도급 원가 서식으로 되돌리시겠습니까?")) run(ops.init); } else if (name === "pw-undo") run(ops.undo); else if (name === "pw-position") { const i = at(state); EstActions.form( "선택항목 절하하기", [ { name: "method", label: "처리방식", value: 2, choices: [ [1, "사사오입"], [2, "절하"], [3, "절상"], [0, "초기화"], ], }, { name: "digits", label: "자리 (0 원 · 1 십원 … 6 백만원)", type: "number", value: num(state.project.options.WongaLastDiget), }, ], (values) => run((p, x) => ops.position(p, x, i, Number(values.method), Number(values.digits))), ); } else if (name === "pw-total") EstActions.form( "공사비역산 자리수 맞추기", [ { name: "digits", label: "도급액 절하 자리 (0 해제 · 3 천원 · 4 만원 …)", type: "number", value: r.wongaTotalDigits ?? 3, }, ], (values) => run((p, x) => ops.total(p, x, Number(values.digits))), ); return true; } function install() { document.addEventListener("change", (event) => { const t = event.target, state = EstApp.state; if (t.dataset.pwPick !== undefined) { state.progressWongaRow = Number(t.dataset.pwPick); return; } if (t.dataset.pwSname !== undefined) { EstApp.change((project) => (project.options.WongaSNameShow = t.checked), false); return; } if (t.dataset.pwField === undefined) return; const r = round(state); try { EstApp.change((project) => ops.field( project, project.progress.find((x) => x.id === r.id), Number(t.dataset.i), t.dataset.pwField, t.value, ), ); } catch (error) { state.notice = error.message; EstApp.render(); } }); } if (typeof document !== "undefined" && document.addEventListener) install(); return { template, ops, render, handle }; })();