feat(est-test): 산출 줄 수량 지정과 지정 해제

- 드래그한 숫자를 결과 표지로 감싸 수량 항목 생성, 품명·규격·단위 표지 반영
- 커서 위치 결과 해제와 미사용 항목 삭제, 내역 연결 항목은 거부
- 이 편집의 ESTX 저장은 명확한 문구로 거부

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-11 15:15:58 +09:00
co-authored by Claude Opus 5.5
parent e7e59fc513
commit 029c7dab5a
7 changed files with 169 additions and 5 deletions
+91 -2
View File
@@ -1,5 +1,6 @@
// 원본 수량산출(TFRMMULLANG) 공종 단위 조작 — 공종복사하기·붙여넣기(mnuCopy·mnuPaste)와 순서 이동.
// 복사본은 현재 산출서 바로 아래에 넣고, 내역 이중 반영을 막기 위해 내역 연결을 비운다.
// 산출 줄의 수량으로 지정·지정 해제(TFRMMULWORD popSetMulLang·popSetFree)는 화면 표기(⟦결과⟧) 위치로 다룬다.
const EstQuantitySheets = (() => {
function copy(project, code, uid) {
const sheets = project.quantitySheets || [],
@@ -30,7 +31,82 @@ const EstQuantitySheets = (() => {
return true;
}
function handle(name, state) {
const RESULT = "⟦결과⟧";
function tagged(shown, label) {
const match = shown.match(new RegExp(`⟦${label}⟧([^⟦]*)⟦${label}⟧`));
return match ? match[1].trim() : "";
}
function count(text) {
return text.split(RESULT).length - 1;
}
function mark(sheet, rowIndex, start, end, code, text) {
const row = sheet.rows[rowIndex];
if (!row) throw Error("산출 줄을 고르세요.");
const shown = text ?? EstText.display(row.LineData);
let from = Math.min(start, end),
to = Math.max(start, end);
while (from < to && /\s/.test(shown[from])) from++;
while (to > from && /\s/.test(shown[to - 1])) to--;
const picked = shown.slice(from, to);
if (!/^-?(\d{1,3}(,\d{3})+|\d+)(\.\d+)?$/.test(picked))
throw Error("산출 줄에서 수량으로 지정할 숫자만 드래그해 고르세요.");
if (count(shown.slice(0, from)) % 2) throw Error("이미 수량으로 지정된 숫자입니다.");
const position = count(shown.slice(0, from)) / 2;
row.LineData = EstText.encode(
`${shown.slice(0, from)}${RESULT}${picked}${RESULT}${shown.slice(to)}`,
);
row.markers = [...(row.markers || [])];
row.markers.splice(position, 0, code);
sheet.markerEdited = true;
sheet.items.push({
Code: code,
Name: tagged(shown, "품명") || "새 항목",
Size: tagged(shown, "규격"),
Unite: tagged(shown, "단위"),
GN: 0,
TargetCode: "",
});
return code;
}
function free(sheet, rowIndex, caret, text) {
const row = sheet.rows[rowIndex];
if (!row) throw Error("산출 줄을 고르세요.");
const shown = text ?? EstText.display(row.LineData),
pairs = [];
for (let at = shown.indexOf(RESULT), open = -1; at >= 0; at = shown.indexOf(RESULT, at + 1))
if (open < 0) open = at;
else {
pairs.push([open, at + RESULT.length]);
open = -1;
}
const position = pairs.findIndex(([open, close]) => caret >= open && caret <= close);
if (position < 0) throw Error("해제할 수량(⟦결과⟧ 안쪽)에 커서를 두세요.");
const code = row.markers?.[position],
item = sheet.items.find((next) => next.Code === code);
if (item?.TargetCode || item?.TargetKey)
throw Error("내역에 연결된 수량 항목입니다. 연결 내역코드를 먼저 비우세요.");
const [open, close] = pairs[position];
row.LineData = EstText.encode(
shown.slice(0, open) +
shown.slice(open + RESULT.length, close - RESULT.length) +
shown.slice(close),
);
row.markers = row.markers.filter((_, i) => i !== position);
sheet.markerEdited = true;
if (!sheet.rows.some((other) => (other.markers || []).includes(code)))
sheet.items = sheet.items.filter((next) => next.Code !== code);
return code;
}
function lineEditor(index) {
const editor = document.querySelector(`[data-quantity-line="${index}"]`);
if (!editor) throw Error("산출 줄을 찾을 수 없습니다.");
return editor;
}
function handle(name, state, node) {
const code = state.quantitySheet;
if (name === "quantity-sheet-copy") {
EstApp.change((project) => {
@@ -44,9 +120,22 @@ const EstQuantitySheets = (() => {
if (index < 0 || index + step < 0 || index + step >= sheets.length)
throw Error("더 옮길 수 없습니다.");
EstApp.change((project) => move(project, code, step), false);
} else if (name === "quantity-mark" || name === "quantity-free") {
const sheet = state.project.quantitySheets?.find((next) => next.Code === code),
index = Number(node?.dataset.index),
editor = lineEditor(index);
if (!sheet) throw Error("수량산출서를 고르세요.");
const { selectionStart: start, selectionEnd: end } = editor;
if (name === "quantity-mark" && start === end)
throw Error("산출 줄에서 수량으로 지정할 숫자를 드래그해 고른 뒤 누르세요.");
EstApp.change(() => {
if (name === "quantity-mark")
mark(sheet, index, start, end, EstQuantity.nextItemCode(sheet), editor.value);
else free(sheet, index, start, editor.value);
}, false);
} else return false;
return true;
}
return { copy, move, handle };
return { copy, move, mark, free, handle };
})();