Files
SGF/scripts/request-history-body.php
T
2026-11-04 09:37:20 +02:00

12 lines
7.7 KiB
PHP

<?php
$requestCommand=function($role,$id,$action,$data)use($call){$base='/api/purchasing-requests';$r=$call($role,$id?$base.'/'.$id:$base);$p=$call($role,$base.'/'.$id.'/preview',['operation_key'=>(string)Str::uuid(),'revision'=>$id?$r['data']['revision']:$r['data']['new_revision'],'action'=>$action,'data'=>$data]);if($p['status']!==200)throw new RuntimeException('Source request preview');$r=$call($role,$base.'/'.$id.'/confirm',['preview'=>$p['data']['preview']]);if($r['status']!==201)throw new RuntimeException('Source request confirmation');return $r['data'];};
$input=['cost_center_id'=>$n['centre'],'priority'=>'normal','needed_by'=>date('Y-m-d',strtotime('+30 days')),'justification'=>'Origem sintetica para consulta integral do historico.','items'=>[['description'=>'Historico QA','category'=>'Outro','purchase_type'=>'consumable','quantity'=>'1.000','unit'=>'UN','unit_price'=>'1.00','tax_rate'=>'0']]];$created=$requestCommand('requester',0,'save',$input);$id=$created['request_id'];$other=$requestCommand('requester',0,'save',$input)['request_id'];$user=App\Models\User::findOrFail($f['requester']);auth()->setUser($user);request()->setUserResolver(fn()=>$user);app(App\Shared\Context\CompanyContext::class)->selectFor($user->id,$f['tenant'],$f['company'],$f['unit']);
$append=function($count,$label)use($id){(new App\Modules\Purchasing\ReceiptRuntime)->run('purchasing_requests.prepare',function($a)use($id,$count,$label){$r=Tempo\Purchasing\Embedded\Models\Requisition::findOrFail($id);for($i=1;$i<=$count;$i++)app(Tempo\Purchasing\Embedded\Services\RequisitionWorkflow::class)->audit($a,'requisition.qa_history',$r,['qa_note'=>$i-1],['qa_fixture'=>true,'label'=>$label,'qa_note'=>$i]);});};$append(125,'volume-only');$expected=$db->table('audit_logs')->where('requisition_id',$id)->orderByDesc('id')->pluck('id')->all();$record=(array)$db->table('requisitions')->where('id',$id)->first();$url='/api/purchasing-requests/'.$id.'/history';$r=$call('requester','/api/purchasing-requests/'.$id);$first=$r['data'];$ok($r['status']===200&&count($first['history'])===25&&is_string($first['history_navigation']['next'])&&$first['history_navigation']['previous']===null,'request detail reuses first25 events and offers opaque older cursor');$cursor=$first['history_navigation']['next'];$r=$call('requester',$url);$ok($r['status']===200&&array_column($r['data']['data'],'id')===array_slice($expected,0,25)&&!isset($r['data']['total']),'first cursor page retains exact own-stage order without global count');
$append(1,'new-head-after-first-page');$seen=array_column($first['history'],'id');$next=$cursor;$second=null;while($next){$r=$call('requester',$url.'?cursor='.rawurlencode($next));if($r['status']!==200)throw new RuntimeException('Cursor traversal failed');$second??=$r['data'];array_push($seen,...array_column($r['data']['data'],'id'));$next=$r['data']['navigation']['next'];}$ok($seen===$expected&&count(array_unique($seen))===count($expected),'all older pages retain127 original events once despite new head append');$r=$call('requester',$url.'?cursor='.rawurlencode($second['navigation']['previous']));$ok($r['status']===200&&array_column($r['data']['data'],'id')===array_slice($expected,0,25),'previous cursor returns the original immediately newer25 events');$r=$call('requester',$url.'?per_page=100');$hundred=$r['data'];$ok($r['status']===200&&count($hundred['data'])===100&&$hundred['data'][0]['after']['label']==='new-head-after-first-page','refresh includes new event and supports100-row page');$r=$call('requester',$url.'?per_page=100&cursor='.rawurlencode($hundred['navigation']['next']));$ok($r['status']===200&&count($r['data']['data'])===28&&$r['data']['navigation']['next']===null,'100-row traversal exposes remaining28 original records with final cursor absent');
$ok($call('requester',$url.'?per_page=100&cursor='.rawurlencode($cursor))['status']===409,'page-size change cannot reuse prior25-row cursor');$ok($call('requester','/api/purchasing-requests/'.$other.'/history?cursor='.rawurlencode($cursor))['status']===409,'cursor cannot migrate to another authorised request');$ok($call('actor',$url.'?cursor='.rawurlencode($cursor))['status']===409,'another authorised reader must start own cursor context');$ok($call('requester',$url.'?cursor=invalid')['status']===409&&$call('requester',$url.'?page=2')['status']===422&&$call('requester',$url.'?per_page=30')['status']===422&&$call('requester',$url.'?cursor[]=x')['status']===422,'invalid cursors and unsupported parameter forms are refused');
$cut=json_decode(Illuminate\Support\Facades\Crypt::decryptString($cursor),true);$cut['observed']=time()-601;$expired=Illuminate\Support\Facades\Crypt::encryptString(json_encode($cut,JSON_THROW_ON_ERROR));$ok($call('requester',$url.'?cursor='.rawurlencode($expired))['status']===409,'expired cursor requires an explicit fresh consultation');$grant=DB::table('permission_grants')->where($f['scope'])->where('user_id',$f['requester'])->where('permission','purchasing_requests.view')->first();DB::table('permission_grants')->where('id',$grant->id)->delete();try{$ok($call('requester',$url.'?cursor='.rawurlencode($cursor))['status']===403,'current view right is checked again on every history page');}finally{DB::table('permission_grants')->insert((array)$grant);}
$ok($call('validator',$url)['status']===403&&$call('requester','/api/purchasing-requests/'.$n['foreign_requisition'].'/history')['status']===403,'physical-only role and foreign request cannot read history');$enter('requester',1,['foreign'=>1]);$ok($call('requester',$url,null,1)['status']===403,'unmapped unit cannot reuse known request history');$enter('actor',1,['module'=>'commercial']);$ok($call('actor',$url,null,1)['status']===409,'another active module cannot read purchasing history');$enter('requester');$ok($call('requester',$url.'?cursor='.rawurlencode($cursor))['status']===409,'new session context generation invalidates former cursor');$r=$call('requester',$url);$ok($r['status']===200&&count($r['data']['data'])===25&&!array_filter($r['data']['data'],fn($v)=>!str_starts_with($v['action'],'requisition.')&&$v['action']!=='sgf.request.command'),'fresh consultation restores own-stage history only');
$ok((array)$db->table('requisitions')->where('id',$id)->first()===$record&&$db->table('purchase_orders')->where('tenant_id',$n['tenant'])->count()===0&&$db->table('goods_receipts')->where('tenant_id',$n['tenant'])->count()===0&&$db->table('invoices')->where('tenant_id',$n['tenant'])->count()===0&&$db->table('pa_obligations')->where('tenant_id',$n['tenant'])->count()===0&&$db->table('external_payments')->where('tenant_id',$n['tenant'])->count()===0,'history browsing changes no request version order stock invoice debt or payment');
$queries=0;DB::listen(function()use(&$queries){$queries++;});$controller=new App\Modules\Purchasing\RequestController;$runtime=new App\Modules\Purchasing\ReceiptRuntime;$request=Illuminate\Http\Request::create('/','GET');$deny=function($fn){try{$fn();throw new RuntimeException('Guard accepted');}catch(Symfony\Component\HttpKernel\Exception\HttpException $e){if($e->getStatusCode()!==404)throw $e;}};$deny(fn()=>$controller->history(0,$request,$runtime));$ok($queries===0,'zero request id refuses before database access');config(['receipt_qa.request_history_enabled'=>false]);$deny(fn()=>$controller->history($id,$request,$runtime));$ok($queries===0,'disabled request-history feature refuses before database access');
file_put_contents($out,json_encode(['passed'=>true,'checks'=>$checks,'request_id'=>$id,'initial_events'=>127,'events_after_append'=>128,'native_audit_fixture_notes'=>126,'drafts'=>2,'orders'=>0,'request_version_unchanged'=>true,'new_right_names'=>[],'visual_review'=>false,'deployment_capacity_validated'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo count($checks)." request history checks passed.\n";