feat(est-test): 원가계산서 산출근거 약어로 표시
- 원본 popupSNameShow: 산출근거 식의 기호를 비목 약어로, 숫자를 요율 글자로 바꿔 화면·인쇄에 표시하고 편집 잠금, 저장 값은 그대로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -148,3 +148,21 @@ def test_position_sets_row_digits_like_original():
|
||||
assert result["input"]["Cost"] == 1234600 and result["input"]["TC"] == 1234567
|
||||
assert result["input"]["Diget"] == 2 and result["input"]["DigetIndex"] == 1
|
||||
assert result["last"] == 2
|
||||
|
||||
|
||||
def test_abbreviate_formula_like_original_sname_show():
|
||||
result = run_js(
|
||||
r"""(() => {
|
||||
const rows = source.tables.Wonga, a = EstWongaTools.abbreviate(rows, source.options);
|
||||
const by = (g) => a[rows.findIndex((r) => r.Gubun === g)];
|
||||
const nf = EstWongaTools.abbreviate([{ Index: 1, NumStr: '1', Name: '직 접 노 무 비' },
|
||||
{ NumStr: '2', VSusic: '1 × 0.05', Youl: '5%' }], { VerFormat: false });
|
||||
return { five: by('5'), c: by('C'), e: by('E'), empty: a[0], nf: nf[1], same: rows.every((r, i) => r.VSusic === source.tables.Wonga[i].VSusic) };
|
||||
})()"""
|
||||
)
|
||||
assert result["five"] == "직.노 × 16.5%"
|
||||
assert result["c"] == "( 산출경비 ~ 18 )"
|
||||
assert result["e"] == "순.공 × 8%"
|
||||
assert result["empty"] == ""
|
||||
assert result["nf"] == "직접노무비 × 5%"
|
||||
assert result["same"] is True
|
||||
|
||||
@@ -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] 원가계산서 산출근거 약어로 표시(원본 popupSNameShow 0xd0e494 · 약어 생성 0xc97790) — 원가계산서(W) 메뉴 단추로 켜고 끔. 산출근거 식의 기호(영문 시작 또는 2자 이하, 범위 기호 뒤 제외)를 그 행 약어(SName, 없으면 빈칸 뺀 이름)로, 숫자×100 이 그 행 요율이면 요율 글자로, 「:」 는 「~」 로 바꿔 화면·인쇄에 보이고 그동안 산출근거 칸 잠금. 저장 값(VSusic)은 그대로. 기성 원가 화면의 같은 확인란과 같은 설정.
|
||||
- 검증완료: `test_est_wonga_tools.py` 5개(샘플 「직.노 × 16.5%」·「( 산출경비 ~ 18 )」·「순.공 × 8%」, 번호 열쇠, 원본 식 불변) 통과. ORCA 브라우저에서 켜기 → 약어 표시·입력칸 0, 끄기 → 입력칸 31, 설정 지워 복구.
|
||||
- [x] 발췌근거(원본 barbtnColumn — TfrmHeaderChange 0x110b7ec · 자재 TfrmStuffcHeadChange 0xf61b84) — 자재·노임 화면 「발췌근거」 단추로 출처 열(Cost1~6) 이름·쪽수(자재)·보기 편집, 자재는 8가지 출처 이름에서 고르고 중복 거부, 숨긴 출처는 최저·평균·최고 단가 고르기에서 빠짐(서버 재계산도 같은 코드). 값은 계산 환경 `PriceSources`(ESTX 저장에는 남지 않음 — 원본도 출처 이름은 격자 배치에만 둠).
|
||||
- 검증완료: `test_est_sources.py`(최저 80→100·최고 120→100, 중복 거부, 머리글 「물가자료 (120)」, 숨긴 열 빠짐) 및 REA 대조 39개 통과. ORCA 브라우저에서 자재단가 발췌근거 2번 「물가자료」 쪽 77, 3번 숨김 → 머리글 반영·도급액 불변, 설정 지워 복구.
|
||||
- [x] 조달청 표준공사코드 적용(원본 mnuCodeChange → TfrmCodeChange — 디스어셈블 근거) — 작업데이타 7종 탭(일위·단산·기계·표준·견적·자재·노임), 코드변경(목록 코드·직접 입력, 기계 16자리면 끝 0), 검사 색(EST 코드 노랑·다른 표 중복 연분홍+비고·자리수 진황), 변경취소, 적용(자기 표·본문 머리 Code/TitleCode·참조 행 LinkIndex=종류·Index≤0 만, 재계산 없음), 코드일괄변경(고른 .estx 의 같은 표에서 자동연결 맞춤 규칙, EST→비EST 만·중복 금지, 표준은 옛 코드 부분일치), 자동변경(표준 행 번호 구간 C·A·M), 다산코드정리, 사용자정의코드, 목록·작업데이타 검색(& 두 조각). 조달청 목록은 원본 설치의 PPS_STUFFC.dat·PPS_HEAD.dat 를 사용자가 골라 불러옴(내장 자료 없음, `/api/pps-table`). 원본의 분류 트리(PPS_*TYPE)·다산정리 PkID 쓰기는 하지 않음.
|
||||
|
||||
@@ -76,6 +76,8 @@
|
||||
|
||||
편집(E) › `조달청 표준공사코드 적용`은 노임·자재·기계·일위 등의 코드를 조달청 코드로 바꿉니다. 왼쪽 목록은 원본 설치 폴더 `System\MainSys` 의 `PPS_STUFFC.dat`(노임·자재·장비) 또는 `PPS_HEAD.dat`(일위·단산·표준)를 골라 불러옵니다. 코드를 바꾼 뒤 `적용`을 누르면 그 코드를 쓰는 내역·일위·단산 줄도 함께 바뀝니다. `코드일괄변경`은 고른 .estx 파일(예: 원본 JodalCodeDB.estx)에서 품명·규격이 같은 코드를 찾아 EST 코드만 바꿉니다.
|
||||
|
||||
원가계산서(W) › `산출근거 약어로 표시`를 누르면 산출근거가 「직.노 × 16.5%」처럼 비목 약어와 요율로 보이고(인쇄 포함), 다시 누르면 원래 식으로 돌아옵니다. 약어로 보이는 동안에는 산출근거를 고칠 수 없습니다.
|
||||
|
||||
자재단가·노임단가 화면의 `발췌근거` 단추로 가격 출처 열(출처1~6)의 이름과 쪽수를 바꾸거나 숨길 수 있습니다. 숨긴 출처는 최저·평균·최고 단가를 고를 때 빠집니다.
|
||||
|
||||
원본의 엑셀형 수량산출서(본문이 옛 Excel .xls)는 가져올 때 셀 값과 수식을 함께 읽어 수량산출 화면 아래 격자로 보입니다. 셀을 누르면 수식줄에서 값이나 `=수식`을 고치고 바로 다시 계산합니다(참조·범위·사칙·SUM·ROUND 등). Shift 로 범위를 골라 행·열 넣기·지우기, 잘라내기·복사·붙여넣기, 굵게·정렬·병합·대각선을 쓸 수 있고, 수식의 참조는 원본처럼 따라 옮겨집니다. `우측·위·좌측·아래 합`은 원본처럼 그 방향의 숫자 셀을 더하는 SUM 을 넣고, `수량으로 지정`은 셀을 수량 항목에 연결해 항목 수량이 그 셀 값(산출서 자리수)을 따르게 합니다. 원본 파일에는 셀과 항목의 대응이 보이지 않아, 가져올 때 저장 수량과 값이 같은 합계 셀을 「추정 연결」로 둡니다. 셀을 고친 엑셀형 산출서는 ESTX 저장을 거부합니다.
|
||||
|
||||
@@ -208,7 +208,8 @@
|
||||
<button data-action="jungsang-toggle">정산원가계산서 작성/해제</button
|
||||
><button data-action="jungsang-init">당초금액 초기화</button>
|
||||
<button data-action="wonga-gita">기타경비 계산</button
|
||||
><button data-action="wonga-anjun">산업안전보건관리비 비교</button>
|
||||
><button data-action="wonga-anjun">산업안전보건관리비 비교</button
|
||||
><button data-action="wonga-sname">산출근거 약어로 표시</button>
|
||||
</div>
|
||||
</details>
|
||||
<details>
|
||||
|
||||
@@ -266,6 +266,15 @@ const EstForms = (() => {
|
||||
columns = [...common, "GN", "TU", "TC", "NC", "GC", "JC", "SC"];
|
||||
}
|
||||
}
|
||||
if (
|
||||
table === "Wonga" &&
|
||||
project.options.WongaSNameShow &&
|
||||
typeof EstWongaTools !== "undefined"
|
||||
) {
|
||||
const rows = project.tables.Wonga || [],
|
||||
abbr = EstWongaTools.abbreviate(rows, project.options);
|
||||
return { table, columns, rows: rows.map((row, i) => ({ ...row, VSusic: abbr[i] })) };
|
||||
}
|
||||
return { table, columns, rows: project.tables[table] || [] };
|
||||
}
|
||||
const totals = {
|
||||
|
||||
@@ -76,7 +76,13 @@ const EstViews = (() => {
|
||||
}
|
||||
function grid(table, list, columns) {
|
||||
const { esc, number, state } = EstApp;
|
||||
return `<table class="sheet" data-grid="${table}"><thead><tr><th class="row-number">선택</th>${columns.map((key) => `<th class="col-${key}">${esc((table === "Wonga" && typeof EstJungSang !== "undefined" && EstJungSang.label(state.project, key)) || (typeof EstSources !== "undefined" && EstSources.label(state.project.options, table, key)) || EstModel.labels[key] || key)}</th>`).join("")}</tr></thead><tbody>${list.map(({ row, index }) => `<tr data-index="${index}" class="${state.selection.has(index) ? "selected " : ""}${Number(row.Index) > 0 ? "heading " : ""}${Number(row.Level) > 0 ? "level " : ""}${row.Error ? "error-row" : ""}"><th class="row-number"><label><input type="checkbox" data-select="${index}" ${state.selection.has(index) ? "checked" : ""}>${index + 1}</label></th>${columns.map((key) => `<td class="col-${key}"${key === "Name" && Number(row.LeftSpace) > 0 ? ` style="padding-left:${Number(row.LeftSpace) * 6}px"` : ""}>${cell(table, index, key, row[key])}</td>`).join("")}</tr>`).join("")}</tbody></table>`;
|
||||
const abbr =
|
||||
table === "Wonga" &&
|
||||
state.project.options.WongaSNameShow &&
|
||||
typeof EstWongaTools !== "undefined"
|
||||
? EstWongaTools.abbreviate(state.project.tables.Wonga || [], state.project.options)
|
||||
: null;
|
||||
return `<table class="sheet" data-grid="${table}"><thead><tr><th class="row-number">선택</th>${columns.map((key) => `<th class="col-${key}">${esc((table === "Wonga" && typeof EstJungSang !== "undefined" && EstJungSang.label(state.project, key)) || (typeof EstSources !== "undefined" && EstSources.label(state.project.options, table, key)) || EstModel.labels[key] || key)}</th>`).join("")}</tr></thead><tbody>${list.map(({ row, index }) => `<tr data-index="${index}" class="${state.selection.has(index) ? "selected " : ""}${Number(row.Index) > 0 ? "heading " : ""}${Number(row.Level) > 0 ? "level " : ""}${row.Error ? "error-row" : ""}"><th class="row-number"><label><input type="checkbox" data-select="${index}" ${state.selection.has(index) ? "checked" : ""}>${index + 1}</label></th>${columns.map((key) => `<td class="col-${key}"${key === "Name" && Number(row.LeftSpace) > 0 ? ` style="padding-left:${Number(row.LeftSpace) * 6}px"` : ""}>${abbr && key === "VSusic" ? `<span title="산출근거 약어 표시 중(편집 잠김)">${esc(abbr[index])}</span>` : cell(table, index, key, row[key])}</td>`).join("")}</tr>`).join("")}</tbody></table>`;
|
||||
}
|
||||
function render(state) {
|
||||
const { project, view } = state,
|
||||
|
||||
@@ -241,12 +241,59 @@ const EstWongaTools = (() => {
|
||||
);
|
||||
}
|
||||
|
||||
// 산출근거 약어로 표시(popupSNameShow 0xd0e494 · 약어 생성 0xc97790): 산출근거 식의 기호(영문 시작 또는 2자 이하,
|
||||
// 범위 기호 뒤 제외)를 그 행의 약어(SName, 없으면 빈칸 뺀 이름)로, 숫자×100 이 그 행 요율과 같으면 요율 글자로, 「:」 는 「~」 로.
|
||||
// 화면·인쇄에서 산출근거 대신 보이고 그동안 산출근거 칸은 잠근다(원본 VGungu). 저장 값은 바꾸지 않는다.
|
||||
function abbreviate(rows, options = {}) {
|
||||
const key = (r) =>
|
||||
String(options.VerFormat === false ? (r.NumStr ?? r.Index ?? "") : (r.Gubun ?? "")).trim();
|
||||
const keys = new Map();
|
||||
rows.forEach((r) => {
|
||||
const k = key(r);
|
||||
if (k && !keys.has(k)) keys.set(k, r);
|
||||
});
|
||||
const label = (r) => String(r.SName || r.Name || "").replace(/\s/g, "");
|
||||
return rows.map((row) => {
|
||||
const s = String(row.VSusic ?? "").trim();
|
||||
if (!s) return "";
|
||||
const youl = String(row.Youl ?? "")
|
||||
.replace("%", "")
|
||||
.trim();
|
||||
const tokens = s.match(/\d+(?:\.\d+)?|[A-Za-z가-힣_][A-Za-z0-9가-힣_]*|\S/g) || [];
|
||||
return tokens
|
||||
.map((t, i) => {
|
||||
const prev = tokens[i - 1];
|
||||
if (t === ":") return "~";
|
||||
const numeric = /^\d+(?:\.\d+)?$/.test(t);
|
||||
if (
|
||||
(/^[A-Z]/.test(t) || (t.length <= 2 && /^[A-Za-z0-9가-힣]+$/.test(t))) &&
|
||||
prev !== ":" &&
|
||||
prev !== "~"
|
||||
) {
|
||||
const hit = keys.get(t);
|
||||
if (hit) return label(hit);
|
||||
}
|
||||
if (numeric && youl && Number(youl) === Number((Number(t) * 100).toPrecision(12)))
|
||||
return String(row.Youl).trim();
|
||||
return t;
|
||||
})
|
||||
.join(" ");
|
||||
});
|
||||
}
|
||||
|
||||
function handle(name, state) {
|
||||
if (name === "wonga-sname") {
|
||||
EstApp.change(
|
||||
(project) => (project.options.WongaSNameShow = !project.options.WongaSNameShow),
|
||||
false,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if (name === "wonga-gita") dialog(state, "gita");
|
||||
else if (name === "wonga-anjun") dialog(state, "anjun");
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return { gita, anjun, anjunBase, position, positionDialog, handle };
|
||||
return { gita, anjun, anjunBase, position, positionDialog, abbreviate, handle };
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user