Files
SGF/scripts/direct-cost-concurrency.php
nala d10ba06edc Close isolated direct cost trial by delegated technical acceptance
Preserve verified 275 MZN and incomplete 175 MZN demonstrations. Keep 090000 and four real cost grants unapplied; real costing and fiscal blocks remain intact.
2026-10-27 12:39:41 +02:00

27 lines
4.5 KiB
PHP

<?php
require __DIR__.'/../vendor/autoload.php';$app=require __DIR__.'/../bootstrap/app.php';$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
use App\Modules\Workshop\DirectCosts;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Symfony\Component\Process\Process;
DirectCosts::guard();$f=json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/browser-fixture.json'),true,32,JSON_THROW_ON_ERROR);$fx=$f['direct_costs'];$proof=[];$key=fn()=>(string)Str::uuid();
$base=['tenant'=>$f['tenant'],'company'=>$f['company'],'unit'=>$f['unit'],'order'=>$fx['order']];
$pair=function($one,$two)use($base){
$a=new Process([PHP_BINARY,'scripts/direct-cost-worker.php','--env=testing'],base_path(),null,json_encode($one+$base+['pause'=>true]));$b=new Process([PHP_BINARY,'scripts/direct-cost-worker.php','--env=testing'],base_path(),null,json_encode($two+$base));
$a->start();if(!$a->waitUntil(fn($t,$v)=>str_contains($v,'LOCK_HELD')))throw new RuntimeException('Concurrent lock not observed.');$b->start();$a->wait();$b->wait();$out=[];
foreach([$a,$b] as $p){if(!$p->isSuccessful())throw new RuntimeException('Cost worker failed.');$lines=array_values(array_filter(explode("\n",trim($p->getOutput())),fn($v)=>str_starts_with($v,'{')));$out[]=json_decode(end($lines),true,32,JSON_THROW_ON_ERROR);}return $out;
};
$check=function($out,bool $replay){if($out[0]['status']!==200||$out[1]['status']!==($replay?200:409)||($replay&&(!$out[1]['result']['replayed']||$out[0]['result']['id']!==$out[1]['result']['id'])))throw new RuntimeException('Unexpected concurrent outcome.');};
$rate=['method'=>'rate','actor'=>$f['manager_id'],'key'=>$key(),'input'=>['entry_id'=>$fx['material'],'entry_version'=>1,'identifier'=>'TEST-CONCURRENT-'.Str::uuid(),'version'=>1,'previous_id'=>null,'currency'=>'MZN','unit'=>'UN','amount'=>'1','evidence'=>'Taxa sintética concorrente.']];
$out=$pair($rate,$rate);$check($out,true);$proof['rate_repetition_one_version']=true;$rid=$out[0]['result']['id'];
$decision=['method'=>'decide','actor'=>$f['reader_id'],'key'=>$key(),'input'=>['kind'=>'rate','id'=>$rid,'version'=>1,'decision'=>'approved','reason'=>'Validação sintética concorrente.']];$check($pair($decision,$decision),true);$proof['decision_repetition_one_effect']=true;
$one=$rate;$one['key']=$key();$one['input']['version']=2;$one['input']['previous_id']=$rid;$two=$one;$two['key']=$key();$two['input']['amount']='2';$check($pair($one,$two),false);$proof['competing_rate_versions_one_winner']=true;
$rates=DB::table('dc_rates')->where('order_id',$fx['order'])->whereIn('identifier',['TEST-TRABALHO','TEST-MATERIAL'])->get()->keyBy('identifier');
$lines=[['entry_id'=>$fx['time'],'entry_version'=>1,'rate_id'=>$rates['TEST-TRABALHO']->id,'rate_version'=>1],['entry_id'=>$fx['material'],'entry_version'=>1,'rate_id'=>$rates['TEST-MATERIAL']->id,'rate_version'=>1]];
$last=DB::table('dc_calculations')->where('order_id',$fx['order'])->orderByDesc('version')->first();
$calc=['method'=>'calculate','actor'=>$f['manager_id'],'key'=>$key(),'input'=>['currency'=>'MZN','version'=>$last->version+1,'previous_id'=>$last->id,'lines'=>$lines]];$out=$pair($calc,$calc);$check($out,true);$proof['calculation_repetition_one_snapshot']=true;
$last=DB::table('dc_calculations')->where('order_id',$fx['order'])->orderByDesc('version')->first();$calc['key']=$key();$calc['input']['version']=$last->version+1;$calc['input']['previous_id']=$last->id;$calc['input']['lines'][1]['rate_id']=null;$calc['input']['lines'][1]['rate_version']=null;$two=$calc;$two['key']=$key();$two['input']['lines']=$lines;$two['input']['lines'][0]['rate_id']=null;$two['input']['lines'][0]['rate_version']=null;$out=$pair($calc,$two);$check($out,false);$proof['competing_calculations_one_revision']=true;
$last=DB::table('dc_calculations')->where('order_id',$fx['order'])->orderByDesc('version')->first();$calc['key']=$key();$calc['input']['version']=$last->version+1;$calc['input']['previous_id']=$last->id;$calc['input']['lines']=$lines;
$oldDecision=['method'=>'decide','actor'=>$f['reader_id'],'key'=>$key(),'input'=>['kind'=>'calculation','id'=>$last->id,'version'=>$last->version,'decision'=>'approved','reason'=>'Decisão de uma versão agora antiga.']];$check($pair($calc,$oldDecision),false);$proof['new_calculation_refuses_old_approval']=true;
file_put_contents(base_path('docs/proofs/direct-cost-concurrency.json'),json_encode(['database'=>'sgf_tempo_test','overlapping_processes_per_case'=>2,'checks'=>$proof,'synthetic_only'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo "Six overlapping-process direct cost scenarios passed.\n";