- 기성고 화면을 native-progress-view.js 로 옮김 - 코드·레벨·사정금액·노재경 구분(비목순서·실적)·비고란·집계공종 진하게·수량/금액 0 숨김·품명정렬·두줄·바탕색 반영 - Enter 뒤 셀 이동 방향, 취소면 연 때 값으로 되돌림 - 주 메뉴 환경설정은 기성 화면에서 이 창을 엶 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
258 lines
10 KiB
JavaScript
258 lines
10 KiB
JavaScript
// 원본 기성 원가계산서(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) =>
|
|
`<button data-action="${action}" ${locked ? "disabled" : ""}>${label}</button>`;
|
|
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", "되살리기"),
|
|
'<button data-action="accom-option" title="Ctrl+U">환경 설정</button>',
|
|
`<label><input type="checkbox" data-pw-sname ${lockFormula ? "checked" : ""}> 산출근거 약어로 표시</label>`,
|
|
round.wonga ? "<span>회차 서식 사용 중</span>" : "",
|
|
round.wongaTotalDigits ? `<span>역산 ${round.wongaTotalDigits}자리</span>` : "",
|
|
].join("");
|
|
const input = (i, name, value, disabled) =>
|
|
`<input data-pw-field="${name}" data-i="${i}" value="${esc(value ?? "")}" ${locked || disabled ? "disabled" : ""}${name === "Name" || name === "VSusic" ? "" : ' size="8"'}>`;
|
|
const body = (output.costs || [])
|
|
.map((row, i) => {
|
|
const tpl = list[i] || {};
|
|
return `<tr><td><input type="radio" name="pw-row" data-pw-pick="${i}" ${pick === i ? "checked" : ""}></td><td>${input(i, "Gubun", tpl.Gubun, false)}</td><td>${input(i, "Name", tpl.Name, false)}</td><td>${input(i, "Youl", tpl.Youl, false)}</td><td>${input(i, "VSusic", tpl.VSusic, lockFormula)}</td><td>${num(tpl.Diget) ? `${tpl.Diget}/${num(tpl.DigetIndex)}` : ""}</td><td>${number(row.ContractCost)}</td><td>${number(row.JunCost)}</td><td>${number(row.GumCost)}</td><td>${number(row.SaCost)}</td><td>${number(row.TotalCost)}</td></tr>`;
|
|
})
|
|
.join("");
|
|
return `<h4>기성 원가계산서</h4><div class="business-tools">${tools}</div><table class="sheet"><thead><tr><th></th><th>기호</th><th>원가 비목</th><th>요율</th><th>산출근거</th><th>자리</th><th>도급금액</th><th>전회</th><th>금회</th><th>사정</th><th>누계</th></tr></thead><tbody>${body}</tbody></table>`;
|
|
}
|
|
|
|
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 };
|
|
})();
|