from test_est_rea_parity import run def test_estimate_move_preserves_row_identity_and_blocks_ambiguous_or_total_rows(): result = run( "math", """(()=>{ const rows=[{PkID:1,Code:'A'},{PkID:2,Code:'B'},{PkID:3,Code:'C'},{LinkIndex:54}]; const moved=EstBusiness.moveEstimateRows(rows,new Set([1,2]),-1); const order=rows.map(row=>row.PkID||0); const restored=EstBusiness.moveEstimateRows(rows,new Set(moved),1); let blocked=0; for (const [data,selection,direction] of [ [[{Code:'A'},{Code:'A'}],[0],1], [[{PkID:1},{LinkIndex:54}],[0],1], [[{PkID:1},{PkID:2},{PkID:3}],[0,2],1], [[{PkID:1},{Name:'unknown'}],[0],1]]) { try{EstBusiness.moveEstimateRows(data,new Set(selection),direction)}catch{blocked++} } return {moved,order,restored,blocked}; })()""", ) assert result == {"moved": [0, 1], "order": [2, 3, 1, 0], "restored": [1, 2], "blocked": 4} def test_duplicate_codes_match_distinct_original_rows_and_track_deletions(): result = run( "math", """(()=>{ const p=EstNative.empty();p.tables.EstMainDat=[{Code:'A',Name:'first',GN:2,TC:200}, {Code:'A',Name:'second',GN:4,TC:400}]; p.baseline={name:'old',tables:{EstMainDat:[{Code:'A',Name:'first',GN:1,TC:100}, {Code:'A',Name:'second',GN:3,TC:300},{Code:'A',Name:'removed',GN:5,TC:500}]}}; return EstBusiness.changes(p); })()""", ) assert [row["originalTC"] for row in result] == [100, 300, 500] assert [row["difference"] for row in result] == [100, 100, -500] assert result[-1]["status"] == "deleted" def test_design_change_costs_include_duplicate_new_and_deleted_cost_rows(): result = run( "math", """(()=>{ const p=EstNative.empty();p.tables.Wonga=[ {Gubun:'A',Name:'first',Cost:200},{Gubun:'A',Name:'second',Cost:60}, {Gubun:'C',Name:'new',Cost:25}]; p.baseline={name:'old',tables:{EstMainDat:[],Wonga:[ {Gubun:'A',Name:'first',Cost:100},{Gubun:'A',Name:'second',Cost:50}, {Gubun:'B',Name:'removed',Cost:30}]}}; const before=JSON.stringify(p), rows=EstBusiness.costChanges(p); const output=EstForms.data('change-costs',p); const node={innerHTML:''};globalThis.EstApp={esc:x=>String(x??''),number:x=>String(x??0),byId:()=>node}; EstExtra.render({project:p,view:'change'}); return {rows,output,html:node.innerHTML,same:before===JSON.stringify(p)}; })()""", ) assert result["same"] assert [row["difference"] for row in result["rows"]] == [100, 10, 25, -30] assert result["output"]["rows"] == result["rows"] assert "removed" in result["html"] def test_stored_original_costs_remain_attached_to_each_duplicate_row(): result = run( "math", """(()=>{ const p=EstNative.empty();p.tables.Wonga=[ {Gubun:'A',Name:'new',Cost:10},{Gubun:'A',Name:'existing',Cost:50,Cost_:30}]; return EstBusiness.costChanges(p); })()""", ) assert [row["originalCost"] for row in result] == [0, 30] assert [row["difference"] for row in result] == [10, 20] def test_change_view_includes_named_rows_without_codes(): result = run( "math", """(()=>{ const p=EstNative.empty();p.tables.EstMainDat=[{PkID:1,Code:'',Name:'earthwork',GN:2,TC:200}]; p.baseline={name:'base',tables:{EstMainDat:[{PkID:1,Code:'',Name:'earthwork',GN:1,TC:100}]}}; const node={innerHTML:''};globalThis.EstApp={esc:x=>String(x??''),number:x=>String(x??0),byId:()=>node}; const state={project:p,view:'change'};EstExtra.render(state);return {html:node.innerHTML,changes:EstBusiness.changes(p)}; })()""", ) assert "earthwork" in result["html"] assert result["changes"][0]["difference"] == 100 def test_original_recalculation_modes_keep_old_quantities_and_only_refresh_selected_prices(): result = run( "math", """(()=>{ const p=EstNative.empty(); p.tables.StuffcDat=[{PkID:1,Code:'M',ValueIndex:4,InputTC:100}]; p.tables.EstMainDat=[{PkID:1,Code:'M',LinkIndex:5,GN:2},{PkID:2,LinkIndex:54}]; const q=EstBusiness.baseline(EstNative.calculate(p)); const before=JSON.stringify(q.baseline); q.tables.StuffcDat[0].InputTC=150;q.tables.EstMainDat[0].GN=7; q.tables.EstMainDat.splice(1,0,{PkID:3,Code:'M',LinkIndex:5,GN:1,NewItem:true}); const modes=[0,1,2].map(method=>EstBusiness.original(q,method).tables.EstMainDat); return {modes,unchanged:before===JSON.stringify(q.baseline)}; })()""", ) assert result["unchanged"] for mode, rows in enumerate(result["modes"]): assert rows[0]["GN"] == 2 assert rows[0]["TC"] == (200 if mode == 0 else 300) assert rows[1]["TC"] == 150 assert rows[-1]["TC"] == (350 if mode == 0 else 450) def test_design_change_includes_deleted_and_new_rows_without_modifying_contract(): result = run( "math", """(()=>{ const p=EstNative.empty();p.options.SourceRefresh=false; p.tables.EstMainDat=[{PkID:1,Code:'A',Name:'kept',GN:2,GU:100}, {PkID:3,Code:'B',Name:'removed',GN:3,NU:50},{PkID:2,LinkIndex:54}]; let q=EstBusiness.baseline(EstNative.calculate(p)); const before=JSON.stringify(q.baseline); q.tables.EstMainDat.splice(1,1); q.tables.EstMainDat[0].GN=4; q.tables.EstMainDat.splice(1,0,{...EstModel.newRow('EstMainDat',q.tables.EstMainDat,q),Code:'C',Name:'new',GN:1,JU:25}); q=EstNative.calculate(q); return {changes:EstBusiness.changes(q),same:before===JSON.stringify(q.baseline)}; })()""", ) assert result["same"] rows = {row["Code"]: row for row in result["changes"] if row["Code"]} assert rows["A"]["status"] == "changed" assert rows["A"]["difference"] == 200 assert rows["B"]["status"] == "deleted" assert rows["B"]["originalGN"] == 3 assert rows["B"]["GN"] == rows["B"]["TC"] == 0 assert rows["B"]["difference"] == -150 assert rows["C"]["status"] == "new" assert rows["C"]["difference"] == 25 def test_blank_codes_have_distinct_keys_and_metadata_changes_are_reported(): result = run( "math", """(()=>{ const p=EstNative.empty();p.tables.EstMainDat=[ {PkID:1,Code:'A',Name:'renamed',GN:1,TC:10}, {Code:'',Name:'first'},{Code:'',Name:'second'}]; p.baseline={name:'old',tables:{EstMainDat:[ {PkID:1,Code:'A',Name:'old',GN:1,TC:10}]}}; return {rows:EstBusiness.changes(p),keys:p.tables.EstMainDat.map(EstBusiness.rowKey)}; })()""", ) assert len(set(result["keys"][1:])) == 2 assert result["rows"][0]["status"] == "changed" assert result["rows"][0]["difference"] == 0