27 lines
7.9 KiB
PHP
27 lines
7.9 KiB
PHP
<?php
|
|
require __DIR__.'/sgf-payable-due-proof-bootstrap.php';
|
|
$before=[];foreach(['invoices','pa_obligations','pa_sources','pa_commands','stock_items','stock_movements','gl_journals','audit_logs']as$t)$before[$t]=$native->table($t)->orderBy('id')->get()->toJson();$grants=$host->table('permission_grants')->orderBy('id')->get()->toJson();$baseVersions=$native->table('pd_due_versions')->count();$baseCommands=$native->table('pd_due_commands')->count();$id=(int)$native->table('pa_obligations')->orderBy('id')->value('id');$offset=(int)$native->table('pd_due_versions')->where('obligation_id',$id)->max('version');$historyBefore=$native->table('pd_due_versions')->where('obligation_id',$id)->count();$relativeInput=$dueInput;$dueInput=fn($action,$id,$version,$data)=>$relativeInput($action,$id,$version+$offset,$data);$query=['obligation_id'=>$id,'reference_on'=>'2026-11-30'];$data=['due_on'=>'2026-11-30','evidence'=>'QA artificial; termos de teste, não condição real.'];
|
|
$host->beginTransaction();$native->beginTransaction();try{
|
|
$initial=$dueCall(17,'index',$query);$ok($initial['value']['basis']['position']['days_overdue']===null,'original unknown due not zero');
|
|
$input=$dueInput('prepare',$id,0,$data);$cut=$dueCall(13,'preview',$input);$ok($cut['status']===200&&$cut['value']['impact']['original_obligation_changed']===false,'preview explicit non-monetary impact');$ok($native->table('pd_due_versions')->count()===$baseVersions,'preview rolled back');
|
|
[$v1,$first,$key]=$dueCommand(13,$input);$ok($v1['state']==='prepared'&&$v1['version']===$offset+1,'preparation persisted');$replay=$dueCall(13,'confirm',['preview'=>$first['preview']]);$ok($replay['value']['replayed']&&$replay['value']['id']===$v1['id']&&$native->table('pd_due_commands')->count()===$baseCommands+1,'repetition no duplicate');
|
|
$rejectCatalogue(fn()=>$dueCall(13,'preview',$dueInput('review',$id,1,['decision'=>'approved'])),403,'own review refused');$rejectCatalogue(fn()=>$dueCall(14,'preview',$input),403,'reviewer cannot prepare');$rejectCatalogue(fn()=>$dueCall(17,'preview',$input),403,'reader cannot prepare');
|
|
$oldReview=$dueCall(14,'preview',$dueInput('review',$id,1,['decision'=>'approved']));[$v2]=$dueCommand(13,$dueInput('prepare',$id,1,$data));$rejectCatalogue(fn()=>$dueCall(14,'confirm',['preview'=>$oldReview['value']['preview']]),409,'stale review refused');
|
|
[$v3]=$dueCommand(14,$dueInput('review',$id,2,['decision'=>'approved']));$ok($v3['state']==='approved','distinct review applied');$read=$dueCall(17,'index',$query);$ok($read['value']['days_overdue']===0&&$read['value']['due_status']==='not_overdue'&&$read['value']['basis']['position']['due_on']===null,'same day not overdue original date intact');$later=$dueCall(17,'index',array_replace($query,['reference_on'=>'2026-12-01']));$ok($later['value']['days_overdue']===1,'explicit next day one day overdue');$earlier=$dueCall(17,'index',array_replace($query,['reference_on'=>'2026-11-29']));$ok($earlier['value']['days_overdue']===0,'future due no negative delay');
|
|
[$v4]=$dueCommand(13,$dueInput('prepare',$id,3,['due_on'=>null,'evidence'=>'QA correcção explícita para desconhecido.']));[$v5]=$dueCommand(14,$dueInput('review',$id,4,['decision'=>'refused']));$ok($dueCall(17,'index',$query)['value']['reviewed_terms']['due_on']==='2026-11-30','refusal preserves previous terms');
|
|
[$v6]=$dueCommand(13,$dueInput('prepare',$id,5,['due_on'=>null,'evidence'=>'QA correcção aprovada para desconhecido.']));[$v7]=$dueCommand(14,$dueInput('review',$id,6,['decision'=>'approved']));$unknown=$dueCall(17,'index',$query);$ok($unknown['value']['days_overdue']===null&&$unknown['value']['due_status']==='unknown','approved clear returns unknown');
|
|
$h100=$dueCall(17,'index',$query+['per_page'=>100]);$ok($h100['value']['history']['total']===$historyBefore+7&&$h100['value']['history']['data']===$unknown['value']['history']['data'],'history25/100 equivalent');$empty=$dueCall(17,'index',$query+['page'=>100000]);$ok($empty['value']['history']['data']===[],'history out of range no fallback');
|
|
$receipt=$dueCall(13,'receipt',[],$key,'fresh');$ok($receipt['value']['operation_executed']===false&&$receipt['value']['result']['id']===$v1['id'],'own receipt fresh session');$rejectCatalogue(fn()=>$dueCall(14,'receipt',[],$key),404,'other receipt hidden');
|
|
$rejectCatalogue(fn()=>$dueCall(17,'index',$query+['per_page'=>1000]),422,'unbounded history refused');$rejectCatalogue(fn()=>$dueCall(17,'index',$query+['tenant_id'=>2]),422,'context override refused');$rejectCatalogue(fn()=>$dueCall(17,'index',array_replace($query,['obligation_id'=>2147483647])),403,'foreign obligation refused');$rejectCatalogue(fn()=>$dueCall(17,'index',$query,null,'due-proof','accounting'),409,'wrong module refused');$rejectCatalogue(fn()=>$dueCall(13,'preview',array_replace($input,['data'=>['due_on'=>'2026-02-30','evidence'=>$data['evidence']]])),422,'impossible date refused');
|
|
$fresh=$dueCall(13,'preview',$dueInput('prepare',$id,7,$data));$rejectCatalogue(fn()=>$dueCall(13,'confirm',['preview'=>$fresh['value']['preview']],null,'other-generation'),409,'changed context invalidates confirmation');
|
|
// Original source mutation only inside a nested savepoint; no real workflow repeated.
|
|
$native->beginTransaction();try{$sourceId=$native->table('pa_obligations as o')->join('pa_sources as s','s.id','=','o.source_id')->where('o.id',$id)->value('s.local_source_id');$native->table('invoices')->where('id',$sourceId)->update(['number'=>'QA-ORIGEM-ALTERADA-APENAS-SAVEPOINT']);$rejectCatalogue(fn()=>$dueCall(13,'confirm',['preview'=>$fresh['value']['preview']]),409,'changed financial basis invalidates confirmation');}finally{$native->rollBack();}
|
|
// Force failure at final receipt insertion and then recover the same cut.
|
|
$native->unprepared("CREATE FUNCTION pd_test_fail() RETURNS trigger LANGUAGE plpgsql AS \$\$ BEGIN RAISE EXCEPTION 'QA_TEST_FAILURE'; END; \$\$; CREATE TRIGGER pd_test_failure BEFORE INSERT ON pd_due_commands FOR EACH ROW EXECUTE FUNCTION pd_test_fail();");$failed=false;try{$dueCall(13,'confirm',['preview'=>$fresh['value']['preview']]);}catch(Illuminate\Database\QueryException){$failed=true;}$ok($failed&&$native->table('pd_due_versions')->where('obligation_id',$id)->count()===$historyBefore+7,'failed receipt rolls back version and audit');$native->unprepared('DROP TRIGGER pd_test_failure ON pd_due_commands; DROP FUNCTION pd_test_fail();');$recovered=$dueCall(13,'confirm',['preview'=>$fresh['value']['preview']]);$ok($recovered['status']===201&&$recovered['value']['version']===$offset+8,'same cut recovers once after failure');
|
|
$next=$dueCall(13,'preview',$dueInput('prepare',$id,8,$data));$host->table('permission_grants')->where(['tenant_id'=>1,'company_id'=>2,'unit_id'=>2,'user_id'=>13,'permission'=>'supplier_payables.due.prepare'])->delete();$rejectCatalogue(fn()=>$dueCall(13,'confirm',['preview'=>$next['value']['preview']]),403,'revoked right rechecked');
|
|
$immutable=false;$native->beginTransaction();try{$native->table('pd_due_versions')->where('id',$v1['id'])->update(['reason'=>'Alteração indevida do histórico']);}catch(Illuminate\Database\QueryException$e){$immutable=str_contains($e->getMessage(),'SGF_PAYABLE_DUE_APPEND_ONLY');}finally{$native->rollBack();}$ok($immutable,'history immutable');
|
|
}finally{while($native->transactionLevel()>0)$native->rollBack();while($host->transactionLevel()>0)$host->rollBack();}
|
|
foreach($before as$t=>$json)$ok($native->table($t)->orderBy('id')->get()->toJson()===$json,'original '.$t.' unchanged');$ok($host->table('permission_grants')->orderBy('id')->get()->toJson()===$grants,'grants unchanged by tests');$ok($native->table('pd_due_versions')->count()===$baseVersions&&$native->table('pd_due_commands')->count()===$baseCommands,'all regression versions and commands rolled back');
|
|
file_put_contents($work.'/payable-due-result.json',json_encode(['passed'=>true,'checks'=>$checks,'fixtures_rolled_back'=>true,'new_demo'=>false,'new_financial_effects'=>0],JSON_PRETTY_PRINT));echo json_encode(['passed'=>true,'checks'=>count($checks),'proof'=>$work.'/payable-due-result.json']).PHP_EOL;
|
|
|