Files
SGF/scripts/native-payable-page-proof.php
T
2026-11-04 09:37:20 +02:00

35 lines
7.7 KiB
PHP

<?php
use App\Models\{Invoice,Tenant,User};
use App\Enums\PurchasingRole;
use App\Services\SupplierPayables;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tempo\Finance\Payables\Payables;
if($phase==='native-page-setup'){
$bundle=json_decode(file_get_contents($s['root'].'/source.json'),true,64,JSON_THROW_ON_ERROR);$doc=$bundle['source'];$u=User::query()->findOrFail($doc['validated_by']);auth()->setUser($u);$proposal=DB::table('pa_proposals')->where('tenant_id',$u->tenant_id)->first();$result=app(SupplierPayables::class)->engine($u)->command('validate_history',$proposal->id,'Synthetic distinct native page preparation.',(string)Str::uuid());if($result['state']!=='recognised'||fingerprint()!==$bundle['native_originals_sha256'])throw new LogicException('Expected single native recognition without source effects');$reader=User::factory()->create(['tenant_id'=>$u->tenant_id,'purchasing_role'=>PurchasingRole::Finance]);DB::table('pa_access')->insert(['tenant_id'=>$u->tenant_id,'company_id'=>0,'unit_id'=>0,'user_id'=>$reader->id,'person'=>'native-page-reader-'.$reader->id,'action'=>'view']);file_put_contents($s['root'].'/native-page-fixture.json',json_encode(['reader'=>$reader->id,'tenant'=>$u->tenant_id,'invoice'=>$doc['document'],'originals_sha256'=>fingerprint()],JSON_PRETTY_PRINT));echo "Owned native historical recognition ready; originals unchanged.\n";exit;
}
$f=json_decode(file_get_contents($s['root'].'/native-page-fixture.json'),true,64,JSON_THROW_ON_ERROR);$u=User::query()->findOrFail($f['reader']);auth()->setUser($u);$defaultPagedRead=config('common-payables.paged_read_enabled');config(['common-payables.paged_read_enabled'=>true]);$service=app(SupplierPayables::class);$scope=['tenant_id'=>$f['tenant'],'company_id'=>0,'unit_id'=>0];
if($phase==='native-page-http'){
// The HTTP kernel must authenticate from the QA session, never this CLI helper's user.
auth()->forgetGuards();
Illuminate\Support\Facades\Route::middleware('web')->get('/qa/native-enter',function(Illuminate\Http\Request $r)use($f){$u=User::query()->findOrFail($f['reader']);auth()->login($u);$r->session()->regenerate();return response()->json(['csrf'=>csrf_token(),'invoice'=>$f['invoice']])->header('Cache-Control','no-store');});
$app->handleRequest(Illuminate\Http\Request::capture());exit;
}
if($phase==='native-page-seed'){
$target=(int)($argv[3]??0);if(!in_array($target,[1000,5000,10000],true))throw new LogicException('Owned native volume refused');$count=Invoice::query()->where('tenant_id',$f['tenant'])->count();if($count>$target)throw new LogicException('Retained native fixture is not reset');$invoice=(array)DB::table('invoices')->where('id',$f['invoice'])->first();unset($invoice['id']);$item=(array)DB::table('invoice_items')->where('invoice_id',$f['invoice'])->first();unset($item['id']);$validation=(array)DB::table('audit_logs')->where('tenant_id',$f['tenant'])->where('action','invoice.validated')->where('after->invoice_id',$f['invoice'])->first();unset($validation['id']);
for($n=$count;$n<$target;$n++){DB::transaction(function()use($n,$invoice,$item,$validation){$row=$invoice;$row['number']='NATIVE-LOAD-'.str_pad((string)$n,5,'0',STR_PAD_LEFT);$id=DB::table('invoices')->insertGetId($row);DB::table('invoice_items')->insert(array_replace($item,['invoice_id'=>$id]));$after=json_decode($validation['after'],true);$after['invoice_id']=$id;$after['benchmark_only']=true;DB::table('audit_logs')->insert(array_replace($validation,['after'=>json_encode($after,JSON_THROW_ON_ERROR)]));});}
file_put_contents($s['root'].'/native-page-volume.json',json_encode(['invoices'=>$target,'native_workflow_invoices'=>1,'unrecognised_benchmark_clones'=>$target-1,'indices_added'=>false],JSON_PRETTY_PRINT));echo 'Native synthetic volume '.$target." ready.\n";exit;
}
if($phase==='native-page-bench'){
$strategy=$argv[3]??'';if(!in_array($strategy,['legacy','page25','page100','invoice'],true))throw new LogicException('Unknown native read strategy');$volume=Invoice::query()->where('tenant_id',$f['tenant'])->count();$service->listing($u,$f['invoice']);$queries=0;$capture=false;DB::listen(function($event)use(&$queries,&$capture){if($capture)$queries++;});gc_collect_cycles();memory_reset_peak_usage();$baseline=memory_get_usage(true);$capture=true;$start=hrtime(true);
if($strategy==='legacy'){config(['common-payables.paged_read_enabled'=>false]);$data=$service->listing($u);}elseif($strategy==='invoice'){$data=$service->listing($u,$f['invoice']);}else{$data=$service->listing($u,null,['per_page'=>$strategy==='page25'?25:100]);}
$ms=(hrtime(true)-$start)/1e6;$capture=false;$peak=memory_get_peak_usage(true);$windows=[];
foreach($strategy==='legacy'?[25,100]:($strategy==='invoice'?[]:[$strategy==='page25'?25:100]) as $size){$ids=Invoice::query()->where('tenant_id',$f['tenant'])->orderByDesc('id')->limit($size)->pluck('id')->all();$set=array_fill_keys($ids,true);$documents=array_values(array_filter($data['documents'],fn($doc)=>isset($set[$doc['local_id']])));usort($documents,fn($a,$b)=>$a['local_id']<=>$b['local_id']);$windows[$size]=hash('sha256',Payables::canonical($documents));}
$originalSource=DB::table('pa_sources')->where($scope)->where('local_source_id',$f['invoice'])->value('id');$positions=array_values(array_filter($data['obligations'],fn($o)=>$o['source_id']===$originalSource));$report=['volume'=>$volume,'strategy'=>$strategy,'time_ms'=>round($ms,3),'queries'=>$queries,'php_heap_baseline_bytes'=>$baseline,'php_heap_peak_bytes'=>$peak,'documents'=>count($data['documents']),'obligations'=>count($data['obligations']),'document_fingerprints'=>$windows,'original_position_fingerprint'=>hash('sha256',Payables::canonical($positions)),'original_balance_minor'=>$positions[0]['balance_minor']??null,'php'=>PHP_VERSION,'independent_php_process'=>true,'indices_added'=>false,'not_deployment_capacity'=>true];file_put_contents($s['root'].'/native-bench-'.$volume.'-'.$strategy.'.json',json_encode($report,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo json_encode($report)."\n";exit;
}
if($phase==='native-page-post'){
DB::beginTransaction();DB::statement('SET TRANSACTION READ ONLY');try{$row=DB::table('invoices')->where('id',$f['invoice'])->first();$book=DB::table('pa_obligations')->where($scope)->first();$import=DB::table('pa_history_imports')->where($scope)->first();$statuses=DB::table('external_payments')->where('tenant_id',$f['tenant'])->orderBy('id')->pluck('status')->all();$originals=[];foreach(['invoices','invoice_items','external_payments','budget_entries','budget_consumptions','stock_movements'] as $table){$q=DB::table($table);if($table==='invoices')$q->where('id',$f['invoice']);elseif(in_array($table,['invoice_items','external_payments'],true))$q->where('invoice_id',$f['invoice']);else $q->where('tenant_id',$f['tenant']);$originals[$table]=$q->orderBy('id')->get()->map(fn($r)=>(array)$r)->all();}$hash=hash('sha256',Payables::canonical($originals));if((int)$row->total_minor!==6000||(int)$book->principal_minor!==6000||(int)$import->confirmed_minor!==3000||$statuses!==['confirmed','pending','rejected']||DB::table('pa_obligations')->where($scope)->count()!==1||$hash!==$f['originals_sha256']||$defaultPagedRead!==false)throw new LogicException('Native final ledger differs');file_put_contents($s['root'].'/native-page-post.json',json_encode(['read_only'=>true,'native_workflow_invoice_preserved'=>true,'originals_sha256'=>$hash,'original_source_stock_and_budget_unchanged'=>true,'one_obligation'=>true,'principal_minor'=>6000,'confirmed_minor'=>3000,'statuses'=>$statuses,'invoices'=>DB::table('invoices')->where('tenant_id',$f['tenant'])->count(),'new_paged_read_default'=>$defaultPagedRead],JSON_PRETTY_PRINT));}finally{DB::rollBack();}echo "Native final original and single financial authority preserved.\n";exit;
}
throw new LogicException('Unknown native QA phase');