Files
Aislo/resources/tester/test_est_standard.py
T

314 lines
13 KiB
Python

from test_est_rea_parity import run
def test_standard_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:21,GN:2,TC:200},
{PkID:2,Code:'B',Name:'changed link',LinkIndex:5,GN:1,TC:50},
{PkID:4,Code:'D',Name:'new',LinkIndex:21,GN:1,TC:40}];
p.baseline={tables:{EstMainDat:[
{PkID:1,Code:'A',Name:'changed',LinkIndex:21,GN:1,TC:100},
{PkID:2,Code:'B',Name:'changed link',LinkIndex:21,GN:1,TC:50},
{PkID:3,Code:'C',Name:'deleted',LinkIndex:21,GN:3,TC:300},
{PkID:5,Code:'E',Name:'other',LinkIndex:5,GN:1,TC:10}]}};
return {rows:EstStandard.changes(p),print:EstForms.data('standard-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_standard_usage_reports_applied_and_source_prices_without_mutation():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.tables.SilJukDat=[{Code:'A',Name:'source',ValueIndex:4,InputTC:120},
{Code:'B',ValueIndex:4,InputTC:50},{Code:'B',ValueIndex:4,InputTC:60}];
p.tables.EstMainDat=[{Code:'A',Name:'work',LinkIndex:21,GN:2,TU:100,TC:200},
{Code:'B',Name:'duplicate',LinkIndex:21,GN:1,TU:45,TC:45},
{Code:'C',Name:'missing',LinkIndex:21,GN:1,TU:20,TC:20},
{Code:'A',Name:'material',LinkIndex:5,GN:1,TU:10}];
const before=JSON.stringify(p), rows=EstStandard.usage(p), print=EstForms.data('standard-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"] == 120
assert result["rows"][0]["appliedPrice"] == 100
assert result["rows"][0]["difference"] == -20
assert result["rows"][1]["sourcePrice"] is None
assert result["print"]["rows"] == result["rows"]
def test_standard_apply_row_and_all_prices():
result = run(
"math",
"""(()=>{
const p = EstNative.empty();
p.options.SourceRefresh = false;
p.options.AutoCalc = false;
p.tables.SilJukDat = [
{ Code: 'A', Name: 'standard A', ValueIndex: 4, InputTC: 150, InputGC: 50, InputNC: 70, InputJC: 30, InputSC: 0 },
{ Code: 'B', Name: 'standard B', ValueIndex: 4, InputTC: 300, InputSC: 300 },
{ Code: 'D', Name: 'dup', ValueIndex: 4, InputTC: 50 },
{ Code: 'D', Name: 'dup', ValueIndex: 4, InputTC: 60 },
];
p.tables.EstMainDat = [
{ Code: 'A', Name: 'item A', LinkIndex: 21, GN: 2, TU: 100, GU: 30, NU: 50, JU: 20, GC: 60, NC: 100, JC: 40, TC: 200, SCheck: 1 },
{ Code: 'B', Name: 'item B', LinkIndex: 21, GN: 3, TU: 200, SU: 200, SC: 600, TC: 600 },
{ Code: 'C', Name: 'item C', LinkIndex: 21, GN: 1, TU: 50, SU: 50, SC: 50, TC: 50 },
{ Code: 'D', Name: 'item D', LinkIndex: 21, GN: 1, TU: 40, SU: 40, SC: 40, TC: 40 },
{ Code: 'OTHER', Name: 'other row', LinkIndex: 5, GN: 5, TU: 10, GC: 50, TC: 50 },
];
const initialUsage = EstStandard.usage(p);
const otherBefore = JSON.stringify(p.tables.EstMainDat[4]);
// 행 0(SCheck=1: 재료비 몰아주기 설정)에 단가 적용:
// SCheck=1이 보존되어 GU=150, NU=0, JU=0, SU=0 및 GN=2 -> GC=300, TC=300
EstStandard.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으로 초기화한 뒤 다시 적용하면 원천의 재료/노무/경비 비목 분할(50/70/30)이 유지됨
p.tables.EstMainDat[0].SCheck = 0;
EstStandard.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 { EstStandard.applyRowPrice(p, 2); } catch { errMissing = true; }
let errDup = false;
try { EstStandard.applyRowPrice(p, 3); } catch { errDup = true; }
// 남은 대상인 행 1(수량 3)에 대해 applyAllPrices 적용
const appliedCount = EstStandard.applyAllPrices(p);
const afterAll = {
row1_TU: p.tables.EstMainDat[1].TU,
row1_SU: p.tables.EstMainDat[1].SU,
row1_SC: p.tables.EstMainDat[1].SC,
row1_TC: p.tables.EstMainDat[1].TC,
row2_TU: p.tables.EstMainDat[2].TU,
row3_TU: p.tables.EstMainDat[3].TU,
otherPreserved: JSON.stringify(p.tables.EstMainDat[4]) === otherBefore,
};
const finalUsage = EstStandard.usage(p);
// 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_SC: calculated.tables.EstMainDat[1].SC,
row1_TC: calculated.tables.EstMainDat[1].TC,
};
return { initialUsage, afterRow0, afterRow0_split, errMissing, errDup, appliedCount, afterAll, finalUsage, afterRecalc };
})()""",
)
assert result["initialUsage"][0]["canApply"] is True
assert result["initialUsage"][1]["canApply"] is True
assert result["initialUsage"][2]["canApply"] is False
assert result["initialUsage"][3]["canApply"] is False
assert result["afterRow0"] == {
"row0_TU": 150,
"row0_GU": 150,
"row0_NU": 0,
"row0_JU": 0,
"row0_GC": 60,
"row0_TC": 200,
"row0_SCheck": 1,
"otherPreserved": True,
"sourceRefresh": False,
"autoCalc": False,
}
assert result["afterRow0_split"] == {
"row0_TU": 150,
"row0_GU": 50,
"row0_NU": 70,
"row0_JU": 30,
"row0_GC": 60,
"row0_NC": 100,
"row0_JC": 40,
"row0_TC": 200,
}
assert result["errMissing"] is True
assert result["errDup"] is True
assert result["appliedCount"] == 1
assert result["afterAll"] == {
"row1_TU": 300,
"row1_SU": 300,
"row1_SC": 600,
"row1_TC": 600,
"row2_TU": 50,
"row3_TU": 40,
"otherPreserved": True,
}
assert result["afterRecalc"] == {
"row0_GC": 100,
"row0_NC": 140,
"row0_JC": 60,
"row0_TC": 300,
"row1_SC": 900,
"row1_TC": 900,
}
assert result["finalUsage"][0]["canApply"] is False
assert result["finalUsage"][1]["canApply"] is False
def test_edge_standard_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.SilJukDat = [
{ Code: 'S001', Name: '표준토공', Size: '일반', Unite: 'm3', ValueIndex: 4, InputTC: 15000 },
{ Code: 'S002', Name: '표준포장', Size: 't=10cm', Unite: 'm2', ValueIndex: 4, InputTC: 32000 },
];
p.tables.EstMainDat = [
{ PkID: 101, Code: 'S001', Name: '표준토공', Size: '일반', Unite: 'm3', LinkIndex: 21, GN: 10, TU: 12000, SU: 12000, SC: 120000, TC: 120000 },
{ PkID: 102, Code: 'S002', Name: '표준포장', Size: 't=10cm', Unite: 'm2', LinkIndex: 21, GN: 5, TU: 30000, SU: 30000, SC: 150000, TC: 150000 },
];
EstApp.setProject(p);
EstApp.state.view = 'standard';
EstApp.render();
})()"""
)
# 2. 표준시장단가 사용 내역 화면 확인
assert page.locator("#view-title:has-text('표준시장단가 사용 내역')").is_visible()
btn_apply_all = page.locator("button[data-standard-apply-all]")
assert btn_apply_all.is_visible()
assert "기초단가 일괄 적용 (2건)" in btn_apply_all.text_content()
# 3. 개별 단가 적용 버튼 클릭 (1행 S001)
btn_row_apply = page.locator("button[data-standard-apply-row='0']")
assert btn_row_apply.is_visible()
btn_row_apply.click()
# 1행이 15,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 == 15000
assert tc_row0 == 150000
# 버튼 라벨이 (1건)으로 변경되었는지 확인
assert "기초단가 일괄 적용 (1건)" in btn_apply_all.text_content()
# 4. 일괄 적용 버튼 클릭 (남은 1건 S002 적용)
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 == 32000
assert tc_row1 == 160000
# 이제 일괄 적용 버튼이 disabled 상태인지 확인
assert btn_apply_all.is_disabled()
# 5. Undo(실행 취소) 검증
page.locator(".toolbar button[data-action='undo']").click()
# 1단계 취소 시 2행은 원래대로 30,000 복귀
assert page.evaluate("EstApp.state.project.tables.EstMainDat[1].TU") == 30000
page.locator(".toolbar button[data-action='undo']").click()
# 2단계 취소 시 1행도 원래대로 12,000 복귀
assert page.evaluate("EstApp.state.project.tables.EstMainDat[0].TU") == 12000
browser.close()
def test_standard_manual_mode_autocalc_false_and_recalc_switch():
result = run(
"math",
"""(()=>{
const p = EstNative.empty();
p.options.SourceRefresh = false;
p.options.AutoCalc = false; // 수동계산 모드
p.tables.SilJukDat = [
{ Code: 'S10', Name: '표준단가 10', ValueIndex: 4, InputTC: 20000, InputSC: 20000 },
];
p.tables.EstMainDat = [
{ Level: 1, Code: '', Name: '공종1', TC: 50000 },
{ Code: 'S10', Name: '품목 10', LinkIndex: 21, GN: 5, TU: 10000, SU: 10000, SC: 50000, TC: 50000 },
];
// 1. 수동계산 상태에서 applyRowPrice 실행
// 해당 행(인덱스 1)의 단가(TU, SU)는 20,000으로 갱신되나,
// AutoCalc=false이므로 자체 금액(SC=50000, TC=50000) 및 상위 공종 행(Level 1, TC=50000) 금액이 모두 보존됨
EstStandard.applyRowPrice(p, 1);
const afterManual = {
row1_TU: p.tables.EstMainDat[1].TU,
row1_SC: p.tables.EstMainDat[1].SC,
row1_TC: p.tables.EstMainDat[1].TC,
row0_Level1_TC: p.tables.EstMainDat[0].TC,
};
// 2. 전체 재계산(AutoCalc=true 전환 및 EstNative.calculate 실행)
p.options.AutoCalc = true;
const calculated = EstNative.calculate(p);
const afterRecalc = {
row1_TU: calculated.tables.EstMainDat[1].TU,
row1_SC: calculated.tables.EstMainDat[1].SC,
row1_TC: calculated.tables.EstMainDat[1].TC,
row0_Level1_TC: calculated.tables.EstMainDat[0].TC,
};
return { afterManual, afterRecalc };
})()""",
)
assert result["afterManual"]["row1_TU"] == 20000
assert result["afterManual"]["row1_SC"] == 50000
assert result["afterManual"]["row1_TC"] == 50000
# 수동 상태에서는 자체 금액 및 상위 공종(Level 1) 금액 모두 보존됨
assert result["afterManual"]["row0_Level1_TC"] == 50000
# 전체 재계산 전환 후 수량(GN=5)을 반영해 자체 금액 및 상위 공종 정상 합산됨
assert result["afterRecalc"]["row1_TU"] == 20000
assert result["afterRecalc"]["row1_SC"] == 100000
assert result["afterRecalc"]["row1_TC"] == 100000
assert result["afterRecalc"]["row0_Level1_TC"] == 100000