- 엑셀형 산출서 BIFF 수식 해석·다시 계산·수식줄 편집·4방향 합·수량으로 지정(가져올 때 값이 같은 합계 셀 추정 연결), 셀 편집 시 ESTX 저장 거부 - 원가 행 계산을 원본 c9d278 끝부분대로: 소수 4자리 반올림 뒤 원 미만 절사, Diget>0 행 자리맞춤(0x11293e8) - 원가계산서 「선택 행 자릿수 설정」을 원본 선택항목 절하하기 대화로(도급액 행 거부, 입력 행 원금액 기준) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
138 lines
6.6 KiB
JavaScript
138 lines
6.6 KiB
JavaScript
// 원본 엑셀형 수량산출서(TFRMMULEXCEL) 웹 편집 화면 — 셀 고르기·수식줄 편집·다시 계산, 셀 합(우·상·좌·하 btnSum*),
|
|
// 수량으로 지정(popSetMullang)·지정 해제(popSetFree) = 셀 ↔ 수량 항목 연결(sheet.excelLinks). 항목 수량 = 연결 셀 값(산출서 자리수).
|
|
// 원본 파일 속 셀↔항목 대응은 찾지 못해, 가져올 때 값이 같은 셀 하나(합계 수식·아래쪽 우선)를 「추정」 연결로 둔다.
|
|
// 편집한 엑셀형 산출서는 .xls 로 다시 쓰지 못하므로 ESTX 저장을 막는다(sheet.excelEdited).
|
|
const EstExcelView = (() => {
|
|
const L = (n) => EstExcelSheet.letters(n);
|
|
|
|
function render(sheet, state) {
|
|
if (!sheet?.excel?.length) return "";
|
|
const { esc, number } = EstApp,
|
|
sel = state.excelCell?.sheet === sheet.Code ? state.excelCell : null,
|
|
links = sheet.excelLinks || {};
|
|
const linked = new Map(
|
|
Object.entries(links).map(([code, l]) => [`${l.n},${l.row},${l.col}`, { code, ...l }]),
|
|
);
|
|
return sheet.excel
|
|
.map(({ name, cells }, n) => {
|
|
const keys = Object.keys(cells).map((k) => k.split(",").map(Number));
|
|
const rows = Math.max(20, ...keys.map(([r]) => r + 2)),
|
|
cols = Math.min(40, Math.max(8, ...keys.map(([, c]) => c + 2)));
|
|
const raw = sel && sel.n === n ? cells[`${sel.row},${sel.col}`] : null;
|
|
const bar =
|
|
sel && sel.n === n
|
|
? `${L(sel.col)}${sel.row + 1} <input data-excel-bar style="width:50%" value="${esc(raw ? (typeof raw.formula === "string" ? `=${raw.formula}` : (raw.value ?? "")) : "")}" aria-label="수식줄">`
|
|
: "셀을 누르면 수식줄에서 고칩니다.";
|
|
const items = (sheet.items || [])
|
|
.map(
|
|
(i) =>
|
|
`<option value="${esc(i.Code)}">${esc(i.Code)} ${esc(i.Name ?? "")} ${esc(i.Size ?? "")}</option>`,
|
|
)
|
|
.join("");
|
|
const tools =
|
|
sel && sel.n === n
|
|
? ` <button data-action="excel-sum" data-dir="right">우측 합</button><button data-action="excel-sum" data-dir="up">위 합</button><button data-action="excel-sum" data-dir="left">좌측 합</button><button data-action="excel-sum" data-dir="down">아래 합</button> <select data-excel-item aria-label="수량 항목">${items}</select><button data-action="excel-link">수량으로 지정</button><button data-action="excel-unlink">지정 해제</button>`
|
|
: "";
|
|
const body = Array.from({ length: rows }, (_, r) => {
|
|
const tds = Array.from({ length: cols }, (_, c) => {
|
|
const cell = cells[`${r},${c}`],
|
|
link = linked.get(`${n},${r},${c}`),
|
|
chosen = sel && sel.n === n && sel.row === r && sel.col === c;
|
|
const value =
|
|
cell == null
|
|
? ""
|
|
: typeof cell.value === "number"
|
|
? number(cell.value)
|
|
: esc(cell.value);
|
|
const style = [
|
|
cell?.formula ? "font-weight:bold" : "",
|
|
link ? "background:#e6f4ea" : "",
|
|
chosen ? "outline:2px solid #316ea5" : "",
|
|
]
|
|
.filter(Boolean)
|
|
.join(";");
|
|
const title = [
|
|
typeof cell?.formula === "string" ? `=${cell.formula}` : "",
|
|
link ? `수량: ${link.code}${link.inferred ? " (추정 연결)" : ""}` : "",
|
|
]
|
|
.filter(Boolean)
|
|
.join(" · ");
|
|
return `<td data-excel-pick="${n},${r},${c}"${style ? ` style="${style}"` : ""}${title ? ` title="${esc(title)}"` : ""}>${value}</td>`;
|
|
}).join("");
|
|
return `<tr><th>${r + 1}</th>${tds}</tr>`;
|
|
}).join("");
|
|
return `<h4>엑셀형 산출서 · ${esc(name)}</h4><div class="business-tools">${bar}${tools}</div><div style="overflow:auto;max-height:420px"><table class="sheet" style="font-size:11px"><thead><tr><th></th>${Array.from({ length: cols }, (_, c) => `<th>${L(c)}</th>`).join("")}</tr></thead><tbody>${body}</tbody></table></div>`;
|
|
})
|
|
.join("");
|
|
}
|
|
|
|
function sheetOf(state) {
|
|
return state.project.quantitySheets?.find((s) => s.Code === state.quantitySheet);
|
|
}
|
|
function setCell(sheet, n, row, col, text) {
|
|
const cells = sheet.excel[n].cells,
|
|
k = `${row},${col}`,
|
|
t = String(text ?? "");
|
|
if (t === "") delete cells[k];
|
|
else if (t.startsWith("=")) cells[k] = { value: 0, formula: t.slice(1) };
|
|
else {
|
|
const v = Number(t.replaceAll(",", ""));
|
|
cells[k] = { value: t.trim() !== "" && Number.isFinite(v) ? v : t };
|
|
}
|
|
sheet.excelEdited = true;
|
|
return EstExcelSheet.recalc(cells);
|
|
}
|
|
|
|
function install() {
|
|
document.addEventListener("click", (event) => {
|
|
const td = event.target.closest("[data-excel-pick]");
|
|
if (!td) return;
|
|
const [n, row, col] = td.dataset.excelPick.split(",").map(Number);
|
|
EstApp.state.excelCell = { sheet: EstApp.state.quantitySheet, n, row, col };
|
|
EstApp.render();
|
|
});
|
|
document.addEventListener("change", (event) => {
|
|
if (event.target.dataset.excelBar === undefined) return;
|
|
const state = EstApp.state,
|
|
sel = state.excelCell,
|
|
value = event.target.value;
|
|
let errors = [];
|
|
EstApp.change(() => {
|
|
errors = setCell(sheetOf(state), sel.n, sel.row, sel.col, value);
|
|
}, false);
|
|
if (errors.length) state.notice = errors.slice(0, 3).join(" / ");
|
|
});
|
|
}
|
|
|
|
function handle(name, state, node) {
|
|
if (!["excel-sum", "excel-link", "excel-unlink"].includes(name)) return false;
|
|
const sheet = sheetOf(state),
|
|
sel = state.excelCell;
|
|
if (!sheet?.excel || !sel) throw Error("엑셀형 산출서에서 셀을 고르세요.");
|
|
if (name === "excel-sum") {
|
|
const formula = EstExcelSheet.sumFormula(
|
|
sheet.excel[sel.n].cells,
|
|
sel.row,
|
|
sel.col,
|
|
node.dataset.dir,
|
|
);
|
|
EstApp.change(() => setCell(sheet, sel.n, sel.row, sel.col, `=${formula}`), false);
|
|
} else {
|
|
const code = document.querySelector("[data-excel-item]")?.value;
|
|
EstApp.change(() => {
|
|
sheet.excelLinks ||= {};
|
|
for (const [k, l] of Object.entries(sheet.excelLinks))
|
|
if (l.n === sel.n && l.row === sel.row && l.col === sel.col) delete sheet.excelLinks[k];
|
|
if (name === "excel-link") {
|
|
if (!code) throw Error("수량 항목을 고르세요.");
|
|
sheet.excelLinks[code] = { n: sel.n, row: sel.row, col: sel.col };
|
|
}
|
|
}, false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (typeof document !== "undefined" && document.addEventListener) install();
|
|
return { render, setCell, handle };
|
|
})();
|