205 lines
8.2 KiB
Python
205 lines
8.2 KiB
Python
from test_est_rea_parity import run
|
|
|
|
|
|
def test_new_progress_name_avoids_existing_names_with_whitespace():
|
|
result = run(
|
|
"math",
|
|
"""[EstBusiness.newProgressName([{name:'2회 기성'},{name:' 1회 기성 '}]),
|
|
EstBusiness.newProgressName([{name:'2회 기성'}]),EstBusiness.newProgressName([])]""",
|
|
)
|
|
assert result == ["3회 기성", "1회 기성", "1회 기성"]
|
|
|
|
|
|
def test_progress_cannot_lock_when_previous_result_is_missing():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();
|
|
p.tables.EstMainDat=[{PkID:1,Code:'R',GN:10,GU:100},{LinkIndex:54}];
|
|
p.progress=[{id:'one',locked:true,inputs:{}},{id:'two',inputs:{'1':{Gum:2}}}];
|
|
const calculated=EstProgress.calculate(p,'two');
|
|
let rejected=false;
|
|
try { EstProgress.lock(p,'two'); } catch { rejected=true; }
|
|
return {errors:calculated.errors,rejected};
|
|
})()""",
|
|
)
|
|
assert any("전회 기성 결과가 없습니다" in error for error in result["errors"])
|
|
assert result["rejected"]
|
|
|
|
|
|
def test_progress_cannot_lock_when_previous_costs_are_missing():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();
|
|
p.tables.EstMainDat=[{PkID:1,Code:'R',GN:10,GU:100},{LinkIndex:54}];
|
|
p.tables.Wonga=[{Gubun:'D',BaseField:'TC'},{LinkIndex:55,VSusic:'D'}];
|
|
p.progress=[{id:'one',locked:true,inputs:{},result:{rows:[]}},
|
|
{id:'two',inputs:{'1':{Gum:2}}}];
|
|
const calculated=EstProgress.calculate(p,'two');
|
|
let rejected=false;
|
|
try { EstProgress.lock(p,'two'); } catch { rejected=true; }
|
|
return {errors:calculated.errors,rejected};
|
|
})()""",
|
|
)
|
|
assert any("전회 기성 원가 결과가 없습니다" in error for error in result["errors"])
|
|
assert result["rejected"]
|
|
|
|
|
|
def test_progress_rejects_ambiguous_inputs_without_applying_them_to_multiple_rows():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.options.SourceRefresh=false;
|
|
p.tables.EstMainDat=[{Code:'A',GN:10,GU:100},{Code:'A',GN:20,GU:100},
|
|
{PkID:3,Code:'B',GN:10,GU:100},{LinkIndex:54}];
|
|
p.progress=[{id:'one',inputs:{A:{Gum:2},'3':{Gum:3}}}];
|
|
const out=EstProgress.calculate(EstNative.calculate(p),'one');let rejected=false;
|
|
try{EstProgress.lock(EstNative.calculate(p),'one')}catch{rejected=true}
|
|
return {out,rejected};
|
|
})()""",
|
|
)
|
|
assert result["rejected"] and result["out"]["errors"]
|
|
assert [row["GumTC"] for row in result["out"]["rows"][:3]] == [0, 0, 300]
|
|
|
|
|
|
def test_progress_amount_input_preserves_exact_money_and_component_total():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.options.SourceRefresh=false;
|
|
p.tables.EstMainDat=[{PkID:1,Code:'R',GN:3,NU:33,GU:33,JU:34},{LinkIndex:54}];
|
|
p.tables.Wonga=[{Gubun:'D',BaseField:'TC'},{Gubun:'T',LinkIndex:55,VSusic:'D'}];
|
|
p.progress=[{id:'one',inputs:{'1':{Gum:100,GumMode:'amount',Sa:90,SaMode:'amount'}},approved:true}];
|
|
return EstProgress.calculate(EstNative.calculate(p),'one');
|
|
})()""",
|
|
)
|
|
assert result["errors"] == []
|
|
row = result["rows"][0]
|
|
assert row["GumTC"] == 100
|
|
assert sum(row["Gum" + key] for key in ["NC", "GC", "JC", "SC"]) == 100
|
|
assert row["TotalTC"] == 90
|
|
assert result["total"] == 90
|
|
|
|
|
|
def test_progress_fixed_amount_and_zero_quantity_ratio():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.tables.EstMainDat=[
|
|
{PkID:1,Code:'R',GN:0,GC:1000,TC:1000,CalcIndex:1},{LinkIndex:54}];
|
|
p.progress=[{id:'one',inputs:{'1':{Gum:25,GumMode:'ratio',Sa:333,SaMode:'amount'}}}];
|
|
return EstProgress.calculate(p,'one');
|
|
})()""",
|
|
)
|
|
assert result["errors"] == []
|
|
assert result["rows"][0]["GumGN"] == 0
|
|
assert result["rows"][0]["GumTC"] == 250
|
|
assert result["rows"][0]["SaTC"] == 333
|
|
|
|
|
|
def test_progress_ratio_quantity_precision_and_bankers_percentage():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.tables.EstMainDat=[
|
|
{PkID:1,Code:'R',GN:10,NU:10,NC:100,TC:100},
|
|
{PkID:2,Name:'percentage',GN:5,LinkIndex:67,SCheck:2,Size:'노무',NC:5,TC:5},
|
|
{LinkIndex:54}];
|
|
p.progress=[{id:'one',inputs:{'1':{Gum:25,GumMode:'ratio',Sa:5}}}];
|
|
return EstProgress.calculate(p,'one');
|
|
})()""",
|
|
)
|
|
assert result["errors"] == []
|
|
assert result["rows"][0]["GumGN"] == 3
|
|
assert result["rows"][1]["GumTC"] == 2
|
|
assert result["rows"][1]["SaTC"] == 2 # Round(2.5) = 2 in c9261c.
|
|
|
|
|
|
def test_locked_progress_uses_its_contract_and_server_rebuilds_its_totals():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.options.SourceRefresh=false;
|
|
p.tables.EstMainDat=[{PkID:1,Code:'R',GN:10,GU:100},{LinkIndex:54}];
|
|
p.tables.Wonga=[{Gubun:'D',Name:'material',BaseField:'GC'},
|
|
{Gubun:'V',Name:'부가세',Youl:'10%',VSusic:'D*0.1'},
|
|
{Gubun:'T',Name:'total',LinkIndex:55,VSusic:'D+V'}];
|
|
p.progress=[{id:'one',inputs:{'1':{Gum:2}}}];
|
|
const q=EstProgress.lock(EstNative.calculate(p),'one');
|
|
q.tables.EstMainDat[0].GU=200;
|
|
q.progress[0].result.rows[0].TotalTC=99999;
|
|
EstProgress.rebuild(q);
|
|
q.progress.push({id:'two',inputs:{'1':{Gum:3}}});
|
|
const second=EstProgress.calculate(q,'two');
|
|
const unlocked=EstProgress.unlock(q,'one');
|
|
return {q,second,unlocked};
|
|
})()""",
|
|
)
|
|
assert result["q"]["progress"][0]["result"]["rows"][0]["TotalTC"] == 200
|
|
second = result["second"]
|
|
assert second["errors"] == []
|
|
assert second["rows"][0]["JunTC"] == 200
|
|
assert second["rows"][0]["GumTC"] == 600
|
|
assert second["rows"][0]["TotalTC"] == 800
|
|
assert second["costs"][-1]["TotalCost"] == 880
|
|
assert all(not row["locked"] for row in result["unlocked"]["progress"])
|
|
assert all("result" not in row for row in result["unlocked"]["progress"])
|
|
|
|
|
|
def test_progress_total_only_mode_and_invalid_saved_input_rejection():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.tables.EstMainDat=[{PkID:1,Code:'R',GN:10,TC:1000,CalcIndex:1},{LinkIndex:54}];
|
|
p.tables.Wonga=[{Gubun:'D',BaseField:'TC'},{Gubun:'T',LinkIndex:55,VSusic:'D'}];
|
|
p.progress=[{id:'one',separateComponents:false,inputs:{'1':{Gum:25,GumMode:'ratio'}}}];
|
|
const output=EstProgress.calculate(p,'one');
|
|
p.progress[0].inputs['1'].Gum='invalid';let rejected=false;
|
|
try{EstModel.validate(p);}catch{rejected=true;}
|
|
return {output,rejected};
|
|
})()""",
|
|
)
|
|
assert result["rejected"]
|
|
assert result["output"]["errors"] == []
|
|
assert result["output"]["rows"][0]["GumTC"] == 250
|
|
assert result["output"]["total"] == 250
|
|
|
|
|
|
def test_progress_preserves_zero_wonga_total_and_falls_back_only_when_absent():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.options.SourceRefresh=false;
|
|
p.tables.EstMainDat=[{PkID:1,Code:'R',GN:10,GU:100},{LinkIndex:54}];
|
|
p.tables.Wonga=[{Gubun:'D',BaseField:'TC'},{Gubun:'T',LinkIndex:55,VSusic:'0'}];
|
|
p.progress=[{id:'one',inputs:{'1':{Gum:2}}}];
|
|
const zero=EstProgress.calculate(EstNative.calculate(p),'one');
|
|
p.tables.Wonga=[];const absent=EstProgress.calculate(EstNative.calculate(p),'one');
|
|
return {zero,absent};
|
|
})()""",
|
|
)
|
|
assert result["zero"]["errors"] == []
|
|
assert result["zero"]["rows"][0]["GumTC"] == 200
|
|
assert result["zero"]["total"] == 0
|
|
assert result["absent"]["total"] == 200
|
|
|
|
|
|
def test_progress_preserves_prior_costs_for_multiple_blank_codes():
|
|
result = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.options.SourceRefresh=false;
|
|
p.tables.EstMainDat=[{PkID:1,Code:'R',GN:10,GU:100,NU:50},{LinkIndex:54}];
|
|
p.tables.Wonga=[{Gubun:'',Name:'material',BaseField:'GC'},
|
|
{Gubun:'',Name:'labor',BaseField:'NC'}];
|
|
p.progress=[{id:'one',inputs:{'1':{Gum:2}}}];
|
|
const q=EstProgress.lock(EstNative.calculate(p),'one');
|
|
q.progress.push({id:'two',inputs:{'1':{Gum:3}}});
|
|
return EstProgress.calculate(q,'two');
|
|
})()""",
|
|
)
|
|
assert result["errors"] == []
|
|
assert [row["JunCost"] for row in result["costs"]] == [200, 100]
|
|
assert [row["TotalCost"] for row in result["costs"]] == [500, 250]
|