248 lines
11 KiB
Python
248 lines
11 KiB
Python
# Annual allocation and resource expansion regression cases.
|
||
|
||
from test_est_rea_parity import run
|
||
|
||
|
||
def test_annual_formulas_split_component_amounts_and_preserve_remainder():
|
||
result = run(
|
||
"math",
|
||
"""(()=>{
|
||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||
p.tables.EstMainDat=[{PkID:1,Code:'R',Name:'item',GN:1,NU:5,GU:3},{LinkIndex:54}];
|
||
p.annual=[{name:'1차',formula:'[기준]*0.5'},{name:'2차',formula:'[기준]-[1차]'}];
|
||
const q=EstNative.calculate(p),before=JSON.stringify(q);
|
||
return {output:EstBusiness.annual(q),preserved:before===JSON.stringify(q)};
|
||
})()""",
|
||
)
|
||
assert result["preserved"]
|
||
output = result["output"]
|
||
assert output["errors"] == []
|
||
first, second = [item["project"] for item in output["results"]]
|
||
assert first["tables"]["EstMainDat"][0]["GN"] == 0.5
|
||
assert second["tables"]["EstMainDat"][0]["GN"] == 0.5
|
||
assert [item["calculation"]["direct"]["TC"] for item in [first, second]] == [3, 5]
|
||
assert first["tables"]["EstMainDat"][0]["NC"] == 2
|
||
assert second["tables"]["EstMainDat"][0]["NC"] == 3
|
||
assert second["tables"]["EstMainDat"][0]["GC"] == 2
|
||
assert all(first["tables"]["EstMainDat"][0][key] == 0 for key in ["TU", "NU", "GU", "JU"])
|
||
|
||
|
||
def test_annual_fixed_amount_with_zero_quantity_and_duplicate_names():
|
||
result = run(
|
||
"math",
|
||
"""(()=>{
|
||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||
p.tables.EstMainDat=[{PkID:1,Code:'R',GN:0,NC:3,GC:7,TC:10,CalcIndex:1},{LinkIndex:54}];
|
||
p.annual=[{name:'1차',formula:'[기준]*0.5'},{name:'2차',formula:'[기준]-[1차]'}];
|
||
const output=EstBusiness.annual(EstNative.calculate(p));
|
||
p.annual[1].name='1차';
|
||
return {output,invalid:EstBusiness.annual(p)};
|
||
})()""",
|
||
)
|
||
assert result["output"]["errors"] == []
|
||
assert [
|
||
item["project"]["calculation"]["direct"]["TC"] for item in result["output"]["results"]
|
||
] == [4, 6]
|
||
assert result["invalid"]["results"] == []
|
||
assert len(result["invalid"]["errors"]) == 1
|
||
|
||
|
||
def test_resource_expansion_preserves_different_labor_and_material_bid_rates():
|
||
result = run(
|
||
"math",
|
||
r"""(()=>{
|
||
const p=EstNative.empty();
|
||
p.tables.StuffcDat=[{Code:'M',Name:'material',ValueIndex:4,InputTC:100}];
|
||
p.tables.LaborcDat=[{Code:'L',Name:'labor',ValueIndex:4,InputTC:100}];
|
||
p.tables.CostMainDat=[{Index:1,Code:'C'},
|
||
{Code:'M',LinkIndex:5,SCheck:-2,LineData:' \x0ematerial\x0e: \x03100\x03 = \x12100\x12'},
|
||
{Code:'L',LinkIndex:6,SCheck:-1,LineData:' \x0elabor\x0e: \x03100\x03 = \x12100\x12'}];
|
||
p.tables.CostTitleDat=[{Code:'C'}];
|
||
p.tables.EstMainDat=[{PkID:1,Code:'C',LinkIndex:104,GN:10},{LinkIndex:54}];
|
||
const bid=EstBusiness.bid(EstNative.calculate(p),[80,90,100,100]);
|
||
const direct=EstResources.calculate(bid);
|
||
p.tables.HeadMainDat=[{Index:1,Code:'H'},{Code:'C',LinkIndex:104,GN:1}];
|
||
p.tables.HeadTitleDat=[{Code:'H'}];p.tables.EstMainDat[0].LinkIndex=103;p.tables.EstMainDat[0].Code='H';
|
||
const nested=EstResources.calculate(EstBusiness.bid(EstNative.calculate(p),[80,90,100,100]));
|
||
return {direct,nested};
|
||
})()""",
|
||
)
|
||
for output in result.values():
|
||
assert output["errors"] == []
|
||
resources = {row["Code"]: row for row in output["rows"]}
|
||
assert resources["M"]["quantity"] == 9
|
||
assert resources["M"]["amount"] == 900
|
||
assert resources["L"]["quantity"] == 8
|
||
assert resources["L"]["amount"] == 800
|
||
|
||
|
||
def test_resource_expansion_selects_nested_head_component_and_all_conversions():
|
||
result = run(
|
||
"math",
|
||
r"""(()=>{
|
||
const p=EstNative.empty();
|
||
p.tables.StuffcDat=[{Code:'M',Name:'material',ValueIndex:4,InputTC:100}];
|
||
p.tables.LaborcDat=[{Code:'L',Name:'labor',ValueIndex:4,InputTC:100}];
|
||
p.tables.HeadMainDat=[{Index:1,Code:'H',Name:'head'},
|
||
{Code:'M',LinkIndex:5,GN:1},{Code:'L',LinkIndex:6,GN:1}];
|
||
p.tables.HeadTitleDat=[{Code:'H',Name:'head'}];
|
||
p.tables.CostMainDat=[{Index:1,Code:'C'},
|
||
{Code:'H',LinkIndex:103,LineData:' \x0ehead\x0e'},
|
||
{Code:'H',LinkIndex:103,SCheck:-1,LineData:' 노무비: \x03100\x03 = \x12100\x12'},
|
||
{Code:'H',LinkIndex:103,SCheck:-2,LineData:' 재료비: \x03100\x03 = \x12100\x12'}];
|
||
p.tables.CostTitleDat=[{Code:'C'}];
|
||
p.tables.EstMainDat=[{PkID:1,Code:'C',LinkIndex:104,GN:10},{LinkIndex:54}];
|
||
const selected=EstResources.calculate(p);
|
||
p.tables.HeadMainDat.push({LinkIndex:68,GN:0.5},{LinkIndex:68,GN:0.1});
|
||
p.tables.EstMainDat[0].Code='H';p.tables.EstMainDat[0].LinkIndex=103;
|
||
return {selected,converted:EstResources.calculate(p)};
|
||
})()""",
|
||
)
|
||
assert all(output["errors"] == [] for output in result.values())
|
||
assert len(result["selected"]["rows"]) == 2
|
||
assert all(
|
||
row["quantity"] == 10 and row["amount"] == 1000 for row in result["selected"]["rows"]
|
||
)
|
||
assert all(
|
||
row["quantity"] == 0.5 and row["amount"] == 50 for row in result["converted"]["rows"]
|
||
)
|
||
|
||
|
||
def test_resource_expansion_includes_money_only_rows_and_percentage_costs():
|
||
result = run(
|
||
"math",
|
||
"""(()=>{
|
||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||
p.tables.EstMainDat=[{PkID:1,Name:'fixed',GN:0,NC:3,GC:7,JC:2,TC:12,CalcIndex:1},{LinkIndex:54}];
|
||
const fixed=EstResources.calculate(p);
|
||
const q=EstNative.empty();q.tables.StuffcDat=[{Code:'M',Name:'material',ValueIndex:4,InputTC:100}];
|
||
q.tables.HeadMainDat=[{Index:1,Code:'H'},{Code:'M',LinkIndex:5,GN:1},
|
||
{LinkIndex:58,Size:'합계',SCheck:3,GN:10}];q.tables.HeadTitleDat=[{Code:'H'}];
|
||
q.tables.EstMainDat=[{Code:'H',LinkIndex:103,GN:2},{LinkIndex:54}];
|
||
return {fixed,percentage:EstResources.calculate(q)};
|
||
})()""",
|
||
)
|
||
assert all(output["errors"] == [] for output in result.values())
|
||
fixed = {row["Code"]: row for row in result["fixed"]["rows"]}
|
||
assert fixed["LZZZZZZZZZZ"]["amount"] == fixed["LZZZZZZZZZZ"]["quantity"] == 3
|
||
assert fixed["MZZZZZZZZZZ"]["amount"] == fixed["MZZZZZZZZZZ"]["quantity"] == 7
|
||
assert fixed["EZZZZZZZZZZ"]["amount"] == 2
|
||
assert fixed["EZZZZZZZZZZ"]["Unite"] == "제외금액"
|
||
assert sum(row["amount"] for row in result["percentage"]["rows"]) == 220
|
||
|
||
|
||
def test_cost_code_refresh_without_name_marker_still_tracks_the_actual_resource():
|
||
result = run(
|
||
"math",
|
||
r"""(()=>{
|
||
const p=EstNative.empty();
|
||
p.tables.StuffcDat=[{Code:'M',Name:'material',ValueIndex:4,InputTC:150}];
|
||
p.tables.CostMainDat=[{Index:1,Code:'C'},
|
||
{Code:'M',LinkIndex:5,SCheck:-2,LineData:' 재료비: \x03100\x03 ×2 = \x12200\x12'}];
|
||
p.tables.CostTitleDat=[{Code:'C'}];
|
||
p.tables.EstMainDat=[{Code:'C',LinkIndex:104,GN:1},{LinkIndex:54}];
|
||
return {calculated:EstNative.calculate(p),resources:EstResources.calculate(p)};
|
||
})()""",
|
||
)
|
||
assert result["calculated"]["calculation"]["errors"] == []
|
||
assert result["calculated"]["calculation"]["direct"]["TC"] == 300
|
||
assert result["resources"]["errors"] == []
|
||
assert result["resources"]["rows"][0]["Code"] == "M"
|
||
assert result["resources"]["rows"][0]["quantity"] == 2
|
||
assert result["resources"]["rows"][0]["amount"] == 300
|
||
|
||
|
||
def test_unlinked_text_price_does_not_inherit_the_previous_rows_source():
|
||
result = run(
|
||
"math",
|
||
r"""(()=>{
|
||
const p=EstNative.empty();
|
||
p.tables.StuffcDat=[{Code:'M',Name:'material',ValueIndex:4,InputTC:150}];
|
||
p.tables.CostMainDat=[{Index:1,Code:'C'},
|
||
{Code:'M',LinkIndex:5,SCheck:-2,LineData:' \x0ematerial\x0e: \x03100\x03 = \x12100\x12'},
|
||
{Code:'M',LinkIndex:0,SCheck:-2,LineData:' 직접입력: \x0350\x03 ×2 = \x12100\x12'}];
|
||
p.tables.CostTitleDat=[{Code:'C'}];
|
||
p.tables.EstMainDat=[{Code:'C',LinkIndex:104,GN:1},{LinkIndex:54}];
|
||
return {calculated:EstNative.calculate(p),resources:EstResources.calculate(p)};
|
||
})()""",
|
||
)
|
||
assert result["calculated"]["calculation"]["errors"] == []
|
||
assert result["calculated"]["calculation"]["direct"]["TC"] == 250
|
||
resources = {row["Code"]: row for row in result["resources"]["rows"]}
|
||
assert resources["M"]["quantity"] == 1
|
||
assert resources["M"]["amount"] == 150
|
||
assert resources["MZZZZZZZZZZ"]["amount"] == 100
|
||
|
||
|
||
def test_change_comparison_uses_original_file_values_without_overwriting_a_baseline():
|
||
result = run(
|
||
"math",
|
||
"""(()=>{
|
||
const p=EstNative.empty();p.tables.EstMainDat=[
|
||
{PkID:1,Code:'R',GN:3,TC:150,GN_:2,TC_:100},
|
||
{PkID:2,Code:'Z',GN:1,TC:10,GN_:0,TC_:0},
|
||
{PkID:3,Code:'N',GN:1,TC:20}];
|
||
const stored=EstBusiness.changes(p);
|
||
p.baseline={name:'explicit',tables:{EstMainDat:[{PkID:1,Code:'R',GN:4,TC:200}]}};
|
||
return {stored,explicit:EstBusiness.changes(p),unchanged:p.tables.EstMainDat[0].TC_};
|
||
})()""",
|
||
)
|
||
assert result["stored"][0]["originalGN"] == 2
|
||
assert result["stored"][0]["originalTC"] == 100
|
||
assert result["stored"][0]["difference"] == 50
|
||
assert result["stored"][0]["originalSource"] == "file"
|
||
assert not result["stored"][1]["NewItem"]
|
||
assert result["stored"][2]["NewItem"]
|
||
assert result["explicit"][0]["originalTC"] == 200
|
||
assert result["explicit"][0]["difference"] == -50
|
||
assert result["explicit"][1]["NewItem"]
|
||
assert result["unchanged"] == 100
|
||
|
||
|
||
def test_original_numeric_fields_reject_invalid_saved_values():
|
||
result = run(
|
||
"math",
|
||
"""(()=>{
|
||
const p=EstNative.empty();p.tables.EstMainDat=[{GN:1,GN_:'invalid'}];
|
||
try {EstModel.validate(p);return false;}catch(error){return error.message;}
|
||
})()""",
|
||
)
|
||
assert "GN_" in result
|
||
|
||
|
||
def test_baseline_keeps_current_inputs_once_and_preserves_working_binaries():
|
||
result = run(
|
||
"math",
|
||
"""(()=>{
|
||
const p=EstNative.empty();
|
||
p.tables.CostMainDat=[{Index:1,Code:'C',LineData:'current',LineData_:'previous'}];
|
||
p.tables.CadMainDat=[{Code:'D',Data:{binary:'QUJD'}}];
|
||
p.tables.StuffcDat=[{Code:'M',InputTC:100}];p.originalArchive='original bytes';
|
||
const q=EstBusiness.baseline(p),again=EstBusiness.baseline(q);
|
||
return {p,q,again};
|
||
})()""",
|
||
)
|
||
for item in [result["q"], result["again"]]:
|
||
assert item["originalArchive"] == "original bytes"
|
||
assert item["tables"]["CadMainDat"][0]["Data"]["binary"] == "QUJD"
|
||
assert "CadMainDat" not in item["baseline"]["tables"]
|
||
assert "StuffcDat" not in item["baseline"]["tables"]
|
||
assert item["baseline"]["tables"]["CostMainDat"][0]["LineData"] == "current"
|
||
assert "LineData_" not in item["baseline"]["tables"]["CostMainDat"][0]
|
||
assert result["p"]["tables"]["CostMainDat"][0]["LineData_"] == "previous"
|
||
|
||
|
||
def test_large_original_baseline_stays_within_the_save_request_limit():
|
||
import os
|
||
from pathlib import Path
|
||
import pytest
|
||
|
||
archive = Path(
|
||
os.environ.get("EST_REA_MAIN", "C:/Dasansoft/Est Plus 2/Data/ProjectData/main.estx")
|
||
)
|
||
if not archive.is_file():
|
||
pytest.skip("Original main.estx is not installed")
|
||
result = run("baseline-size", str(archive))
|
||
assert result["archivePreserved"]
|
||
assert result["bytes"] < 256 * 1024 * 1024
|