Files
Aislo/resources/tester/test_est_progress_wonga.py
T
eomsangdonandClaude Opus 5.5 892285843d feat(est-test): 워드형 수량산출서 표 보존과 편집 도구
- 가져오기가 WPT 표를 버리던 결함 고침: 표 줄(칸·수량 표지·수식·세로병합) 보존, 문서 읽기 native-quantity-wpt.cjs 로 분리
- 표 셀 합·수량 지정·병합/분리·표 만들기·표 모양, 문단·그림·쪽 삽입/삭제·품명/규격 지정·공종명칭 입력·테두리선·산출근거 양식
- 수량 불러오기·수량목록보기·선택수량복사·내역서 수량붙여넣기(연결)
- ESTX 저장은 표 줄을 건너 문단만 맞춤, 표·서식 편집은 거부

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 18:35:50 +09:00

97 lines
3.9 KiB
Python

"""EST 테스트 앱 기성 원가계산서 편집(원본 TfrmWongaAccomH/V 팝업) — 회차별 원가 서식 사본."""
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-wpt.cjs",
"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-wonga-tools.js",
"native-progress-wonga.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_progress_wonga_edits_apply_to_round_copy_only():
result = run_js(
r"""(() => {
const p = EstNative.calculate(EstModel.validate(structuredClone(source)));
const inputs = {};
p.tables.EstMainDat.forEach((row, i) => {
if (row.Code && !Number(row.Level)) inputs[EstBusiness.rowKey(row, i)] = { Gum: 50, GumMode: 'ratio' };
});
p.progress = [{ id: 'one', name: '1회', inputs }];
const round = p.progress[0], ops = EstProgressWonga.ops;
const calc = () => EstProgress.calculate(p, 'one');
const plain = (t) => String(t).replace(/\s+/g, '');
const base = calc();
const anjun = base.costs.findIndex((r) => plain(r.Name).includes('산업안전') && Number(r.CalcIndex) !== 2);
const total = base.costs.findIndex((r) => Number(r.LinkIndex) === 55);
const out = { errors: base.errors, baseTotal: base.total, length: base.costs.length };
ops.insert(p, round, 1);
const ins = calc();
out.inserted = [ins.costs.length, ins.costs[1].Name, ins.costs[1].ContractCost, ins.costs[2].ContractCost === base.costs[1].ContractCost, p.tables.Wonga.length];
ops.undo(p, round);
out.undone = ['wonga' in round, 'wongaUndo' in round];
ops.position(p, round, anjun, 2, 3);
const cut = calc();
out.anjun = [base.costs[anjun].GumCost, cut.costs[anjun].GumCost, cut.costs[anjun].ContractCost];
ops.total(p, round, 4);
const fixed = calc();
out.total = [cut.total, fixed.total, fixed.errors];
ops.clear(p, round, 0);
const r0 = round.wonga[0];
out.cleared = [r0.VSusic, r0.Cost, r0.Youl, r0.Diget, r0.Name !== ''];
ops.init(p, round);
out.init = ['wonga' in round, 'wongaTotalDigits' in round, calc().total === base.total];
ops.undo(p, round);
out.reinit = Array.isArray(round.wonga);
ops.init(p, round);
round.locked = true;
try { ops.append(p, round); } catch (e) { out.locked = e.message; }
return out;
})()"""
)
assert result["errors"] == []
assert result["baseTotal"] > 0
n = result["length"]
assert result["inserted"] == [n + 1, "", 0, True, n]
assert result["undone"] == [False, False]
before, after, contract = result["anjun"]
assert after == before // 1000 * 1000 and after != before and contract > 0
cut_total, fixed_total, errors = result["total"]
assert fixed_total == cut_total // 10000 * 10000 and errors == []
assert result["cleared"] == ["", 0, "", 0, True]
assert result["init"] == [False, False, True]
assert result["reinit"] is True
assert result["locked"] == "확정한 회차는 고칠 수 없습니다."