Files
Aislo/resources/tester/test_est_review_workflow.py
T

211 lines
8.0 KiB
Python

from test_est_rea_parity import run
def test_review_summary_updates_the_print_document_and_active_settings():
result = run(
"math",
"""(()=>{
const handlers={},p=EstNative.empty();p.printSettings={form:'173',notes:'old'};
globalThis.document={addEventListener:(name,fn)=>handlers[name]=fn};
globalThis.EstApp={state:{project:p},change:fn=>fn(),render:()=>{}};
EstReview.install();handlers.change({target:{dataset:{reviewSummary:''},value:'review summary'}});
return {document:p.documents['173'],notes:p.printSettings.notes};
})()""",
)
assert result == {"document": "review summary", "notes": "review summary"}
def test_review_rejects_unallocated_fixed_amounts_and_ambiguous_targets():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.tables.EstMainDat=[
{Name:'fixed',PkID:1,GN:1,TC:100,CalcIndex:1},
{Name:'first',Code:'A',GN:1,GU:10},{Name:'second',Code:'A',GN:1,GU:20}];
p.contractReview={edits:{'row:A':{GU:999}}};
const out=EstReview.calculate(p);let rejected=false;
try{EstReview.copy(p)}catch{rejected=true}
return {errors:out.errors,rejected,units:out.reviewed.tables.EstMainDat.slice(1).map(row=>row.GU)};
})()""",
)
assert len(result["errors"]) == 2 and result["rejected"]
assert result["units"] == [10, 20]
def test_review_recalculates_user_inputs_and_opens_independent_copy():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.name='contract';p.id='123456789012345678901234';
p.options.SourceRefresh=false;p.tables.EstMainDat=[
{PkID:1,Code:'',Name:'normal',GN:10,GU:100},
{PkID:2,Code:'',Name:'fixed',GN:1,GC:100,TC:100,CalcIndex:1},
{LinkIndex:54}];
p.tables.Wonga=[{Gubun:'D',BaseField:'TC'},
{Gubun:'V',VSusic:'D*0.1'},{Gubun:'T',LinkIndex:55,VSusic:'D+V'}];
const q=EstNative.calculate(p);q.contractReview={edits:{'row:1':{GN:2,GU:80,Reason:'checked'},'row:2':{GC:20}}};
const before=JSON.stringify(q),review=EstReview.calculate(q),copy=EstReview.copy(q);
const same=JSON.stringify(q)===before;copy.tables.EstMainDat[0].GN=99;
return {review,same,original:q.tables.EstMainDat[0].GN,copy};
})()""",
)
assert result["same"] and result["original"] == 10
assert result["review"]["errors"] == []
assert result["review"]["before"] == 1210
assert result["review"]["after"] == 198
assert result["review"]["reviewed"]["tables"]["EstMainDat"][1]["TC"] == 20
assert "id" not in result["copy"]
assert "contractReview" not in result["copy"]
def test_review_rejects_invalid_and_stale_inputs():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.tables.EstMainDat=[{PkID:1,Code:'A',GN:1,GU:10}];
const invalid=[];for(const edit of [{GN:'bad'},{GN:Infinity},{unrelated:1}]){
p.contractReview={edits:{'row:1':edit}};
try{EstModel.validate(p);invalid.push(false)}catch{invalid.push(true)}
}
p.contractReview={edits:{'row:missing':{GN:2}}};
let rejected=false;try{EstReview.copy(p)}catch{rejected=true}
return {invalid,rejected};
})()""",
)
assert result["invalid"] == [True, True, True]
assert result["rejected"]
def test_review_summary_print_contains_totals_and_item_differences():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.tables.EstMainDat=[{PkID:1,Code:'',Name:'item',GN:2,GU:100}];
p.contractReview={edits:{'row:1':{GN:1,GU:80,Reason:'quantity reviewed'}}};return EstForms.data(171,p);
})()""",
)
assert result["errors"] == []
assert [result["rows"][0][key] for key in ["originalTC", "TC", "difference"]] == [
200,
80,
-120,
]
assert result["rows"][1]["Name"] == "item"
assert result["rows"][1]["difference"] == -120
assert result["rows"][1]["Reason"] == "quantity reviewed"
def test_review_reset_one_row_keeps_other_inputs():
result = run(
"math",
"""(()=>{
const handlers={},p=EstNative.empty();
p.contractReview={edits:{'row:1':{GU:80,Reason:'first'},'row:2':{GN:3,Reason:'second'}}};
globalThis.document={addEventListener:(name,fn)=>handlers[name]=fn};
globalThis.EstApp={state:{project:p},change:fn=>fn(),render:()=>{}};
EstReview.install();
const click=key=>handlers.click({target:{closest:s=>s==='[data-review-reset]'?{dataset:{reviewReset:key}}:null}});
click('row:1');const after=structuredClone(p.contractReview);click('row:2');
return {after,removed:!Object.hasOwn(p,'contractReview')};
})()""",
)
assert result["after"]["edits"] == {"row:2": {"GN": 3, "Reason": "second"}}
assert result["removed"]
def test_fixed_review_preserves_amount_for_reason_or_quantity_only():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.tables.EstMainDat=[{PkID:1,Name:'fixed',GN:1,TC:100,CalcIndex:1}];
return [{Reason:'checked'},{GN:3},{GC:20}].map(edit=>{
p.contractReview={edits:{'row:1':edit}};
return EstReview.calculate(p).reviewed.tables.EstMainDat[0].TC;
});
})()""",
)
assert result == [100, 100, 20]
def test_review_view_filtering_and_batch_reason_application():
result = run(
"math",
"""(()=>{
const handlers={},p=EstNative.empty();
p.tables.EstMainDat=[
{PkID:1,Code:'A01',Name:'Item1',GN:10,GU:100,TC:1000},
{PkID:2,Code:'A02',Name:'Item2',GN:5,GU:200,TC:1000},
{PkID:3,Code:'A03',Name:'Item3',GN:2,GU:300,TC:600}
];
p.contractReview={
edits:{
'row:1':{GN:8,Reason:'수량조정'},
'row:2':{GU:180}
}
};
let html='';
const domNodes = [
{dataset:{key:'row:1',review:'Reason'}},
{dataset:{key:'row:2',review:'Reason'}},
{dataset:{key:'row:3',review:'Reason'}}
];
globalThis.document={
addEventListener:(name,fn)=>handlers[name]=fn,
querySelectorAll:(sel)=>domNodes
};
globalThis.prompt=(msg)=>'일괄사유적용';
globalThis.EstApp={
state:{project:p,reviewFilter:'all',reviewSelection:new Set()},
change:fn=>fn(),
render:()=>{},
byId:()=>({innerHTML:h=>{html=h;},set innerHTML(h){html=h;}}),
esc:s=>String(s||''),
number:n=>String(n||0)
};
EstReview.render(EstApp.state);
const allRowsCount = (html.match(/<tr class=/g) || []).length;
// 1) changed 필터
EstApp.state.reviewFilter = 'changed';
EstReview.render(EstApp.state);
const changedRowsCount = (html.match(/<tr class=/g) || []).length;
// 2) has-reason 필터
EstApp.state.reviewFilter = 'has-reason';
EstReview.render(EstApp.state);
const hasReasonRowsCount = (html.match(/<tr class=/g) || []).length;
// 3) no-reason 필터
EstApp.state.reviewFilter = 'no-reason';
EstReview.render(EstApp.state);
const noReasonRowsCount = (html.match(/<tr class=/g) || []).length;
// 4) 일괄 사유 적용 (선택 행 있을 때)
EstReview.install();
EstApp.state.reviewSelection.add('row:2');
const clickBatch = () => handlers.click({target:{closest:s=>s==='[data-review-batch-reason]'?{}:null}});
clickBatch();
const afterSelectBatch = p.contractReview.edits['row:2'].Reason;
// 5) 일괄 사유 적용 (선택 행 없을 때 표시 행 전체 적용)
EstApp.state.reviewSelection.clear();
clickBatch();
const afterAllBatch = p.contractReview.edits['row:3']?.Reason;
return {
allRowsCount,
changedRowsCount,
hasReasonRowsCount,
noReasonRowsCount,
afterSelectBatch,
afterAllBatch
};
})()""",
)
assert result["allRowsCount"] == 3
assert result["changedRowsCount"] == 2
assert result["hasReasonRowsCount"] == 1
assert result["noReasonRowsCount"] == 1
assert result["afterSelectBatch"] == "일괄사유적용"
assert result["afterAllBatch"] == "일괄사유적용"