diff --git a/tools/est-test/PLAN.md b/tools/est-test/PLAN.md
index dae997c8d..d8e7085c5 100644
--- a/tools/est-test/PLAN.md
+++ b/tools/est-test/PLAN.md
@@ -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] 산출 줄 잘라내기·복사·붙여넣기(원본 TFRMMULWORD popCut·popCopy·popPaste) — 산출 줄마다 복사·잘라내기·아래 붙여넣기. 붙일 때 수량 지정 표지를 떼고 글만(같은 항목이 두 번 잡히지 않게), 수량 지정된 줄은 잘라내기 거부.
+ - 검증완료: 산출서·수량 흐름·바인딩 시험 22개 통과. ORCA 브라우저에서 표지 있는 줄 복사 → 아래 붙여넣기(표지 없음), 표지 줄 잘라내기 거부, 샘플 복구.
- [x] 수량산출서 엑셀형식 전환(원본 TFRMMULLANG popSetMode — DataKind 로 편집기 교체) — 산출서 화면 「엑셀형식으로」: 엑셀 시트가 없으면 빈 시트를 만들어 엑셀형으로, 「일반형식으로」는 엑셀 내용을 보관한 채 끄고 셀 연결 수량을 쓰지 않음. 바꾼 산출서는 ESTX 저장 거부(JSON).
- 검증완료: 엑셀·산출서·바인딩 시험 11개 통과. ORCA 브라우저에서 새 산출서 → 엑셀형식(격자 160칸) → A1 =2*3 → 6, 일반형식으로 → 격자 숨김·보관, 샘플 복구.
- 제외(원본에서도 쓰지 않거나 외부 프로그램): 제비율 적용기준 보기(설치 폴더 한글 문서를 한글 프로그램으로 열 뿐, 그림 3장), 엑셀물량 열기(Microsoft Excel 을 COM 으로 열 뿐 EST 로 읽지 않음), 플러스 일위대가로 변환(메뉴에 걸리지 않아 원본에서 실행 불가).
diff --git a/tools/est-test/native-quantity-sheets.js b/tools/est-test/native-quantity-sheets.js
index c2734d28a..9c0f64371 100644
--- a/tools/est-test/native-quantity-sheets.js
+++ b/tools/est-test/native-quantity-sheets.js
@@ -188,10 +188,38 @@ const EstQuantitySheets = (() => {
else free(sheet, index, start, editor.value);
}, false);
} else if (name === "excel-mode") excelMode(state);
- else return false;
+ else if (["quantity-row-copy", "quantity-row-cut", "quantity-row-paste"].includes(name)) {
+ // 산출 줄 잘라내기·복사·붙여넣기(TFRMMULWORD popCut·popCopy·popPaste) — 붙일 때 수량 지정 표지는 떼고 글만
+ const sheet = state.project.quantitySheets?.find((s) => s.Code === state.quantitySheet),
+ index = Number(node.dataset.index);
+ if (!sheet?.rows?.[index]) throw Error("산출 줄을 고르세요.");
+ if (name === "quantity-row-paste") {
+ if (!state.quantityClip?.length) throw Error("복사한 산출 줄이 없습니다.");
+ EstApp.change(
+ () => sheet.rows.splice(index + 1, 0, ...rowsToPaste(state.quantityClip)),
+ false,
+ );
+ } else {
+ if (name === "quantity-row-cut" && sheet.rows[index].markers?.length)
+ throw Error("수량으로 지정된 줄은 먼저 지정 해제하세요.");
+ state.quantityClip = [structuredClone(sheet.rows[index])];
+ if (name === "quantity-row-cut") EstApp.change(() => sheet.rows.splice(index, 1), false);
+ else state.notice = "산출 줄을 복사했습니다.";
+ }
+ } else return false;
return true;
}
+ function rowsToPaste(rows) {
+ return rows.map((row) => {
+ const copy = structuredClone(row);
+ copy.LineData = EstText.encode(EstText.display(copy.LineData).split(RESULT).join(""));
+ delete copy.markers;
+ delete copy.PkID;
+ return copy;
+ });
+ }
+
// 엑셀형 수량산출서(원본 TFRMMULEXCEL) — 편집 화면은 native-excel-view.js.
// 엑셀형식(popSetMode): 원본은 DataKind 를 바꿔 편집기를 갈아끼움 — 웹은 빈 엑셀 시트를 만들거나 끄고 켬(내용은 보관).
function excelView(sheet) {
@@ -222,5 +250,5 @@ const EstQuantitySheets = (() => {
}, false);
}
- return { copy, move, mark, free, excelView, matches, selector, handle };
+ return { copy, move, mark, free, rowsToPaste, excelView, matches, selector, handle };
})();
diff --git a/tools/est-test/native-quantity.cjs b/tools/est-test/native-quantity.cjs
index 8fa90437b..f0ffd7ccf 100644
--- a/tools/est-test/native-quantity.cjs
+++ b/tools/est-test/native-quantity.cjs
@@ -325,7 +325,7 @@ const EstQuantity = (() => {
{ esc, number, byId } = EstApp;
byId("work-content").innerHTML =
selector(state) +
- `
${typeof EstQuantitySheets !== "undefined" ? EstQuantitySheets.excelView(sheet) : ""}${esc(output.errors.join(" / "))} 산출 행의 결과 표지로 수량 항목을 연결합니다. 신규 산출서와 지원되지 않는 원본 구조 변경은 JSON으로 저장합니다.
| 줄 | 산출근거 편집 | 작업 |
${sheet.rows.map((row, index) => `| ${index + 1} | | |
`).join("")}
`;
+ `${typeof EstQuantitySheets !== "undefined" ? EstQuantitySheets.excelView(sheet) : ""}${esc(output.errors.join(" / "))} 산출 행의 결과 표지로 수량 항목을 연결합니다. 신규 산출서와 지원되지 않는 원본 구조 변경은 JSON으로 저장합니다.
| 줄 | 산출근거 편집 | 작업 |
${sheet.rows.map((row, index) => `| ${index + 1} | | |
`).join("")}
`;
}
// 원본 수량목록 자리수 메뉴(mnuAuto·mnuTrunc·mnuUp·mnuRound): 결과 코드 뒤 방식(1 반올림·2 절사·3 올림)+자리수.
function resultFormat(code) {