263 lines
12 KiB
Python
263 lines
12 KiB
Python
from test_est_rea_parity import run
|
|
|
|
|
|
def test_estimate_changes_include_new_deleted_and_changed_link_types():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.tables.EstMainDat=[
|
|
{PkID:1,Code:'A',Name:'changed',LinkIndex:101,GN:2,TC:200},
|
|
{PkID:2,Code:'B',Name:'changed link',LinkIndex:5,GN:1,TC:50},
|
|
{PkID:4,Code:'D',Name:'new',LinkIndex:101,GN:1,TC:40}];
|
|
p.baseline={tables:{EstMainDat:[
|
|
{PkID:1,Code:'A',Name:'changed',LinkIndex:101,GN:1,TC:100},
|
|
{PkID:2,Code:'B',Name:'changed link',LinkIndex:101,GN:1,TC:50},
|
|
{PkID:3,Code:'C',Name:'deleted',LinkIndex:101,GN:3,TC:300},
|
|
{PkID:5,Code:'E',Name:'other',LinkIndex:5,GN:1,TC:10}]}};
|
|
return {rows:EstEstimate.changes(p),print:EstForms.data('estimate-change',p)};
|
|
})()""",
|
|
)
|
|
rows = {row["Code"]: row for row in result["rows"]}
|
|
assert set(rows) == {"A", "B", "C", "D"}
|
|
assert rows["A"]["difference"] == 100
|
|
assert rows["B"]["status"] == "변경"
|
|
assert rows["C"]["status"] == "삭제" and rows["C"]["index"] is None
|
|
assert rows["D"]["status"] == "신규"
|
|
assert result["print"]["rows"] == result["rows"]
|
|
|
|
|
|
def test_estimate_usage_reports_applied_and_source_prices_without_mutation():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.tables.EstiMateDat=[
|
|
{Code:'A',Name:'source',InputTC:12000,InputJC:12000},
|
|
{Code:'B',InputTC:5000,InputJC:5000},{Code:'B',InputTC:6000,InputJC:6000}
|
|
];
|
|
p.tables.EstMainDat=[
|
|
{Code:'A',Name:'work',LinkIndex:101,GN:2,TU:10000,JC:20000,TC:20000},
|
|
{Code:'B',Name:'duplicate',LinkIndex:101,GN:1,TU:4500,JC:4500,TC:4500},
|
|
{Code:'C',Name:'missing',LinkIndex:101,GN:1,TU:2000,JC:2000,TC:2000},
|
|
{Code:'A',Name:'material',LinkIndex:5,GN:1,TU:1000}
|
|
];
|
|
const before=JSON.stringify(p), rows=EstEstimate.usage(p), print=EstForms.data('estimate-usage',p);
|
|
return {rows,print,same:before===JSON.stringify(p)};
|
|
})()""",
|
|
)
|
|
assert result["same"]
|
|
assert [row["status"] for row in result["rows"]] == ["연결됨", "코드 중복", "연결 없음"]
|
|
assert result["rows"][0]["sourcePrice"] == 12000
|
|
assert result["rows"][0]["appliedPrice"] == 10000
|
|
assert result["rows"][0]["difference"] == -2000
|
|
assert result["rows"][1]["sourcePrice"] is None
|
|
assert result["print"]["rows"] == result["rows"]
|
|
|
|
|
|
def test_estimate_apply_row_and_all_prices():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p = EstNative.empty();
|
|
p.options.SourceRefresh = false;
|
|
p.options.AutoCalc = false;
|
|
p.tables.EstiMateDat = [
|
|
{ Code: 'A', Name: 'estimate A', InputTC: 15000, InputGC: 5000, InputNC: 7000, InputJC: 3000, InputSC: 0 },
|
|
{ Code: 'B', Name: 'estimate B', InputTC: 30000, InputJC: 30000 },
|
|
{ Code: 'D', Name: 'dup', InputTC: 5000 },
|
|
{ Code: 'D', Name: 'dup', InputTC: 6000 },
|
|
];
|
|
p.tables.EstMainDat = [
|
|
{ Code: 'A', Name: 'item A', LinkIndex: 101, GN: 2, TU: 10000, GU: 3000, NU: 5000, JU: 2000, GC: 6000, NC: 10000, JC: 4000, TC: 20000, SCheck: 1 },
|
|
{ Code: 'B', Name: 'item B', LinkIndex: 101, GN: 3, TU: 20000, JU: 20000, JC: 60000, TC: 60000 },
|
|
{ Code: 'C', Name: 'item C', LinkIndex: 101, GN: 1, TU: 5000, JU: 5000, JC: 5000, TC: 5000 },
|
|
{ Code: 'D', Name: 'item D', LinkIndex: 101, GN: 1, TU: 4000, JU: 4000, JC: 4000, TC: 4000 },
|
|
{ Code: 'OTHER', Name: 'other row', LinkIndex: 5, GN: 5, TU: 1000, GC: 5000, TC: 5000 },
|
|
];
|
|
const initialUsage = EstEstimate.usage(p);
|
|
const otherBefore = JSON.stringify(p.tables.EstMainDat[4]);
|
|
|
|
// 행 0(SCheck=1: 재료비 몰아주기 설정)에 단가 적용:
|
|
// SCheck=1이 보존되어 GU=15000, NU=0, JU=0, SU=0 및 GN=2 -> GC=30000, TC=30000
|
|
EstEstimate.applyRowPrice(p, 0);
|
|
const afterRow0 = {
|
|
row0_TU: p.tables.EstMainDat[0].TU,
|
|
row0_GU: p.tables.EstMainDat[0].GU,
|
|
row0_NU: p.tables.EstMainDat[0].NU,
|
|
row0_JU: p.tables.EstMainDat[0].JU,
|
|
row0_GC: p.tables.EstMainDat[0].GC,
|
|
row0_TC: p.tables.EstMainDat[0].TC,
|
|
row0_SCheck: p.tables.EstMainDat[0].SCheck,
|
|
otherPreserved: JSON.stringify(p.tables.EstMainDat[4]) === otherBefore,
|
|
sourceRefresh: p.options.SourceRefresh,
|
|
autoCalc: p.options.AutoCalc,
|
|
};
|
|
|
|
// SCheck를 0으로 초기화한 뒤 다시 적용하면 원천의 재료/노무/경비 비목 분할(5000/7000/3000)이 유지됨
|
|
p.tables.EstMainDat[0].SCheck = 0;
|
|
EstEstimate.applyRowPrice(p, 0);
|
|
const afterRow0_split = {
|
|
row0_TU: p.tables.EstMainDat[0].TU,
|
|
row0_GU: p.tables.EstMainDat[0].GU,
|
|
row0_NU: p.tables.EstMainDat[0].NU,
|
|
row0_JU: p.tables.EstMainDat[0].JU,
|
|
row0_GC: p.tables.EstMainDat[0].GC,
|
|
row0_NC: p.tables.EstMainDat[0].NC,
|
|
row0_JC: p.tables.EstMainDat[0].JC,
|
|
row0_TC: p.tables.EstMainDat[0].TC,
|
|
};
|
|
|
|
let errMissing = false;
|
|
try { EstEstimate.applyRowPrice(p, 2); } catch { errMissing = true; }
|
|
let errDup = false;
|
|
try { EstEstimate.applyRowPrice(p, 3); } catch { errDup = true; }
|
|
|
|
// 남은 대상인 행 1(수량 3)에 대해 applyAllPrices 적용
|
|
const appliedCount = EstEstimate.applyAllPrices(p);
|
|
const afterRow1 = {
|
|
row1_TU: p.tables.EstMainDat[1].TU,
|
|
row1_JU: p.tables.EstMainDat[1].JU,
|
|
row1_JC: p.tables.EstMainDat[1].JC,
|
|
row1_TC: p.tables.EstMainDat[1].TC,
|
|
};
|
|
|
|
// AutoCalc=true 전환 후 전체 재계산: 금액이 수량(GN)을 반영해 갱신됨
|
|
p.options.AutoCalc = true;
|
|
const calculated = EstNative.calculate(p);
|
|
const afterRecalc = {
|
|
row0_GC: calculated.tables.EstMainDat[0].GC,
|
|
row0_NC: calculated.tables.EstMainDat[0].NC,
|
|
row0_JC: calculated.tables.EstMainDat[0].JC,
|
|
row0_TC: calculated.tables.EstMainDat[0].TC,
|
|
row1_JC: calculated.tables.EstMainDat[1].JC,
|
|
row1_TC: calculated.tables.EstMainDat[1].TC,
|
|
};
|
|
|
|
return {
|
|
initialCanApply: initialUsage.filter(u => u.canApply).length,
|
|
afterRow0,
|
|
afterRow0_split,
|
|
errMissing,
|
|
errDup,
|
|
appliedCount,
|
|
afterRow1,
|
|
afterRecalc,
|
|
};
|
|
})()""",
|
|
)
|
|
|
|
assert result["initialCanApply"] == 2
|
|
# AutoCalc=false 시 단가는 GU=15000으로 갱신되나 기존 금액(GC=6000, TC=20000) 보존
|
|
assert result["afterRow0"]["row0_TU"] == 15000
|
|
assert result["afterRow0"]["row0_GU"] == 15000
|
|
assert result["afterRow0"]["row0_NU"] == 0
|
|
assert result["afterRow0"]["row0_JU"] == 0
|
|
assert result["afterRow0"]["row0_GC"] == 6000
|
|
assert result["afterRow0"]["row0_TC"] == 20000
|
|
assert result["afterRow0"]["row0_SCheck"] == 1
|
|
assert result["afterRow0"]["otherPreserved"] is True
|
|
assert result["afterRow0"]["sourceRefresh"] is False
|
|
assert result["afterRow0"]["autoCalc"] is False
|
|
|
|
# SCheck=0 시 원천 비목 분할(5000/7000/3000) 유지 및 기존 금액(GC=6000, NC=10000, JC=4000, TC=20000) 보존
|
|
assert result["afterRow0_split"]["row0_TU"] == 15000
|
|
assert result["afterRow0_split"]["row0_GU"] == 5000
|
|
assert result["afterRow0_split"]["row0_NU"] == 7000
|
|
assert result["afterRow0_split"]["row0_JU"] == 3000
|
|
assert result["afterRow0_split"]["row0_GC"] == 6000
|
|
assert result["afterRow0_split"]["row0_NC"] == 10000
|
|
assert result["afterRow0_split"]["row0_JC"] == 4000
|
|
assert result["afterRow0_split"]["row0_TC"] == 20000
|
|
|
|
assert result["errMissing"] is True
|
|
assert result["errDup"] is True
|
|
assert result["appliedCount"] == 1
|
|
# 행 1: 단가 30000 갱신, 수동계산 상태이므로 기존 금액(JC=60000, TC=60000) 보존
|
|
assert result["afterRow1"]["row1_TU"] == 30000
|
|
assert result["afterRow1"]["row1_JU"] == 30000
|
|
assert result["afterRow1"]["row1_JC"] == 60000
|
|
assert result["afterRow1"]["row1_TC"] == 60000
|
|
|
|
# AutoCalc=true 재계산 후 GN(수량)을 반영한 금액으로 정상 계산
|
|
assert result["afterRecalc"]["row0_GC"] == 10000
|
|
assert result["afterRecalc"]["row0_NC"] == 14000
|
|
assert result["afterRecalc"]["row0_JC"] == 6000
|
|
assert result["afterRecalc"]["row0_TC"] == 30000
|
|
assert result["afterRecalc"]["row1_JC"] == 90000
|
|
assert result["afterRecalc"]["row1_TC"] == 90000
|
|
|
|
|
|
def test_edge_estimate_usage_apply_price_and_undo_when_server_running():
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
with sync_playwright() as p:
|
|
browser = p.chromium.launch(channel="msedge", headless=True)
|
|
page = browser.new_page()
|
|
try:
|
|
page.goto("http://100.65.141.15:5175", timeout=5000)
|
|
page.wait_for_function("() => Boolean(EstApp.state.project)")
|
|
except Exception:
|
|
browser.close()
|
|
import pytest
|
|
|
|
pytest.skip("EST 5175 server not reachable")
|
|
|
|
# 1. 테스트 프로젝트 데이터 세팅 (견적단가 및 내역 행 주입)
|
|
page.evaluate(
|
|
"""(() => {
|
|
const p = EstNative.empty();
|
|
p.options.SourceRefresh = false;
|
|
p.tables.EstiMateDat = [
|
|
{ Code: 'E001', Name: '견적비목 1', Size: '규격A', Unite: '식', InputTC: 50000, InputJC: 50000 },
|
|
{ Code: 'E002', Name: '견적비목 2', Size: '규격B', Unite: '식', InputTC: 80000, InputJC: 80000 },
|
|
];
|
|
p.tables.EstMainDat = [
|
|
{ PkID: 201, Code: 'E001', Name: '견적비목 1', Size: '규격A', Unite: '식', LinkIndex: 101, GN: 10, TU: 40000, JU: 40000, JC: 400000, TC: 400000 },
|
|
{ PkID: 202, Code: 'E002', Name: '견적비목 2', Size: '규격B', Unite: '식', LinkIndex: 101, GN: 5, TU: 70000, JU: 70000, JC: 350000, TC: 350000 },
|
|
];
|
|
EstApp.setProject(p);
|
|
EstApp.state.view = 'estimate-usage';
|
|
EstApp.render();
|
|
})()"""
|
|
)
|
|
|
|
# 2. 견적단가 사용 내역 화면 확인
|
|
assert page.locator("#view-title:has-text('견적단가 사용 내역')").is_visible()
|
|
btn_apply_all = page.locator("button[data-estimate-apply-all]")
|
|
assert btn_apply_all.is_visible()
|
|
assert "기초단가 일괄 적용 (2건)" in btn_apply_all.text_content()
|
|
|
|
# 3. 개별 단가 적용 버튼 클릭 (1행 E001)
|
|
btn_row_apply = page.locator("button[data-estimate-apply-row='0']")
|
|
assert btn_row_apply.is_visible()
|
|
btn_row_apply.click()
|
|
|
|
# 1행이 50,000으로 갱신되었는지 확인
|
|
tu_row0 = page.evaluate("EstApp.state.project.tables.EstMainDat[0].TU")
|
|
tc_row0 = page.evaluate("EstApp.state.project.tables.EstMainDat[0].TC")
|
|
assert tu_row0 == 50000
|
|
assert tc_row0 == 500000
|
|
|
|
# 버튼 라벨이 (1건)으로 변경되었는지 확인
|
|
assert "기초단가 일괄 적용 (1건)" in btn_apply_all.text_content()
|
|
|
|
# 4. 일괄 적용 버튼 클릭 (남은 1건 E002 적용)
|
|
btn_apply_all.click()
|
|
tu_row1 = page.evaluate("EstApp.state.project.tables.EstMainDat[1].TU")
|
|
tc_row1 = page.evaluate("EstApp.state.project.tables.EstMainDat[1].TC")
|
|
assert tu_row1 == 80000
|
|
assert tc_row1 == 400000
|
|
|
|
# 이제 일괄 적용 버튼은 disabled 상태인지 확인
|
|
assert btn_apply_all.is_disabled()
|
|
|
|
# 5. Undo(실행 취소) 검증
|
|
page.locator(".toolbar button[data-action='undo']").click()
|
|
# 1단계 취소 후 2행은 원래대로 70,000 복구
|
|
assert page.evaluate("EstApp.state.project.tables.EstMainDat[1].TU") == 70000
|
|
|
|
page.locator(".toolbar button[data-action='undo']").click()
|
|
# 2단계 취소 후 1행도 원래대로 40,000 복구
|
|
assert page.evaluate("EstApp.state.project.tables.EstMainDat[0].TU") == 40000
|
|
|
|
browser.close()
|