- 엑셀형 산출서 BIFF 수식 해석·다시 계산·수식줄 편집·4방향 합·수량으로 지정(가져올 때 값이 같은 합계 셀 추정 연결), 셀 편집 시 ESTX 저장 거부 - 원가 행 계산을 원본 c9d278 끝부분대로: 소수 4자리 반올림 뒤 원 미만 절사, Diget>0 행 자리맞춤(0x11293e8) - 원가계산서 「선택 행 자릿수 설정」을 원본 선택항목 절하하기 대화로(도급액 행 거부, 입력 행 원금액 기준) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
3.5 KiB
Python
73 lines
3.5 KiB
Python
"""EST 테스트 앱 엑셀형 수량산출서 계산·편집(원본 TFRMMULEXCEL, .xls BIFF 수식)."""
|
|
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
MAIN = Path("C:/Dasansoft/Est Plus 2/Data/ProjectData/main.estx")
|
|
|
|
|
|
def node(script: str, *args: str):
|
|
return json.loads(
|
|
subprocess.check_output(["node", "-e", script, *args], cwd=ROOT).decode("utf-8")
|
|
)
|
|
|
|
|
|
def test_evaluate_and_directional_sum():
|
|
out = node("""
|
|
const X = require(process.cwd() + '/tools/est-test/native-excel-sheet.cjs');
|
|
const cells = { '0,0': { value: '품명' }, '1,0': { value: 2 }, '2,0': { value: 3 }, '3,0': {},
|
|
'1,1': { value: 0, formula: '(A2+A3)/2*4' }, '1,2': { value: 0, formula: 'SUM(A2:B2)' },
|
|
'4,4': { value: 0, formula: 'ROUND(10/3,2)+ROUNDUP(1.21,1)+ROUNDDOWN(1.29,1)' }, '5,5': { value: 0, formula: 'F6+1' } };
|
|
const errors = X.recalc(cells);
|
|
const down = X.sumFormula(cells, 3, 0, 'up');
|
|
let none = ''; try { X.sumFormula({ '0,0': { value: 'x' } }, 1, 0, 'up'); } catch (e) { none = e.message; }
|
|
process.stdout.write(JSON.stringify({ b2: cells['1,1'].value, c2: cells['1,2'].value, e5: cells['4,4'].value,
|
|
cycle: [cells['5,5'].value, errors.length], down, none }));
|
|
""")
|
|
assert out["b2"] == 10 and out["c2"] == 12
|
|
assert out["e5"] == 3.33 + 1.3 + 1.2
|
|
assert out["cycle"] == ["#오류", 1]
|
|
assert out["down"] == "SUM(A2:A3)"
|
|
assert out["none"] == "더할 셀이 없습니다."
|
|
|
|
|
|
def test_real_sheets_recompute_and_link_items():
|
|
if not MAIN.exists():
|
|
pytest.skip("installed EST main sample unavailable")
|
|
out = node(
|
|
"""
|
|
const fs = require('fs'), vm = require('vm');
|
|
const imp = require(process.cwd() + '/tools/est-test/import-estx.cjs'), ex = require(process.cwd() + '/tools/est-test/export-estx.cjs');
|
|
const p = imp.importEstx(fs.readFileSync(process.argv[1]));
|
|
const s = p.quantitySheets.find((x) => x.Code === 'N0000000136');
|
|
const formulas = p.quantitySheets.filter((x) => x.excel).flatMap((x) => Object.values(x.excel[0].cells)).filter((c) => c.formula);
|
|
const same = Buffer.compare(ex.exportEstx(p), fs.readFileSync(process.argv[1])) === 0;
|
|
const ctx = vm.createContext({ structuredClone });
|
|
for (const f of ['native-math.js', 'native-formula.js', 'native-text.js', 'native-excel-sheet.cjs', 'native-quantity.cjs'])
|
|
vm.runInContext(fs.readFileSync('tools/est-test/' + f, 'utf8'), ctx, { filename: f });
|
|
ctx.s = s;
|
|
const before = vm.runInContext('EstQuantity.calculate(s).items.map((i) => i.GN)', ctx);
|
|
vm.runInContext("s.excel[0].cells['6,2'].value = 40; EstExcelSheet.recalc(s.excel[0].cells)", ctx);
|
|
const after = vm.runInContext('EstQuantity.calculate(s).items.map((i) => i.GN)', ctx);
|
|
s.excelEdited = true;
|
|
let message = ''; try { ex.exportEstx(p); } catch (e) { message = e.message; }
|
|
process.stdout.write(JSON.stringify({ decoded: formulas.every((c) => typeof c.formula === 'string'), count: formulas.length,
|
|
links: Object.fromEntries(Object.entries(s.excelLinks).map(([k, l]) => [k, [l.row, l.col, l.inferred]])), before, after, same, message }));
|
|
""",
|
|
str(MAIN),
|
|
)
|
|
assert out["decoded"] is True and out["count"] == 221
|
|
assert out["links"] == {
|
|
"V000001": [32, 4, True],
|
|
"V000002": [32, 6, True],
|
|
"V000003": [32, 8, True],
|
|
}
|
|
assert out["before"][:3] == [17, 12, 58]
|
|
assert out["after"][:3] != out["before"][:3]
|
|
assert out["same"] is True
|
|
assert out["message"].startswith("엑셀형 수량산출서 편집은 ESTX")
|