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(/