109 lines
19 KiB
PHP
109 lines
19 KiB
PHP
<?php
|
|
// Owned fresh databases only; no installed connection, source/demonstration reset or cleanup.
|
|
$file=realpath($argv[1]??'');$state=json_decode(file_get_contents('C:/laragon/data/sgf-tempo/run/modular-regression-current.json'),true,512,JSON_THROW_ON_ERROR);
|
|
$private=realpath($state['root'].'/private');
|
|
if(!$file||!$private||!str_starts_with($file,$private.DIRECTORY_SEPARATOR)||$state['port']!==55439)throw new LogicException('QA settings refused');
|
|
$s=json_decode(file_get_contents($file),true,512,JSON_THROW_ON_ERROR);$phase=$argv[2]??'';$native=in_array($phase,['native','native-post'],true)||str_starts_with($phase,'native-page-')||str_starts_with($phase,'native-cycle-');$mode=$native?'native':'sgf';$c=$s[$mode];
|
|
if(!preg_match('/^[a-f0-9]{12}$/D',$s['tag'])||$c['database']!==($native?'compras_receipts_test_':'payable_sgf_').$s['tag']||$c['role']!=='payable_history_'.$mode.'_'.$s['tag'])throw new LogicException('QA identity refused');
|
|
$env=['APP_ENV'=>$native?'testing':'payable-testing','APP_DEBUG'=>'false','APP_KEY'=>'base64:'.base64_encode(str_repeat('s',32)),'DB_CONNECTION'=>'pgsql','DB_URL'=>'','DB_HOST'=>'127.0.0.1','DB_PORT'=>'55439','DB_DATABASE'=>$c['database'],'DB_USERNAME'=>$c['role'],'DB_PASSWORD'=>$c['password'],'SESSION_DRIVER'=>'array','CACHE_STORE'=>'array','QUEUE_CONNECTION'=>'sync','MAIL_MAILER'=>'array','BCRYPT_ROUNDS'=>'4','LARAVEL_STORAGE_PATH'=>$c['storage'],'APP_CONFIG_CACHE'=>$c['storage'].'/cache/config.php','APP_ROUTES_CACHE'=>$c['storage'].'/cache/routes.php','APP_SERVICES_CACHE'=>$c['storage'].'/cache/services.php','APP_PACKAGES_CACHE'=>$c['storage'].'/cache/packages.php','VIEW_COMPILED_PATH'=>$c['storage'].'/framework/views','COMMON_PAYABLES_PATH'=>dirname(__DIR__).'/packages/payables','COMMON_PAYABLES_ENABLED'=>'true','COMMON_PAYABLES_HISTORY_ENABLED'=>'true','PAYABLE_SOURCE_INSTANCE'=>$s['source_instance']];
|
|
if(isset($s['common_bundle'])){
|
|
$bundleRoot=realpath($s['common_bundle']);if(!$native||!$bundleRoot||!str_starts_with($bundleRoot,$private.DIRECTORY_SEPARATOR))throw new LogicException('Owned standalone artifact required');
|
|
$bundle=['payables'=>$bundleRoot.'/payables','ledger'=>$bundleRoot.'/general-ledger'];$env['COMMON_FINANCE_BUNDLE_ROOT']=$bundleRoot;$env['COMMON_FINANCE_BUNDLE_SHA256']=$s['common_bundle_sha256']??'';
|
|
}
|
|
foreach($env as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}
|
|
if(str_starts_with($phase,'ledger-sales-')){foreach(['APP_ENV'=>'testing','SGF_SALES_LEDGER_QA'=>'true'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}}
|
|
if(str_starts_with($phase,'ledger-cost-')){foreach(['APP_ENV'=>'testing','SGF_COST_LEDGER_QA'=>'true'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}}
|
|
if(str_starts_with($phase,'ledger-cost-correction-')||($s['cost_correction']??false)===true){putenv('SGF_COST_CORRECTION_QA=true');$_ENV['SGF_COST_CORRECTION_QA']=$_SERVER['SGF_COST_CORRECTION_QA']='true';}
|
|
if(str_starts_with($phase,'ledger-cost-composition-')||($s['ledger_composition']??false)===true){foreach(['SGF_COST_CORRECTION_QA'=>'true','SGF_SALES_LEDGER_QA'=>'true','SGF_LEDGER_COMPOSITION_QA'=>'true'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}}
|
|
if(str_starts_with($phase,'ledger-cost-industrial-')||($s['industrial_ledger']??false)===true){putenv('SGF_INDUSTRIAL_LEDGER_QA=true');$_ENV['SGF_INDUSTRIAL_LEDGER_QA']=$_SERVER['SGF_INDUSTRIAL_LEDGER_QA']='true';}
|
|
if(str_starts_with($phase,'ledger-cost-industrial-correction-')||($s['industrial_correction']??false)===true){putenv('SGF_INDUSTRIAL_CORRECTION_QA=true');$_ENV['SGF_INDUSTRIAL_CORRECTION_QA']=$_SERVER['SGF_INDUSTRIAL_CORRECTION_QA']='true';}
|
|
if($phase==='ledger-cost-http'){foreach(['APP_ENV'=>'browser-testing','SESSION_DRIVER'=>'file','SESSION_COOKIE'=>'sgf_cost_'.$s['tag'],'SESSION_SECURE_COOKIE'=>'false'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}}
|
|
if($phase==='ledger-sales-http'){foreach(['APP_ENV'=>'browser-testing','SESSION_DRIVER'=>'file','SESSION_COOKIE'=>'sgf_sales_'.$s['tag'],'SESSION_SECURE_COOKIE'=>'false'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}}
|
|
if(isset($s['app_key'])){putenv('APP_KEY='.$s['app_key']);$_ENV['APP_KEY']=$_SERVER['APP_KEY']=$s['app_key'];}
|
|
if(in_array($phase,['http','settlement-page-http','settlement-read-http','payable-report-http','ledger-http'],true)){putenv('SESSION_DRIVER=file');$_ENV['SESSION_DRIVER']=$_SERVER['SESSION_DRIVER']='file';}
|
|
if($phase==='ledger-http'){foreach(['SESSION_COOKIE'=>'sgf_ledger_'.$s['tag'],'SESSION_SECURE_COOKIE'=>'false'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}}
|
|
if($phase==='native-page-http'){
|
|
foreach(['APP_ENV'=>'payable-testing','SESSION_DRIVER'=>'file','SESSION_COOKIE'=>'qa_native_page_'.$s['tag'],'SESSION_SECURE_COOKIE'=>'false','COMMON_PAYABLES_HISTORY_DECISIONS_ENABLED'=>'false'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}
|
|
}
|
|
if($phase==='native-cycle-http'){
|
|
foreach(['APP_ENV'=>'payable-testing','SESSION_DRIVER'=>'file','SESSION_COOKIE'=>'sgc_ledger_'.$s['tag'],'SESSION_SECURE_COOKIE'=>'false'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}
|
|
}
|
|
require $c['root'].'/vendor/autoload.php';$app=require $c['root'].'/bootstrap/app.php';$app->useStoragePath($c['storage']);$app->addAbsoluteCachePathPrefix('C:');$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Tempo\Finance\Payables\{Payables,HistoricalPayments,HistorySchema};
|
|
$i=DB::selectOne("SELECT current_database() AS db,current_user AS role,inet_server_port() AS port,current_setting('cluster_name') AS cluster");
|
|
if($i->db!==$c['database']||$i->role!==$c['role']||(int)$i->port!==55439||$i->cluster!==$s['cluster'])throw new LogicException('Effective QA host refused');
|
|
$role=DB::selectOne('SELECT rolsuper OR rolcreatedb OR rolcreaterole OR rolreplication OR rolbypassrls AS elevated FROM pg_roles WHERE rolname=current_user');if($role->elevated)throw new LogicException('Elevated role refused');
|
|
function fingerprint():string {$rows=[];foreach(['invoices','invoice_items','external_payments','budget_entries','budget_consumptions','stock_movements'] as $t)$rows[$t]=DB::table($t)->orderBy('id')->get()->map(fn($r)=>(array)$r)->all();return hash('sha256',Payables::canonical($rows));}
|
|
if(str_starts_with($phase,'ledger-sales-')){require __DIR__.'/ledger-sales-proof.php';exit;}
|
|
if(str_starts_with($phase,'ledger-cost-')){require __DIR__.'/ledger-cost-proof.php';exit;}
|
|
if(str_starts_with($phase,'native-cycle-')){require __DIR__.'/ledger-cycle-native-proof.php';exit;}
|
|
if($phase==='native'){
|
|
if(DB::getSchemaBuilder()->hasTable('users'))throw new LogicException('Fresh native database required');
|
|
Illuminate\Support\Facades\Artisan::call('migrate',['--force'=>true]);require $c['root'].'/tests/Support/PaymentFixture.php';$f=paymentFixture();
|
|
foreach([$f['author'],$f['validator']] as $u)foreach(['view','prepare','validate'] as $action)DB::table('pa_access')->insert(['tenant_id'=>$u->tenant_id,'company_id'=>0,'unit_id'=>0,'user_id'=>$u->id,'person'=>'native-history-person-'.$u->id,'action'=>$action]);
|
|
foreach([['30.00','confirmed'],['10.00','pending'],['5.00','rejected']] as [$amount,$status]){
|
|
auth()->setUser($f['author']);$p=app(App\Services\ExternalPaymentWorkflow::class)->register($f['author'],$f['invoice'],[...$f['payment_data'],'amount'=>$amount,'external_reference'=>'SIM-HISTORY-'.$status]);
|
|
if($status!=='pending'){auth()->setUser($f['validator']);app(App\Services\ExternalPaymentWorkflow::class)->review($f['validator'],$p,$status,'Synthetic original historical decision.');}
|
|
}
|
|
auth()->setUser($f['author']);$service=app(App\Services\SupplierPayables::class);$doc=$service->document($f['author'],$f['invoice']->id);
|
|
$proposal=$service->engine($f['author'])->command('prepare_history',$f['invoice']->id,'Synthetic historical receipt capture.',(string)Str::uuid());
|
|
$snapshot=json_decode(DB::table('pa_proposals')->where('id',$proposal['proposal_id'])->value('snapshot'),true,64,JSON_THROW_ON_ERROR);
|
|
$capture=$snapshot['_history_import'];$bundle=['source'=>$doc,'capture'=>$capture,'native_originals_sha256'=>fingerprint(),'native_obligations'=>DB::table('pa_obligations')->count()];
|
|
file_put_contents($s['root'].'/source.json',Payables::canonical($bundle));echo "Native conferred invoice and original 30/10/5 history captured; no native obligation recognised.\n";exit;
|
|
}
|
|
if($phase==='native-post'){
|
|
DB::beginTransaction();DB::statement('SET TRANSACTION READ ONLY');
|
|
$bundle=json_decode(file_get_contents($s['root'].'/source.json'),true,64,JSON_THROW_ON_ERROR);if(fingerprint()!==$bundle['native_originals_sha256']||DB::table('pa_obligations')->count()!==0)throw new LogicException('Native originals changed');
|
|
file_put_contents($s['root'].'/native-post.json',json_encode(['originals_unchanged'=>true,'originals_sha256'=>fingerprint(),'native_obligations'=>0,'payments'=>DB::table('external_payments')->orderBy('id')->pluck('status')->all(),'read_only_observation'=>true],JSON_PRETTY_PRINT));DB::rollBack();echo "Native originals and payment states preserved.\n";exit;
|
|
}
|
|
if($phase==='sgf'){
|
|
if(DB::getSchemaBuilder()->hasTable('users'))throw new LogicException('Fresh SGF database required');
|
|
foreach(['0001_01_01_000000_create_users_table.php','2026_10_25_000000_create_identity_foundation.php','2026_10_25_010000_add_identity_workflow.php'] as $m)(require $c['root'].'/database/migrations/'.$m)->up();
|
|
Tempo\Finance\Payables\PayablesSchema::install();HistorySchema::install();
|
|
DB::statement('ALTER TABLE permission_grants DROP CONSTRAINT permission_grants_scope_check');DB::statement("ALTER TABLE permission_grants ADD CONSTRAINT permission_grants_scope_check CHECK ((unit_id IS NULL AND permission='company.view') OR (unit_id IS NOT NULL AND permission IN ('unit.view','supplier_payables.view')))");
|
|
$now=now();$tenant=DB::table('tenants')->insertGetId(['name'=>'Synthetic history tenant','created_at'=>$now,'updated_at'=>$now]);$company=DB::table('companies')->insertGetId(['tenant_id'=>$tenant,'name'=>'Synthetic history company','created_at'=>$now,'updated_at'=>$now]);$unit=DB::table('units')->insertGetId(['tenant_id'=>$tenant,'company_id'=>$company,'name'=>'Synthetic history unit','created_at'=>$now,'updated_at'=>$now]);$scope=['tenant_id'=>$tenant,'company_id'=>$company,'unit_id'=>$unit];$actors=[];
|
|
foreach(['prepare'=>['view','prepare'],'validate'=>['view','validate'],'read'=>['view']] as $name=>$actions){$u=App\Models\User::create(['name'=>'Synthetic '.$name,'email'=>$name.'@history.example.test','password'=>bin2hex(random_bytes(16))]);$actors[$name]=$u->id;DB::table('company_memberships')->insert(['tenant_id'=>$tenant,'company_id'=>$company,'user_id'=>$u->id]);foreach(['company.view'=>null,'unit.view'=>$unit,'supplier_payables.view'=>$unit] as $p=>$which)DB::table('permission_grants')->insert([...$scope,'unit_id'=>$which,'user_id'=>$u->id,'permission'=>$p,'created_at'=>$now,'updated_at'=>$now]);foreach($actions as $action)DB::table('pa_access')->insert($scope+['user_id'=>$u->id,'action'=>$action,'person'=>'sgf-history-person-'.$u->id]);}
|
|
DB::statement('CREATE TABLE pa_imported_sources(id bigserial PRIMARY KEY,tenant_id bigint,company_id bigint,unit_id bigint,payload jsonb,receipt_hash varchar(64))');
|
|
DB::statement('CREATE TABLE pa_source_histories(id bigserial PRIMARY KEY,tenant_id bigint NOT NULL,company_id bigint NOT NULL,unit_id bigint NOT NULL,local_source_id bigint NOT NULL REFERENCES pa_imported_sources(id),version int NOT NULL CHECK(version>0),payload jsonb NOT NULL,receipt_hash varchar(64) NOT NULL,created_at timestamp NOT NULL,UNIQUE(tenant_id,company_id,unit_id,local_source_id,version))');
|
|
DB::statement("CREATE FUNCTION qa_history_immutable() RETURNS trigger LANGUAGE plpgsql AS 'BEGIN RAISE EXCEPTION ''Immutable source history receipt''; END'");DB::statement('CREATE TRIGGER immutable BEFORE UPDATE OR DELETE ON pa_source_histories FOR EACH ROW EXECUTE FUNCTION qa_history_immutable()');
|
|
$bundle=json_decode(file_get_contents($s['root'].'/source.json'),true,64,JSON_THROW_ON_ERROR);$doc=$bundle['source'];unset($doc['local_id']);$id=DB::table('pa_imported_sources')->insertGetId($scope+['payload'=>Payables::canonical($doc),'receipt_hash'=>hash('sha256',Payables::canonical($doc))]);
|
|
$payload=['version'=>App\Modules\Purchasing\SupplierHistoryReceipts::VERSION,'receipt_version'=>1,'source_key'=>Payables::identity($doc),'source_revision'=>Payables::revision($doc),'history_hash'=>$bundle['capture']['history_hash'],'payments'=>$bundle['capture']['payments']];
|
|
DB::table('pa_source_histories')->insert($scope+['local_source_id'=>$id,'version'=>1,'payload'=>Payables::canonical($payload),'receipt_hash'=>hash('sha256',Payables::canonical($payload)),'created_at'=>$now]);
|
|
file_put_contents($s['root'].'/fixture.json',json_encode(['scope'=>$scope,'actors'=>$actors,'source_id'=>$id],JSON_PRETTY_PRINT));echo "SGF receipt and existing-right QA identities installed only in fresh isolated database.\n";exit;
|
|
}
|
|
if($phase==='worker'){
|
|
$input=realpath($argv[3]??'');if(!$input||!str_starts_with($input,realpath($s['root']).DIRECTORY_SEPARATOR))throw new LogicException('Worker input refused');$w=json_decode(file_get_contents($input),true,64,JSON_THROW_ON_ERROR);$f=json_decode(file_get_contents($s['root'].'/fixture.json'),true,64,JSON_THROW_ON_ERROR);
|
|
config(['supplier_payable_history.synthetic_enabled'=>true]);DB::select("SELECT set_config('application_name',?,false)",[$w['application']]);DB::statement("SET lock_timeout='8s'");$u=App\Models\User::findOrFail($f['actors']['validate']);auth()->setUser($u);app(App\Shared\Context\CompanyContext::class)->selectFor($u->id,$f['scope']['tenant_id'],$f['scope']['company_id'],$f['scope']['unit_id']);
|
|
try{$r=app(App\Modules\Purchasing\SupplierPayables::class)->engine($u)->command('validate_history',$w['proposal'],'Synthetic exact history conference.',$w['key']);$out=['status'=>200,'result'=>$r];}catch(DomainException $e){$out=['status'=>409];}
|
|
file_put_contents($input.'.result',json_encode($out,JSON_THROW_ON_ERROR));exit;
|
|
}
|
|
if($phase==='page-read-worker'){
|
|
$input=realpath($argv[3]??'');if(!$input||!str_starts_with($input,realpath($s['root']).DIRECTORY_SEPARATOR))throw new LogicException('Read worker input refused');$w=json_decode(file_get_contents($input),true,64,JSON_THROW_ON_ERROR);$f=json_decode(file_get_contents($s['root'].'/fixture.json'),true,64,JSON_THROW_ON_ERROR);config(['supplier_payable_history.synthetic_enabled'=>true]);$u=App\Models\User::findOrFail($f['actors']['read']);auth()->setUser($u);app(App\Shared\Context\CompanyContext::class)->selectFor($u->id,$f['scope']['tenant_id'],$f['scope']['company_id'],$f['scope']['unit_id']);$start=hrtime(true);$d=app(App\Modules\Purchasing\SupplierPayables::class)->listing($u);file_put_contents($input.'.result',json_encode(['documents'=>count($d['documents']),'balances'=>array_column($d['obligations'],'balance_minor'),'time_ms'=>(hrtime(true)-$start)/1e6],JSON_THROW_ON_ERROR));exit;
|
|
}
|
|
if($phase==='http'){
|
|
config(['supplier_payable_history.synthetic_enabled'=>true]);$f=json_decode(file_get_contents($s['root'].'/fixture.json'),true,64,JSON_THROW_ON_ERROR);
|
|
Illuminate\Support\Facades\Route::middleware('web')->get('/qa/enter/{role}',function(Illuminate\Http\Request $r,string $role)use($f){abort_unless(in_array($role,['prepare','validate','read'],true),404);$u=App\Models\User::findOrFail($f['actors'][$role]);auth()->login($u);$r->session()->regenerate();$token=bin2hex(random_bytes(24));$r->session()->put('auth_version',$u->auth_version);$r->session()->put('company_context',$f['scope']+['token'=>$token,'module_id'=>'purchasing']);return response()->json(['csrf'=>csrf_token(),'context'=>$token,'source_id'=>$f['source_id']])->header('Cache-Control','no-store');});
|
|
$app->handleRequest(Illuminate\Http\Request::capture());exit;
|
|
}
|
|
if($phase==='post'){
|
|
DB::beginTransaction();DB::statement('SET TRANSACTION READ ONLY');$f=json_decode(file_get_contents($s['root'].'/fixture.json'),true,64,JSON_THROW_ON_ERROR);$bundle=json_decode(file_get_contents($s['root'].'/source.json'),true,64,JSON_THROW_ON_ERROR);$source=$bundle['source'];unset($source['local_id']);$row=DB::table('pa_imported_sources')->where($f['scope'])->where('id',$f['source_id'])->first();$ob=DB::table('pa_obligations')->where($f['scope'])->first();$import=DB::table('pa_history_imports')->where($f['scope'])->first();
|
|
if(!$row||!$ob||!$import||Payables::canonical($source)!==Payables::canonical(json_decode($row->payload,true))||DB::table('pa_obligations')->count()!==1||DB::table('pa_history_imports')->count()!==1||DB::table('pa_imported_payments')->count()!==3||(int)$ob->principal_minor!==6000||(int)$import->confirmed_minor!==3000||(int)$import->pending_minor!==1000||(int)$import->rejected_minor!==500||DB::table('pa_source_histories')->count()!==1||config('supplier_payable_history.synthetic_enabled')!==false)throw new LogicException('SGF final preservation check failed');
|
|
file_put_contents($s['root'].'/sgf-post.json',json_encode(['read_only'=>true,'one_obligation'=>true,'one_history_import'=>true,'original_source_unchanged'=>true,'source_receipt_sha256'=>$row->receipt_hash,'imported_payments'=>3,'states'=>DB::table('pa_imported_payments')->orderBy('legacy_payment_id')->pluck('status')->all(),'recognition_events'=>DB::table('pa_events')->where('kind','payable.recognised')->count(),'history_events'=>DB::table('pa_events')->where('kind','payable.legacy_history_imported')->count(),'principal_minor'=>6000,'confirmed_minor'=>3000,'pending_minor'=>1000,'rejected_minor'=>500,'pending_decision_schema_installed'=>DB::getSchemaBuilder()->hasTable('pa_payment_decisions'),'default_feature_enabled'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));DB::rollBack();echo "SGF final source, single import and default-disabled configuration verified read-only.\n";exit;
|
|
}
|
|
if(str_starts_with($phase,'native-page-')){require __DIR__.'/native-payable-page-proof.php';exit;}
|
|
if($phase==='page-evidence'){require __DIR__.'/payable-evidence-cases.php';exit;}
|
|
if($phase==='page-evidence-settlements'){require __DIR__.'/payable-evidence-settlements.php';exit;}
|
|
if($phase==='page-evidence-post'){require __DIR__.'/payable-evidence-post.php';exit;}
|
|
if(str_starts_with($phase,'settlement-page-')||str_starts_with($phase,'settlement-read-')){require __DIR__.'/settlement-page-proof.php';exit;}
|
|
if(str_starts_with($phase,'payable-report-profile-')){require __DIR__.'/payable-report-profile.php';exit;}
|
|
if(str_starts_with($phase,'payable-report-')){require __DIR__.'/payable-report-proof.php';exit;}
|
|
if(str_starts_with($phase,'ledger-')){require __DIR__.'/ledger-proof.php';exit;}
|
|
if($phase==='page-seed'){require __DIR__.'/payable-page-fixture.php';exit;}
|
|
if($phase==='page-cases'){require __DIR__.'/payable-page-cases.php';exit;}
|
|
if($phase==='page-bench'){require __DIR__.'/payable-page-bench.php';exit;}
|
|
if(in_array($phase,['page-fresh-command','page-post','page-concurrent-read','page-filter-positive'],true)){require __DIR__.'/payable-page-final.php';exit;}
|
|
if($phase!=='regression')throw new LogicException('Unknown phase');
|
|
require __DIR__.'/payable-history-sgf-cases.php';
|