feat(est-test): 셀복사·셀붙여넣기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 13:14:05 +09:00
co-authored by Claude Opus 5.5
parent b3f5cc2b7b
commit 42bb744455
5 changed files with 58 additions and 2 deletions
+2
View File
@@ -487,6 +487,8 @@ ative-standard.js에 표준시장단가 원천(SilJukDat)과 내역 행(EstMainD
- 남음: 구성비·요율 전환(CalcBase), 약어 표시(SName), 제비율 기준 문서 보기(원본 한글 파일).
- [x] 수량산출서 검색·전체보기(원본 TFRMMULLANG btnSerch·btnAllDisplay — 빈칸·|·괄호 무시 포함 검색) — 고르기 줄을 `native-quantity-sheets.js` 로 옮겨 700줄 제한 유지.
- 검증완료: `test_est_quantity_sheets.py` 검색 시험 포함 수량 시험 22개 통과. ORCA 브라우저에서 「집수」 검색 → 항목에 집수정이 있는 「배수」만·선택 이동, 전체보기 → 3개 복귀, 샘플 복구.
- [x] 셀복사·셀붙여넣기(원본 ActionCellCopy·ActionCellPaste) — 마지막으로 누른 칸의 열을 고른 행들(없으면 그 행)만큼 복사(시스템 클립보드에도 글자로), 다른 칸을 누르고 붙이면 그 행부터 아래로 차례로 넣음(열 달라도 됨, 수치 칸은 수치만, 코드·결과비목 칸 거부).
- 검증완료: 바인딩 시험 통과. ORCA 브라우저에서 자재 품명 2칸 복사 → 6번째 행 규격부터 붙여넣기(경유·휘발유), 코드 칸 거부, 샘플 복구.
- [x] 산출 줄 잘라내기·복사·붙여넣기(원본 TFRMMULWORD popCut·popCopy·popPaste) — 산출 줄마다 복사·잘라내기·아래 붙여넣기. 붙일 때 수량 지정 표지를 떼고 글만(같은 항목이 두 번 잡히지 않게), 수량 지정된 줄은 잘라내기 거부.
- 검증완료: 산출서·수량 흐름·바인딩 시험 22개 통과. ORCA 브라우저에서 표지 있는 줄 복사 → 아래 붙여넣기(표지 없음), 표지 줄 잘라내기 거부, 샘플 복구.
- [x] 수량산출서 엑셀형식 전환(원본 TFRMMULLANG popSetMode — DataKind 로 편집기 교체) — 산출서 화면 「엑셀형식으로」: 엑셀 시트가 없으면 빈 시트를 만들어 엑셀형으로, 「일반형식으로」는 엑셀 내용을 보관한 채 끄고 셀 연결 수량을 쓰지 않음. 바꾼 산출서는 ESTX 저장 거부(JSON).
+4 -1
View File
@@ -59,6 +59,7 @@
<script defer src="native-docs.js"></script>
<script defer src="native-doc-forms.js"></script>
<script defer src="native-block-file.js"></script>
<script defer src="native-cell-clip.js"></script>
<script defer src="native-wonga-tools.js"></script>
<script defer src="native-estimate-print.js"></script>
<script defer src="native-xml-check.js"></script>
@@ -135,7 +136,9 @@
><button data-action="select-all-rows">전체 선택</button
><button data-action="block-del">블럭 삭제</button
><button data-action="block-save-file">블럭 파일로 저장하기</button
><button data-action="block-load-file">블럭 파일에서 붙여넣기</button>
><button data-action="block-load-file">블럭 파일에서 붙여넣기</button
><button data-action="cell-copy">셀복사</button
><button data-action="cell-paste">셀붙여넣기</button>
<hr />
<button data-action="find">찾기·다음찾기(Ctrl+F)</button>
<button data-action="multi-search">통합 검색(저장된 다른 작업)</button>
+2 -1
View File
@@ -630,7 +630,8 @@ const EstBusinessActions = (() => {
(typeof EstDocEditor !== "undefined" && EstDocEditor.handle(name, state, node)) ||
(typeof EstDocForms !== "undefined" && EstDocForms.handle(name, state, node)) ||
(typeof EstBlockFile !== "undefined" && EstBlockFile.handle(name, state)) ||
(typeof EstDisplay !== "undefined" && EstDisplay.handle(name, state))
(typeof EstDisplay !== "undefined" && EstDisplay.handle(name, state)) ||
(typeof EstCellClip !== "undefined" && EstCellClip.handle(name, state))
) {
EstApp.render();
} else if (name === "insert-annual") {
+49
View File
@@ -0,0 +1,49 @@
// 원본 셀복사·셀붙여넣기(TfrmEstMain ActionCellCopy·ActionCellPaste) — 마지막으로 누른 칸의 열을, 고른 행들(없으면 그 행)만큼 복사하고
// 다른 칸을 누른 뒤 그 행부터 아래로 차례로 붙여넣는다(열이 달라도 됨). 코드·결과비목 칸은 연결 처리가 따로 있어 붙여넣기 거부.
const EstCellClip = (() => {
let last = null;
function copy(state) {
if (!last) throw Error("복사할 칸을 먼저 누르세요.");
const rows = state.project.tables[last.table] || [],
picked = [...state.selection].filter((i) => rows[i]).sort((a, b) => a - b),
list = picked.includes(last.row) ? picked : [last.row];
state.cellClip = { field: last.field, values: list.map((i) => rows[i][last.field] ?? null) };
const text = state.cellClip.values
.map((v) => (last.field === "LineData" ? EstText.display(v ?? "") : (v ?? "")))
.join("\n");
navigator.clipboard?.writeText(text).catch(() => {});
state.notice = `${list.length}칸을 복사했습니다.`;
}
function paste(state) {
if (!last) throw Error("붙여넣을 칸을 먼저 누르세요.");
if (!state.cellClip?.values.length) throw Error("복사한 칸이 없습니다.");
if (["Code", "SCheck"].includes(last.field))
throw Error("코드·결과비목 칸은 셀붙여넣기를 쓸 수 없습니다. 칸에서 직접 고치세요.");
const rows = state.project.tables[last.table] || [],
numeric = EstModel.numeric.has(last.field);
const values = state.cellClip.values.map((v) => {
if (!numeric) return v == null ? "" : String(v);
const n = v === "" || v == null ? null : Number(v);
if (n !== null && !Number.isFinite(n))
throw Error("수치 칸에는 수치만 붙여넣을 수 있습니다.");
return n;
});
if (last.row + values.length > rows.length) throw Error("붙여넣을 행이 모자랍니다.");
EstApp.change((project) => {
const table = project.tables[last.table];
values.forEach((v, i) => (table[last.row + i][last.field] = v));
});
}
if (typeof document !== "undefined" && document.addEventListener)
document.addEventListener("focusin", (event) => {
const t = event.target.closest?.("[data-table][data-field][data-row]");
if (t) last = { table: t.dataset.table, row: Number(t.dataset.row), field: t.dataset.field };
});
function handle(name, state) {
if (name === "cell-copy") copy(state);
else if (name === "cell-paste") paste(state);
else return false;
return true;
}
return { handle };
})();
+1
View File
@@ -80,6 +80,7 @@ const allowed = new Set([
"native-doc-forms.js",
"native-block-file.js",
"native-display.js",
"native-cell-clip.js",
"native-wonga-tools.js",
"native-estimate-print.js",
"native-xml-check.js",