Files
Aislo/resources/tester/test_est_basic_prices.py

365 lines
14 KiB
Python

from test_est_rea_parity import run
def test_basic_prices_changes_for_material_and_labor():
result = run(
"math",
"""(()=>{
const p = EstNative.empty();
p.tables.EstMainDat = [
{ PkID: 1, Code: 'M1', Name: 'changed mat', LinkIndex: 5, GN: 2, TC: 200 },
{ PkID: 2, Code: 'L1', Name: 'changed lab', LinkIndex: 6, GN: 1, TC: 50 },
{ PkID: 4, Code: 'M2', Name: 'new mat', LinkIndex: 5, GN: 1, TC: 40 },
{ PkID: 5, Code: 'L2', Name: 'new lab', LinkIndex: 6, GN: 1, TC: 80 },
];
p.baseline = {
tables: {
EstMainDat: [
{ PkID: 1, Code: 'M1', Name: 'changed mat', LinkIndex: 5, GN: 1, TC: 100 },
{ PkID: 2, Code: 'L1', Name: 'changed lab', LinkIndex: 6, GN: 1, TC: 30 },
{ PkID: 3, Code: 'M3', Name: 'deleted mat', LinkIndex: 5, GN: 3, TC: 300 },
{ PkID: 6, Code: 'L3', Name: 'deleted lab', LinkIndex: 6, GN: 2, TC: 120 },
]
}
};
return {
matChanges: EstBasicPrices.changes(p, 'material'),
labChanges: EstBasicPrices.changes(p, 'labor'),
matPrint: EstForms.data('material-change', p),
labPrint: EstForms.data('labor-change', p),
};
})()""",
)
mat_rows = {row["Code"]: row for row in result["matChanges"]}
assert set(mat_rows) == {"M1", "M2", "M3"}
assert mat_rows["M1"]["difference"] == 100
assert mat_rows["M3"]["status"] == "삭제"
assert mat_rows["M2"]["status"] == "신규"
assert result["matPrint"]["rows"] == result["matChanges"]
lab_rows = {row["Code"]: row for row in result["labChanges"]}
assert set(lab_rows) == {"L1", "L2", "L3"}
assert lab_rows["L1"]["difference"] == 20
assert lab_rows["L3"]["status"] == "삭제"
assert lab_rows["L2"]["status"] == "신규"
assert result["labPrint"]["rows"] == result["labChanges"]
def test_basic_prices_usage_reports_applied_and_source_prices_without_mutation():
result = run(
"math",
"""(()=>{
const p = EstNative.empty();
p.tables.StuffcDat = [
{ Code: 'M1', Name: 'mat 1', ValueIndex: 0, Cost1: 1200 },
{ Code: 'M2', Name: 'mat 2', ValueIndex: 0, Cost1: 500 },
{ Code: 'M2', Name: 'mat 2 dup', ValueIndex: 0, Cost1: 600 },
];
p.tables.LaborcDat = [
{ Code: 'L1', Name: 'lab 1', ValueIndex: 0, Cost1: 200000 },
];
p.tables.EstMainDat = [
{ Code: 'M1', Name: 'mat 1', LinkIndex: 5, GN: 2, TU: 1000, GU: 1000, GC: 2000, TC: 2000 },
{ Code: 'M2', Name: 'dup mat', LinkIndex: 5, GN: 1, TU: 450, GU: 450, GC: 450, TC: 450 },
{ Code: 'M3', Name: 'missing mat', LinkIndex: 5, GN: 1, TU: 200, GU: 200, GC: 200, TC: 200 },
{ Code: 'L1', Name: 'lab 1', LinkIndex: 6, GN: 1, TU: 180000, NU: 180000, NC: 180000, TC: 180000 },
];
const before = JSON.stringify(p);
const matUsage = EstBasicPrices.usage(p, 'material');
const labUsage = EstBasicPrices.usage(p, 'labor');
const matPrint = EstForms.data('material-usage', p);
const labPrint = EstForms.data('labor-usage', p);
return { matUsage, labUsage, matPrint, labPrint, same: before === JSON.stringify(p) };
})()""",
)
assert result["same"]
assert [row["status"] for row in result["matUsage"]] == [
"연결됨",
"코드 중복",
"연결 없음",
]
assert result["matUsage"][0]["sourcePrice"] == 1200
assert result["matUsage"][0]["appliedPrice"] == 1000
assert result["matUsage"][0]["difference"] == -200
assert result["matUsage"][1]["sourcePrice"] is None
assert result["matPrint"]["rows"] == result["matUsage"]
assert len(result["labUsage"]) == 1
assert result["labUsage"][0]["status"] == "연결됨"
assert result["labUsage"][0]["sourcePrice"] == 200000
assert result["labUsage"][0]["appliedPrice"] == 180000
assert result["labUsage"][0]["difference"] == -20000
assert result["labPrint"]["rows"] == result["labUsage"]
def test_basic_prices_apply_row_and_all_prices_with_autocalc_modes():
result = run(
"math",
"""(()=>{
const p = EstNative.empty();
p.options.SourceRefresh = false;
p.options.AutoCalc = false; // 수동계산 모드
p.tables.StuffcDat = [
{ Code: 'M1', Name: 'mat 1', ValueIndex: 0, Cost1: 1500 },
{ Code: 'M2', Name: 'mat 2', ValueIndex: 0, Cost1: 3000 },
];
p.tables.LaborcDat = [
{ Code: 'L1', Name: 'lab 1', ValueIndex: 0, Cost1: 220000 },
];
p.tables.EstMainDat = [
{ Code: 'M1', Name: 'item mat 1', LinkIndex: 5, GN: 2, TU: 1000, GU: 1000, GC: 2000, TC: 2000, SCheck: 2 }, // SCheck=2: 노무비 몰아주기
{ Code: 'M2', Name: 'item mat 2', LinkIndex: 5, GN: 3, TU: 2000, GU: 2000, GC: 6000, TC: 6000 },
{ Code: 'L1', Name: 'item lab 1', LinkIndex: 6, GN: 2, TU: 200000, NU: 200000, NC: 400000, TC: 400000 },
];
// 1. 수동계산 상태에서 자재 행 0 단가 적용 (SCheck=2 보존 검증)
// AutoCalc=false이므로 단가는 NU=1500, TU=1500으로 갱신되나 기존 금액(GC=2000, TC=2000) 보존
EstBasicPrices.applyRowPrice(p, 0, 'material');
const afterRow0 = {
TU: p.tables.EstMainDat[0].TU,
GU: p.tables.EstMainDat[0].GU,
NU: p.tables.EstMainDat[0].NU,
GC: p.tables.EstMainDat[0].GC,
NC: p.tables.EstMainDat[0].NC,
TC: p.tables.EstMainDat[0].TC,
SCheck: p.tables.EstMainDat[0].SCheck,
};
// 2. 자재 일괄 적용: 남은 행 1 적용 (단가 3000 갱신, 수동계산 금액 GC=6000 보존)
const matCount = EstBasicPrices.applyAllPrices(p, 'material');
const afterMatAll = {
TU: p.tables.EstMainDat[1].TU,
GU: p.tables.EstMainDat[1].GU,
GC: p.tables.EstMainDat[1].GC,
TC: p.tables.EstMainDat[1].TC,
};
// 3. 노임 단가 적용: 행 2 적용 (단가 220000 갱신, 금액 NC=400000 보존)
const labCount = EstBasicPrices.applyAllPrices(p, 'labor');
const afterLab = {
TU: p.tables.EstMainDat[2].TU,
NU: p.tables.EstMainDat[2].NU,
NC: p.tables.EstMainDat[2].NC,
TC: p.tables.EstMainDat[2].TC,
};
// 4. AutoCalc=true로 전환 후 전체 재계산: GN(수량)을 반영한 금액으로 일괄 전파
p.options.AutoCalc = true;
const calculated = EstNative.calculate(p);
const afterRecalc = {
row0_NC: calculated.tables.EstMainDat[0].NC,
row0_TC: calculated.tables.EstMainDat[0].TC,
row1_GC: calculated.tables.EstMainDat[1].GC,
row1_TC: calculated.tables.EstMainDat[1].TC,
row2_NC: calculated.tables.EstMainDat[2].NC,
row2_TC: calculated.tables.EstMainDat[2].TC,
};
return { afterRow0, matCount, afterMatAll, labCount, afterLab, afterRecalc };
})()""",
)
# 행 0: SCheck=2 보존되어 NU=1500, GU=0, 금액은 수동계산 유지(GC=2000, NC=0, TC=2000)
assert result["afterRow0"]["TU"] == 1500
assert result["afterRow0"]["NU"] == 1500
assert result["afterRow0"]["GU"] == 0
assert result["afterRow0"]["GC"] == 2000
assert result["afterRow0"]["TC"] == 2000
assert result["afterRow0"]["SCheck"] == 2
# 행 1: 단가 3000, 수동 금액 6000 보존
assert result["matCount"] == 1
assert result["afterMatAll"]["TU"] == 3000
assert result["afterMatAll"]["GU"] == 3000
assert result["afterMatAll"]["GC"] == 6000
assert result["afterMatAll"]["TC"] == 6000
# 행 2: 단가 220000, 수동 금액 400000 보존
assert result["labCount"] == 1
assert result["afterLab"]["TU"] == 220000
assert result["afterLab"]["NU"] == 220000
assert result["afterLab"]["NC"] == 400000
assert result["afterLab"]["TC"] == 400000
# 전체 재계산 후: GN(수량) 반영 정상 산출
# 행 0: GN=2 -> NC=3000, TC=3000
assert result["afterRecalc"]["row0_NC"] == 3000
assert result["afterRecalc"]["row0_TC"] == 3000
# 행 1: GN=3 -> GC=9000, TC=9000
assert result["afterRecalc"]["row1_GC"] == 9000
assert result["afterRecalc"]["row1_TC"] == 9000
# 행 2: GN=2 -> NC=440000, TC=440000
assert result["afterRecalc"]["row2_NC"] == 440000
assert result["afterRecalc"]["row2_TC"] == 440000
def test_edge_basic_prices_material_and_labor_workflow_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.StuffcDat = [
{ Code: 'M01', Name: '시멘트 포대', Size: '40kg', Unite: '포', ValueIndex: 0, Cost1: 6000 },
];
p.tables.LaborcDat = [
{ Code: 'L01', Name: '특별인부', Unite: '인', ValueIndex: 0, Cost1: 180000 },
];
p.tables.EstMainDat = [
{ PkID: 301, Code: 'M01', Name: '시멘트 포대', Size: '40kg', Unite: '포', LinkIndex: 5, GN: 10, TU: 5000, GU: 5000, GC: 50000, TC: 50000 },
{ PkID: 302, Code: 'L01', Name: '특별인부', Unite: '인', LinkIndex: 6, GN: 5, TU: 160000, NU: 160000, NC: 800000, TC: 800000 },
];
EstApp.setProject(p);
EstApp.state.view = 'material-usage';
EstApp.render();
})()"""
)
# 2. 자재단가 사용 내역 화면 확인
assert page.locator("#view-title:has-text('자재단가 사용 내역')").is_visible()
btn_mat_apply = page.locator("button[data-basic-apply-row='0']")
assert btn_mat_apply.is_visible()
btn_mat_apply.click()
# 자재 단가 6,000으로 적용 확인
assert page.evaluate("EstApp.state.project.tables.EstMainDat[0].TU") == 6000
assert page.evaluate("EstApp.state.project.tables.EstMainDat[0].GU") == 6000
# 3. 노임단가 사용 내역 화면으로 전환
page.evaluate(
"""(() => {
EstApp.state.view = 'labor-usage';
EstApp.render();
})()"""
)
assert page.locator("#view-title:has-text('노임단가 사용 내역')").is_visible()
btn_lab_apply = page.locator("button[data-basic-apply-all='labor']")
assert btn_lab_apply.is_visible()
btn_lab_apply.click()
# 노임 단가 180,000으로 일괄 적용 확인
assert page.evaluate("EstApp.state.project.tables.EstMainDat[1].TU") == 180000
assert page.evaluate("EstApp.state.project.tables.EstMainDat[1].NU") == 180000
# 4. Undo(실행 취소) 검증
page.locator(".toolbar button[data-action='undo']").click()
assert page.evaluate("EstApp.state.project.tables.EstMainDat[1].TU") == 160000
page.locator(".toolbar button[data-action='undo']").click()
assert page.evaluate("EstApp.state.project.tables.EstMainDat[0].TU") == 5000
# 5. 기계경비 사용 내역 화면 전환 및 개별/일괄 적용, Undo 검증
page.evaluate(
"""(() => {
EstApp.navigate('machine-usage');
})()"""
)
assert page.locator("#view-title:has-text('기계경비 사용 내역')").is_visible()
browser.close()
def test_machine_price_usage_changes_and_safety_rejection():
result = run(
"math",
"""(()=>{
const p = structuredClone(project);
// 신규 항목(TC_ null) 추가하여 비교 검증
p.tables.MachTitleDat.push({
Code: 'E_NEW_999',
Name: '신규 기계',
Size: '10Ton',
Unite: 'hr',
TC: 50000,
NC: 30000,
GC: 10000,
JC: 10000,
TC_: null,
NC_: null,
GC_: null,
JC_: null
});
const usage = EstMachine.usage(p);
const changes = EstMachine.changes(p);
const newItemUsage = usage.find(u => u.Code === 'E_NEW_999');
const newItemChange = changes.find(c => c.Code === 'E_NEW_999');
const excUsage = usage.find(u => u.Code === 'E0000000000');
// 파괴적인 당초값 덮어쓰기 적용 차단 검증
let blockedSingle = 0, singleErrMsg = '';
try {
EstMachine.applyRowPrice(p, 0);
} catch (e) {
blockedSingle++;
singleErrMsg = e.message;
}
let blockedAll = 0, allErrMsg = '';
try {
EstMachine.applyAllPrices(p);
} catch (e) {
blockedAll++;
allErrMsg = e.message;
}
// 호표 단가 및 단가산출 금액 불변 보존 확인
const t0Unchanged = (p.tables.MachTitleDat[0].TC === 103047);
const costRow0 = p.tables.CostMainDat.find(r => r.LinkIndex === 105 && r.Code === 'E0000000000' && r.TC > 0);
const costUnchanged = (costRow0.TC === 1682.7);
return {
usageCount: usage.length,
changesCount: changes.length,
newItemUsage,
newItemChange,
excUsage,
blockedSingle,
singleErrMsg,
blockedAll,
allErrMsg,
t0Unchanged,
costUnchanged
};
})()""",
)
assert result["usageCount"] == 9
assert result["changesCount"] == 9
# 신규 항목 TC_ null 안전 처리 검증
assert result["newItemUsage"]["originalPrice"] is None
assert result["newItemUsage"]["difference"] is None
assert result["newItemUsage"]["status"] == "신규(당초없음)"
assert result["newItemUsage"]["canApply"] is False
assert result["newItemChange"]["status"] == "신규"
# 기존 항목 당초/현재 비교 검증
assert result["excUsage"]["appliedPrice"] == 103047
assert result["excUsage"]["originalPrice"] == 62207
assert result["excUsage"]["difference"] == 40840
assert result["excUsage"]["status"] == "당초차이"
assert result["excUsage"]["canApply"] is False
# 파괴적인 apply 차단 및 데이터 보존 검증
assert result["blockedSingle"] == 1
assert "덮어쓰기 적용은 지원하지 않습니다" in result["singleErrMsg"]
assert result["blockedAll"] == 1
assert "덮어쓰기 적용은 지원하지 않습니다" in result["allErrMsg"]
assert result["t0Unchanged"] is True
assert result["costUnchanged"] is True