Files
SGF/scripts/sgf-stock-completion-concurrency.php
T
2026-11-11 09:00:17 +02:00

19 lines
2.9 KiB
PHP

<?php
use Illuminate\Support\Str;
require __DIR__.'/sgf-stock-completion-proof-bootstrap.php';
$dir=$s['root'].'/stock-completion-concurrency';if(!is_dir($dir))mkdir($dir,0700,true);$file=$dir.'/progress.json';$mode=$argv[1]??'';
if($mode==='prepare'){
if(is_file($file))throw new LogicException('Reuse existing preparation');$state=$invoke('warehouse','index',['family'=>'supplier_return']);$p=$policy;
[$draft]=$command('warehouse','supplier_return','save',['kind'=>'supplier_return','stock_item_id'=>$p['stock_item_id'],'warehouse_id'=>$p['warehouse_id'],'receipt_item_id'=>$p['receipt_item_id'],'reference'=>'QA-CONCURRENT-'.Str::uuid(),'quantity'=>'2.000']);
[$approved]=$command('workshop_verify','supplier_return','approve',[],$draft['record_key'],1);$cuts=[];foreach([1,2] as $i)$cuts[$i]=$prepare('warehouse','supplier_return','execute',['event_on'=>'2026-11-10','evidence'=>'Guia fictícia para duas confirmações concorrentes.'],$draft['record_key'],2)['preview'];
file_put_contents($file,json_encode(['cuts'=>$cuts,'record_key'=>$draft['record_key'],'before_movements'=>$native->table('stock_movements')->where('stock_item_id',$p['stock_item_id'])->count()]));
}elseif($mode==='first'){
$p=json_decode(file_get_contents($file),true);$native->beginTransaction();try{$result=$invoke('warehouse','confirm',['preview'=>$p['cuts'][1]]);file_put_contents($dir.'/locked','native tenant and stock rows locked');$end=microtime(true)+20;while(!is_file($dir.'/release')&&microtime(true)<$end)usleep(50000);if(!is_file($dir.'/release'))throw new LogicException('Release missing');$native->commit();file_put_contents($dir.'/first.json',json_encode($result));}finally{while($native->transactionLevel()>0)$native->rollBack();}
}elseif($mode==='second'){
$p=json_decode(file_get_contents($file),true);$start=microtime(true);$result=$invoke('warehouse','confirm',['preview'=>$p['cuts'][2]]);file_put_contents($dir.'/second.json',json_encode(['status'=>isset($result['message'])?503:200,'elapsed_seconds'=>microtime(true)-$start]));
}elseif($mode==='recover'){
$p=json_decode(file_get_contents($file),true);$reject(fn()=>$invoke('warehouse','confirm',['preview'=>$p['cuts'][2]]),409,'second execution refuses stale version after lock release');$first=$invoke('warehouse','confirm',['preview'=>$p['cuts'][1]]);$ok($first['replayed'],'first conference recovers original result');$ok($native->table('stock_movements')->where('stock_item_id',$policy['stock_item_id'])->count()===$p['before_movements']+1,'concurrent executions add exactly one movement');
file_put_contents($dir.'/result.json',json_encode(['passed'=>true,'second_during_lock'=>json_decode(file_get_contents($dir.'/second.json'),true),'after_release_status'=>409,'one_movement'=>true,'first_key_replayed'=>true,'regression_only'=>true],JSON_PRETTY_PRINT));
}else throw new LogicException('Unknown phase');
echo json_encode(['passed'=>true,'phase'=>$mode,'proof'=>$dir.'/result.json']).PHP_EOL;