feat(est): show existing quantity links
This commit is contained in:
@@ -8,6 +8,21 @@ from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_existing_quantity_links_respect_native_mode_and_avoid_ambiguous_targets():
|
||||
script = """const {existingTargets}=require('./tools/est-test/native-links.cjs');
|
||||
const sheet={Code:'N1',items:[{Code:'r1'},{Code:'r2'},{Code:'r3'},{Code:'r4'}]};
|
||||
const blob=(v,enc='utf8')=>({binary:Buffer.from(v,enc).toString('base64')});
|
||||
const rows=[{Code:'E1',MullangIndex:1,MulCode:blob('N1\\tr1')},
|
||||
{Code:'E2',MullangIndex:1,MulCode:blob('N1\\tr2','utf16le')},
|
||||
{Code:'E3',MullangIndex:2,MulCode:blob('N1\\tr3')},
|
||||
{Code:'E4',MullangIndex:1,MulCode:blob('N1\\tr4')},{Code:'E4'}];
|
||||
const warnings=new Set();existingTargets({EstMainDat:rows},[sheet],warnings);
|
||||
process.stdout.write(JSON.stringify({items:sheet.items,warnings:[...warnings]}));"""
|
||||
result = json.loads(subprocess.check_output(["node", "-e", script], cwd=ROOT).decode("utf-8"))
|
||||
assert [item.get("TargetCode") for item in result["items"]] == ["E1", "E2", None, None]
|
||||
assert len(result["warnings"]) == 2
|
||||
|
||||
|
||||
def test_estx_print_catalog_uses_project_options_when_sample_exists():
|
||||
sample = Path("C:/Dasansoft/Est Plus 2/Data/ProjectData/sample.estx")
|
||||
if not sample.exists():
|
||||
|
||||
@@ -23,7 +23,7 @@ Ctrl+S는 입력 중인 셀도 확정한 뒤 저장합니다. 연속 저장 요
|
||||
|
||||
당초 확정 사본에는 재계산 입력을 한 번만 보관하며, 작업본의 도면·원본 압축 자료는 보존합니다. 설치된 `main.estx`의 당초 확정 후 실제 저장 API를 통과했고, 산근 77,395행과 원본 압축 해시 보존·계산 오류 0을 확인했습니다(요청 약 215MB, 저장 약 221MB).
|
||||
|
||||
초기 자료는 기술정보 DB의 실제 REA 저장 샘플입니다. `REA 샘플 대조`에서 원본 저장값과 275개 계산 결과를 비교합니다. 테스트용 임의 요율을 원본 기본값으로 넣지 않습니다. 새 원가 서식의 요율은 사용자가 입력합니다.
|
||||
초기 자료는 기술정보 DB의 실제 REA 저장 샘플입니다. `REA 샘플 대조`에서 원본 저장값과 275개 계산 결과를 비교합니다. 기존 ESTX의 기본 수량 연결은 문서·항목 코드와 내역 코드가 유일한 경우 화면에 표시하며, 중복·다른 모드는 원문에 보존하고 경고합니다. 원본 연결 데이터 실자료 검증과 새 연결의 ESTX 쓰기는 아직 지원하지 않습니다. 테스트용 임의 요율을 원본 기본값으로 넣지 않습니다. 새 원가 서식의 요율은 사용자가 입력합니다.
|
||||
|
||||
## 검증과 남은 범위
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
const zlib = require("node:zlib");
|
||||
const quantity = require("./native-quantity.cjs");
|
||||
const { readPrintOptions } = require("./import-print.cjs");
|
||||
const { existingTargets } = require("./native-links.cjs");
|
||||
const decoder = new TextDecoder("euc-kr", { fatal: true });
|
||||
const LIMIT = 256 * 1024 * 1024;
|
||||
const crcTable = Array.from({ length: 256 }, (_, n) => {
|
||||
@@ -367,6 +368,7 @@ function importEstx(buffer, name = "가져온 내역서") {
|
||||
warnings.add(`수량산출 ${row.Code}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
existingTargets(tables, quantitySheets, warnings);
|
||||
return {
|
||||
schema: "est-web-2",
|
||||
name: props.TCommonOption?.ProjectName || name,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// REA 0x11167e4 / 0xa911d0: MulCode is split at a tab into sheet and item codes.
|
||||
function existingTargets(tables, sheets, warnings) {
|
||||
const candidates = new Map();
|
||||
const targetCounts = new Map();
|
||||
for (const row of tables.EstMainDat || [])
|
||||
if (row.Code && !Number(row.Level))
|
||||
targetCounts.set(row.Code, (targetCounts.get(row.Code) || 0) + 1);
|
||||
for (const row of tables.EstMainDat || []) {
|
||||
if (Number(row.Level) || !row.MulCode?.binary || !row.MullangIndex || row.ExcelSheetIndex)
|
||||
continue;
|
||||
if (row.MullangIndex !== 1) {
|
||||
warnings.add("수량 연결: 기본 모드 외 연결은 원문에 보존했습니다.");
|
||||
continue;
|
||||
}
|
||||
const bytes = Buffer.from(row.MulCode.binary, "base64");
|
||||
let text;
|
||||
try {
|
||||
let wide = bytes.length > 1 && bytes.length % 2 === 0;
|
||||
for (let i = 1; wide && i < bytes.length; i += 2) wide = bytes[i] === 0;
|
||||
text = wide
|
||||
? bytes.toString("utf16le")
|
||||
: new TextDecoder("euc-kr", { fatal: true }).decode(bytes);
|
||||
} catch {
|
||||
warnings.add("수량 연결: 코드 인코딩을 읽을 수 없습니다.");
|
||||
continue;
|
||||
}
|
||||
const parts = text.replace(/\0+$/, "").split("\t");
|
||||
if (parts.length !== 2 || !parts[0] || !parts[1] || !row.Code) {
|
||||
warnings.add("수량 연결: 문서·항목 코드 또는 내역 코드가 없습니다.");
|
||||
continue;
|
||||
}
|
||||
if (targetCounts.get(row.Code) !== 1) {
|
||||
warnings.add(`수량 연결: 내역 코드 ${row.Code}가 중복되어 자동 선택하지 않았습니다.`);
|
||||
continue;
|
||||
}
|
||||
const sheet = sheets.find((value) => value.Code === parts[0]);
|
||||
const item = sheet?.items.find((value) => value.Code === parts[1]);
|
||||
if (!item) {
|
||||
warnings.add(`수량 연결: ${parts[0]}/${parts[1]} 항목을 찾을 수 없습니다.`);
|
||||
continue;
|
||||
}
|
||||
if (!candidates.has(item)) candidates.set(item, new Set());
|
||||
candidates.get(item).add(row.Code);
|
||||
}
|
||||
for (const [item, codes] of candidates)
|
||||
if (codes.size === 1) item.TargetCode = [...codes][0];
|
||||
else
|
||||
warnings.add(`수량 연결: ${item.Code}에 여러 내역 코드가 연결되어 자동 선택하지 않았습니다.`);
|
||||
}
|
||||
module.exports = { existingTargets };
|
||||
Reference in New Issue
Block a user