commit de compras
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
$s=json_decode(file_get_contents(getenv('RECEIPT_COMPOSITION_STATE')),true,64,JSON_THROW_ON_ERROR);$out=$s['root'].'/request-result.json';if(file_exists($out))throw new LogicException('Completed sourcing proof preserved');require $s['app'].'/vendor/autoload.php';$app=require $s['app'].'/bootstrap/app.php';$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();$f=json_decode(file_get_contents($s['root'].'/fixture.json'),true);$n=json_decode(file_get_contents($s['root'].'/native-fixture.json'),true);config(['database.connections.sourcing_verify'=>$s['nativeConnection']]);$db=DB::connection('sourcing_verify');$checks=[];$ok=function($v,$name)use(&$checks,$s){if(!$v)throw new RuntimeException($name);$checks[]=$name;file_put_contents($s['root'].'/request-progress.json',json_encode($checks,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));};$servers=[];$clients=[];$sessions=[];
|
||||
try{
|
||||
foreach([0,1]as$i){$socket=stream_socket_server('tcp://127.0.0.1:0');$port=(int)substr(strrchr(stream_socket_get_name($socket,false),':'),1);fclose($socket);$server=new Symfony\Component\Process\Process([PHP_BINARY,'-S','127.0.0.1:'.$port,'-t',$s['app'].'/public',dirname(__DIR__).'/scripts/receipt-composition-http.php'],$s['app'],null,null,180);$server->start();$servers[]=$server;for($j=0;$j<80;$j++){if($socket=@fsockopen('127.0.0.1',$port,$errno,$error,.1)){fclose($socket);break;}usleep(50000);}$clients[]=new GuzzleHttp\Client(['base_uri'=>'http://127.0.0.1:'.$port,'http_errors'=>false,'timeout'=>25]);}
|
||||
$enter=function($role,$i=0,$query=[])use(&$sessions,$clients){$jar=new GuzzleHttp\Cookie\CookieJar;$r=$clients[$i]->get('/qa/enter/'.$role,['cookies'=>$jar,'query'=>$query]);$sessions[$role.$i]=['jar'=>$jar]+json_decode((string)$r->getBody(),true,32,JSON_THROW_ON_ERROR);};$options=function($role,$i=0,$data=null,$headers=[])use(&$sessions){$v=$sessions[$role.$i];$o=['cookies'=>$v['jar'],'headers'=>$headers+['Accept'=>'application/json','X-SGF-Context'=>$v['context'],'X-CSRF-TOKEN'=>$v['csrf']]];if($data!==null)$o['json']=$data;return $o;};$call=function($role,$path,$data=null,$i=0,$headers=[])use($clients,$options,$s){$r=$clients[$i]->request($data===null?'GET':'POST',$path,$options($role,$i,$data,$headers));$text=(string)$r->getBody();if($r->getStatusCode()>=500)file_put_contents($s['root'].'/request-last-500.txt',$text);return ['status'=>$r->getStatusCode(),'data'=>json_decode($text,true),'body'=>$text];};foreach(['actor','issuer','validator','requester','approver']as$role)$enter($role);$enter('requester',1);$base='/api/purchasing-requests';
|
||||
$preview=function($role,$id,$action,$data,$key=null)use($call,$base){$r=$call($role,$id?$base.'/'.$id:$base);if($r['status']!==200)throw new RuntimeException('Request state unavailable');return $call($role,$base.'/'.$id.'/preview',['operation_key'=>$key??(string)Str::uuid(),'revision'=>$id?$r['data']['revision']:$r['data']['new_revision'],'action'=>$action,'data'=>$data]);};$confirm=fn($role,$id,$cut)=>$call($role,$base.'/'.$id.'/confirm',['preview'=>$cut]);
|
||||
$fingerprint=function()use($db){$h=hash_init('sha256');foreach(['requisitions','requisition_items','approval_steps','requisition_commands','workflow_notices','audit_logs','budget_entries']as$t)foreach($db->table($t)->orderBy('id')->get()as$row)hash_update($h,json_encode((array)$row));return hash_final($h);};$hostBudget=function(){return hash('sha256',json_encode(DB::table('cb_budget_entries')->orderBy('id')->get()));};$hostBefore=$hostBudget();
|
||||
$r=$call('requester',$base);$ok($r['status']===200&&$r['data']['requests']['total']===0&&count($r['data']['centres'])===1,'new composition starts with no mapped requests and an explicit cost centre');$ok($call('requester','/purchasing/requests')['status']===200,'functional request workspace served');$ok($call('validator',$base)['status']===403,'warehouse cannot prepare or inspect internal request economics');$ok($call('requester',$base.'/'.$n['foreign_requisition'])['status']===403,'foreign company request is refused');$ok($call('requester',$base,null,0,['X-SGF-Context'=>'old'])['status']===409,'stale host context is refused');
|
||||
$data=['cost_center_id'=>$n['centre'],'priority'=>'normal','needed_by'=>date('Y-m-d',strtotime('+30 days')),'justification'=>'Necessidade sintética preparada no SGF para um único livro.','items'=>[['description'=>'Material QA MAIN','category'=>'Material','purchase_type'=>'consumable','quantity'=>'1.000','unit'=>'UN','unit_price'=>'100.00','tax_rate'=>'0']]];$ok($preview('actor',0,'save',$data)['status']===403,'procurement cannot impersonate the scoped requesting identity');$bad=$data;$bad['cost_center_id']=999999;$ok($preview('requester',0,'save',$bad)['status']===422,'unmapped cost centre cannot be used');$before=$fingerprint();$key=(string)Str::uuid();$p=$preview('requester',0,'save',$data,$key);$ok($p['status']===200&&$p['data']['impact']['after']['status']==='draft'&&!isset($p['data']['impact']['after']['id']),'original draft preview omits temporary identifiers');$ok($fingerprint()===$before,'preview leaves request budget notices command receipts and audit unchanged');$cut=$p['data']['preview'];$other=$data;$other['justification']='Outro conteúdo para a mesma origem sintética.';$alternate=$preview('requester',0,'save',$other,$key)['data']['preview'];
|
||||
$db->unprepared("CREATE FUNCTION public.request_qa_fail() RETURNS trigger LANGUAGE plpgsql AS \$\$ BEGIN IF NEW.action='sgf.request.command' THEN RAISE EXCEPTION 'synthetic receipt failure'; END IF; RETURN NEW; END \$\$; CREATE TRIGGER request_qa_fail BEFORE INSERT ON audit_logs FOR EACH ROW EXECUTE FUNCTION public.request_qa_fail()");try{$ok($confirm('requester',0,$cut)['status']===500,'host origin audit failure aborts native draft confirmation');$ok($fingerprint()===$before,'native request lines command receipt and audit roll back atomically');}finally{$db->unprepared('DROP TRIGGER request_qa_fail ON audit_logs; DROP FUNCTION public.request_qa_fail()');}
|
||||
$responses=GuzzleHttp\Promise\Utils::unwrap([$clients[0]->postAsync($base.'/0/confirm',$options('requester',0,['preview'=>$cut])),$clients[1]->postAsync($base.'/0/confirm',$options('requester',1,['preview'=>$cut]))]);$statuses=array_map(fn($r)=>$r->getStatusCode(),$responses);sort($statuses);$ok($statuses===[200,201],'two workers recover one native requisition after failure');$id=json_decode((string)$responses[0]->getBody(),true)['request_id'];$ok($db->table('requisitions')->where('tenant_id',$n['tenant'])->count()===1&&$db->table('requisition_items')->where('requisition_id',$id)->count()===1,'one draft and one original line are committed');$ok($confirm('requester',0,$alternate)['status']===409,'same key with another payload is refused');
|
||||
$submit=$preview('requester',$id,'submit',[])['data']['preview'];$data['justification']='Necessidade sintética revista antes da submissão.';$p=$preview('requester',$id,'save',$data);$r=$confirm('requester',$id,$p['data']['preview']);$ok($r['status']===201&&$r['data']['version']===2,'requester edits draft through original lifecycle');$ok($confirm('requester',$id,$submit)['status']===409,'submission prepared before draft edit is refused');$p=$preview('requester',$id,'submit',[]);$grant=DB::table('permission_grants')->where($f['scope'])->where('user_id',$f['requester'])->where('permission','purchasing_requests.submit')->first();DB::table('permission_grants')->where('id',$grant->id)->delete();try{$ok($confirm('requester',$id,$p['data']['preview'])['status']===403,'submission right is checked again at commit');}finally{DB::table('permission_grants')->insert((array)$grant);}$r=$confirm('requester',$id,$p['data']['preview']);$ok($r['status']===201&&$r['data']['status']==='pending','submission reserves and opens the original independent approval');$ok($confirm('requester',$id,$p['data']['preview'])['status']===200&&$db->table('budget_entries')->where('requisition_id',$id)->where('kind','reserve')->count()===1,'repeated submission keeps exactly one reservation');
|
||||
$ok($preview('requester',$id,'decide',['decision'=>'approved','comment'=>'Tentativa de auto aprovação sintética.'])['status']===403,'requester cannot approve their own request');$r=$call('approver',$base.'/'.$id);$ok($r['status']===200&&$r['data']['capabilities']['decide']===true,'mapped department head can decide current exact request');$p=$preview('approver',$id,'decide',['decision'=>'approved','comment'=>'Necessidade conferida por identidade distinta.']);$r=$confirm('approver',$id,$p['data']['preview']);$ok($r['status']===201&&$r['data']['status']==='approved','department head approves through the same original service');$ok($confirm('approver',$id,$p['data']['preview'])['status']===200&&$db->table('approval_steps')->where('requisition_id',$id)->where('decision','approved')->count()===1,'approval repetition records one decision');$source=$call('actor','/api/purchasing-sourcing/requests/'.$id);$ok($source['status']===200&&$source['data']['capabilities']['quote']===true&&count($source['data']['quotes'])===0,'new approved SGF requisition reaches original sourcing without ID rebinding');
|
||||
$tooMuch=$data;$tooMuch['items'][0]['unit_price']='1001.00';$p=$preview('requester',0,'save',$tooMuch);$large=$confirm('requester',0,$p['data']['preview'])['data']['request_id'];$ok($preview('requester',$large,'submit',[])['status']===422&&$db->table('budget_entries')->where('requisition_id',$large)->count()===0,'insufficient native budget refuses submission without a reservation');$tooMuch['items'][0]['unit_price']='50.00';$p=$preview('requester',$large,'save',$tooMuch);$confirm('requester',$large,$p['data']['preview']);$p=$preview('requester',$large,'submit',[]);$confirm('requester',$large,$p['data']['preview']);$p=$preview('approver',$large,'decide',['decision'=>'rejected','comment'=>'Rejeição independente da necessidade alternativa.']);$r=$confirm('approver',$large,$p['data']['preview']);$ok($r['status']===201&&$db->table('budget_entries')->where('requisition_id',$large)->where('kind','release')->count()===1,'original rejection releases only its own reservation');$ok($confirm('approver',$large,$p['data']['preview'])['status']===200&&$db->table('budget_entries')->where('requisition_id',$large)->where('kind','release')->count()===1,'rejection replay does not release twice');$r=$call('actor','/api/purchasing-sourcing');$ok($r['data']['total']===1&&$r['data']['data'][0]['id']===$id,'only approved request enters sourcing list');$r=$call('requester',$base.'?status=rejected&page=1');$ok($r['status']===200&&$r['data']['requests']['total']===1&&$r['data']['requests']['per_page']===15,'request status filter and server page are preserved');$ok($hostBudget()===$hostBefore,'SGF host budget entries are unchanged across draft submission approval and rejection');
|
||||
$n['requisition']=$id;$n['line']=$db->table('requisition_items')->where('requisition_id',$id)->value('id');file_put_contents($s['root'].'/native-fixture.json',json_encode($n,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));file_put_contents($out,json_encode(['passed'=>true,'checks'=>$checks,'concurrency_statuses'=>$statuses,'host_budget_before'=>$hostBefore,'host_budget_after'=>$hostBudget(),'single_native_reservation'=>true,'legacy_ir_not_transferred'=>true,'source_ready_without_rebinding'=>true,'visual_review'=>false,'deployment_capacity_validated'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo count($checks)." request checks passed.\n";
|
||||
}finally{foreach($servers as$i=>$server){$server->stop(2);file_put_contents($s['root'].'/request-http-'.$i.'.log',$server->getOutput()."\n".$server->getErrorOutput());}}
|
||||
Reference in New Issue
Block a user