- 원본 처리 근거대로 소계 줄 넣기·지우기, 내역 엔진에 소계 행 합 추가 - 공종 들여쓰기, 단가산출 들여쓰기, 단가산출 레벨 나무 보기 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99 lines
4.0 KiB
Python
99 lines
4.0 KiB
Python
"""EST 테스트 앱 하단으로 집계·공종 다듬기·단가산출 들여쓰기·레벨 확인(원본 f01a90·0x113eb44·0xf0770c·0xd10840)."""
|
|
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
FILES = [
|
|
"native-math.js",
|
|
"native-formula.js",
|
|
"native-machine.js",
|
|
"native-rows.js",
|
|
"native-text.js",
|
|
"native-quantity.cjs",
|
|
"native-wonga.js",
|
|
"native-engine.js",
|
|
"native-row-match.js",
|
|
"native-escalation.js",
|
|
"native-progress.js",
|
|
"native-business.js",
|
|
"native-review.js",
|
|
"native-resources.js",
|
|
"native-schedule.js",
|
|
"native-model.js",
|
|
"native-verify.js",
|
|
"native-est-tools.js",
|
|
]
|
|
|
|
|
|
def run_js(code: str):
|
|
runner = f"""
|
|
const fs = require('fs'), vm = require('vm');
|
|
const ctx = vm.createContext({{ structuredClone, console }});
|
|
for (const f of {json.dumps(FILES)})
|
|
vm.runInContext(fs.readFileSync('tools/est-test/' + f, 'utf8'), ctx, {{ filename: f }});
|
|
ctx.source = JSON.parse(fs.readFileSync('tools/est-test/sample.json', 'utf8'));
|
|
process.stdout.write(JSON.stringify(vm.runInContext({json.dumps(code)}, ctx)));
|
|
"""
|
|
return json.loads(subprocess.check_output(["node", "-e", runner], cwd=ROOT).decode("utf-8"))
|
|
|
|
|
|
def test_down_sum_inserts_nested_subtotals_and_engine_fills_them():
|
|
result = run_js(
|
|
"""(() => {
|
|
const calc = (p) => EstNative.calculate(EstModel.validate(p));
|
|
const p = calc(structuredClone(source));
|
|
const total = p.calculation.total, direct = p.calculation.direct.TC;
|
|
const added = EstEstTools.downSum(p);
|
|
const again = EstEstTools.downSum(p);
|
|
const done = calc(p);
|
|
const rows = done.tables.EstMainDat;
|
|
const subs = rows.map((r, i) => [r, i]).filter(([r]) => Number(r.LinkIndex) === 53);
|
|
const first = subs[0][1];
|
|
let head = first - 1; while (head >= 0 && !(Number(rows[head].Level) > 0)) head--;
|
|
const expected = rows.slice(head + 1, first).filter((r) => !Number(r.Level)).reduce((n, r) => n + Number(r.TC || 0), 0);
|
|
const removed = EstEstTools.upSum(done);
|
|
return { added: added > 0, again, firstOk: Math.abs(Number(rows[first].TC) - expected) < 1 && expected > 0,
|
|
name: rows[first].Name, total: done.calculation.total === total, direct: done.calculation.direct.TC === direct,
|
|
removed: removed === added, errors: done.calculation.errors.length };
|
|
})()"""
|
|
)
|
|
assert result == {
|
|
"added": True,
|
|
"again": 0,
|
|
"firstOk": True,
|
|
"name": "소 계",
|
|
"total": True,
|
|
"direct": True,
|
|
"removed": True,
|
|
"errors": 0,
|
|
}
|
|
|
|
|
|
def test_left_space_indent_and_level_tree():
|
|
result = run_js(
|
|
"""(() => {
|
|
const p = { options: {}, tables: { EstMainDat: [
|
|
{ Level: 3, Name: 'A' }, { Level: 2, Name: 'B' }, { Level: 0, Name: 'b1' }, { Level: 2, Name: 'C' },
|
|
{ Level: 1, Name: 'D' }, { Level: 0, Name: 'd1' }, { Level: 3, Name: 'E' }, { LinkIndex: 54, Level: 0 } ],
|
|
CostMainDat: [
|
|
{ Index: 1, Code: 'D1', Name: '터파기', Size: '토사' },
|
|
{ LineData: ' 가 = \x123\x12' },
|
|
{ LineData: ' 1', Level: 0 }, { LineData: ' ───── = \x120.5\x12' }, { LineData: ' 2' },
|
|
{ Level: 2, LineData: ' 큰 공종' },
|
|
{ SCheck: -1, LineData: '노무 \x1212,345\x12' } ] } };
|
|
EstEstTools.leftSpace(p, 2);
|
|
EstEstTools.costIndent(p, 4);
|
|
const tree = EstEstTools.levelTree(p, 'D1');
|
|
return { left: p.tables.EstMainDat.map((r) => r.LeftSpace), lines: p.tables.CostMainDat.map((r) => r.LineData),
|
|
tree: tree.title, items: tree.lines.map((l) => l.text.replace(/ {2,}/g, '_')) };
|
|
})()"""
|
|
)
|
|
assert result["left"] == [0, 2, 4, 2, 4, 6, 0, 0]
|
|
assert result["lines"][1] == " 가 = \x123\x12"
|
|
assert result["lines"][2:5] == [" 1", " ───── = \x120.5\x12", " 2"]
|
|
assert result["lines"][5] == " 큰 공종"
|
|
assert result["tree"] == "터파기 (토사)"
|
|
assert result["items"] == ["큰 공종_[2]", "노무비 : 12,345"]
|