feat(est-test): 수량산출서 검색과 전체보기

- 원본처럼 이름·항목 명칭·규격에서 빈칸·|·괄호를 빼고 찾아 목록을 좁힘
- 고르기 줄을 산출서 모듈로 옮겨 700줄 제한 유지

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-11 20:18:46 +09:00
co-authored by Claude Opus 5.5
parent d88eff4886
commit 507b5a5166
5 changed files with 64 additions and 3 deletions
+2
View File
@@ -481,6 +481,8 @@ ative-standard.js에 표준시장단가 원천(SilJukDat)과 내역 행(EstMainD
- [x] 기타경비 계산·산업안전보건관리비 비교(원본 TfrmWongaDiget 모드 4·1, 0xd0bc80·0xd0b298 — 디스어셈블 근거, 내장 요율표 없음) — 작은 기초금액 식 선택·요율 입력·비교 줄(CalcIndex 2), 대상액(직접공사비 또는 재료비 소계+비과세+직접노무비)·관급/1.1·1.2배·기초금액(기본 5,349,000 원본 상수).
- 검증완료: `test_est_wonga_tools.py` 2개(샘플에서 원본이 적어 둔 식 `(A + 4) × 0.0315 × 1.2`·비교 `((A + 4) + J/1.1) × 0.0315` 그대로 재현·오류 0, 기타경비 작은 쪽·비교 줄·잘못된 입력 거부) 통과. ORCA 브라우저에서 안전관리비 비교 → 같은 식·도급액 유지, 기타경비는 직접공사비 행 없는 샘플에서 원본처럼 거부, 샘플 복구.
- 남음: 구성비·요율 전환(CalcBase), 약어 표시(SName), 제비율 기준 문서 보기(원본 한글 파일).
- [x] 수량산출서 검색·전체보기(원본 TFRMMULLANG btnSerch·btnAllDisplay — 빈칸·|·괄호 무시 포함 검색) — 고르기 줄을 `native-quantity-sheets.js` 로 옮겨 700줄 제한 유지.
- 검증완료: `test_est_quantity_sheets.py` 검색 시험 포함 수량 시험 22개 통과. ORCA 브라우저에서 「집수」 검색 → 항목에 집수정이 있는 「배수」만·선택 이동, 전체보기 → 3개 복귀, 샘플 복구.
- 대조 합계(CAD 제외 882개): 구현 283 · 부분 193 · 없음 273 · 제외 133.
- [ ] 남은 핵심(실무 빈도순):
3. 수량산출서 — 누계·4방향 합계, 제출용 전체 인쇄.
+2
View File
@@ -58,6 +58,8 @@
산출 줄 옆 `수량 지정`은 줄 글에서 드래그해 고른 숫자를 결과 표지로 감싸고 수량 항목을 새로 만듭니다. 품명·규격·단위 표지가 있으면 항목 이름에 옮깁니다. `지정 해제`는 커서가 놓인 결과 표지를 풀고, 다른 줄에서 쓰지 않는 항목을 지웁니다. 내역에 연결된 항목은 먼저 연결을 비워야 합니다. 이 변경이 있는 작업은 ESTX 수정 저장을 거부합니다.
수량산출 화면의 `검색`은 원본처럼 산출서 이름과 항목의 명칭·규격에서 빈칸·`|`·괄호를 빼고 찾아 목록을 좁히고, `전체보기`로 되돌립니다.
수량산출 화면의 `산출서 복사`는 현재 산출서를 바로 아래에 복사하고, 내역에 두 번 반영되지 않도록 복사본의 내역 연결을 비웁니다. `산출서 위로`·`아래로`로 순서를 바꿉니다. 산출서 추가·순서 변경이 있는 작업은 ESTX 수정 저장을 거부하고 JSON 저장을 안내합니다.
수량산출 화면의 수량 항목마다 `끝수 처리`에서 AUTO(산출서 기본)·절사·올림·반올림 0~5자리를 고릅니다. 원본처럼 결과 코드 뒤에 방식과 자리수를 붙이며(`r000001` → `r00000131`), 산출 행의 결과 표지도 함께 바꿉니다. 이 변경이 있는 작업은 ESTX 수정 저장을 거부하고 JSON 저장을 안내합니다.
+46 -1
View File
@@ -106,8 +106,53 @@ const EstQuantitySheets = (() => {
return editor;
}
// 원본 수량산출 검색(TFRMMULLANG btnSerch·btnAllDisplay): 명칭·규격에서 빈칸·|·괄호를 빼고 포함 검색.
const loose = (text) =>
String(text ?? "")
.replace(/[\s|()[\]{}]/g, "")
.toLowerCase();
function matches(sheet, query) {
const q = loose(query);
if (!q) return true;
return [sheet.Name, sheet.Code, ...(sheet.items || []).flatMap((i) => [i.Name, i.Size])].some(
(t) => loose(t).includes(q),
);
}
// 수량산출서 고르기 줄(원본 공종 목록 + 검색·전체보기).
function selector(state) {
const { esc } = EstApp;
const options = (state.project.quantitySheets || [])
.filter((sheet) => matches(sheet, state.quantityQuery) || sheet.Code === state.quantitySheet)
.map(
(sheet) =>
`<option value="${esc(sheet.Code)}" ${state.quantitySheet === sheet.Code ? "selected" : ""}>${esc(sheet.Name || sheet.Code)}</option>`,
)
.join("");
return `<div class="business-tools"><label>수량산출서 <select id="quantity-sheet-choice"><option value="manual">수식표 직접 작성</option>${options}</select></label><input data-quantity-query value="${esc(state.quantityQuery || "")}" placeholder="명칭·규격" aria-label="산출서 검색"><button data-action="quantity-sheet-search">검색</button><button data-action="quantity-sheet-all">전체보기</button><button data-quantity-new>새 산출서</button><button data-quantity-excel>Excel 셀 수량 연결</button><button data-quantity-excel-links>Excel 연결 관리</button></div>`;
}
function handle(name, state, node) {
const code = state.quantitySheet;
if (name === "quantity-sheet-search" || name === "quantity-sheet-all") {
state.quantityQuery =
name === "quantity-sheet-all"
? ""
: (document.querySelector("[data-quantity-query]")?.value ?? "");
const first = (state.project.quantitySheets || []).find((s) =>
matches(s, state.quantityQuery),
);
if (state.quantityQuery && !first) throw Error("찾는 수량산출서가 없습니다.");
if (
first &&
!matches(
state.project.quantitySheets.find((s) => s.Code === code) || {},
state.quantityQuery,
)
)
state.quantitySheet = first.Code;
return true;
}
if (name === "quantity-sheet-copy") {
EstApp.change((project) => {
state.quantitySheet = copy(project, code, EstApp.uid());
@@ -176,5 +221,5 @@ const EstQuantitySheets = (() => {
.join("");
}
return { copy, move, mark, free, excelView, handle };
return { copy, move, mark, free, excelView, matches, selector, handle };
})();
+1 -2
View File
@@ -223,8 +223,7 @@ const EstQuantity = (() => {
return `Q${index}`;
}
function selector(state) {
const { esc } = EstApp;
return `<div class="business-tools"><label>수량산출서 <select id="quantity-sheet-choice"><option value="manual">수식표 직접 작성</option>${(state.project.quantitySheets || []).map((sheet) => `<option value="${esc(sheet.Code)}" ${state.quantitySheet === sheet.Code ? "selected" : ""}>${esc(sheet.Name || sheet.Code)}</option>`).join("")}</select></label><button data-quantity-new>새 산출서</button><button data-quantity-excel>Excel 셀 수량 연결</button><button data-quantity-excel-links>Excel 연결 관리</button></div>`;
return typeof EstQuantitySheets === "undefined" ? "" : EstQuantitySheets.selector(state);
}
function validExcelAddress(address) {
const parts = typeof address === "string" && address.match(/^([A-Z]{1,3})([1-9]\d*)$/);