Files
SGF/scripts/sgf-payable-positions-proof.php
T
2026-11-11 09:00:17 +02:00

7 lines
4.8 KiB
PHP

<?php
use Illuminate\Support\Facades\Auth;use App\Shared\Context\CompanyContext;use App\Modules\Purchasing\{PayablePositionReadController,ComposedFinance,ComposedFinanceController};
require __DIR__.'/sgf-ledger-proof-bootstrap.php';config(['sgf_single_installation.finance_enabled'=>true]);set_exception_handler(function($e){echo json_encode(['passed'=>false,'type'=>get_class($e),'message'=>$e->getMessage()]).PHP_EOL;exit(1);});
$callPosition=function(array $query=[],string $role='read_only',string $module='finance',bool $detail=false)use($app,$profiles,$f){Auth::forgetGuards();Auth::shouldUse('web');$u=App\Models\User::findOrFail($profiles[$role]['user_id']);Auth::guard('web')->setUser($u);$r=Illuminate\Http\Request::create('/api/finance/payable-positions','GET',$query);$r->attributes->set('sgf_module',$module);$r->setUserResolver(fn()=>$u);$session=app('session')->driver();$session->start();$session->put('company_context',$f['scope']+['module_id'=>$module,'token'=>'payable-position-proof-'.$u->id]);$r->setLaravelSession($session);$app->instance('request',$r);app(CompanyContext::class)->selectFor($u->id,1,2,2);$controller=$detail?app(ComposedFinanceController::class):app(PayablePositionReadController::class);return$controller->index($r,app(ComposedFinance::class))->getData(true);};
$reject=function($fn,$expected,$label)use($ok){$status=null;try{$fn();}catch(Symfony\Component\HttpKernel\Exception\HttpExceptionInterface$e){$status=$e->getStatusCode();}catch(Illuminate\Validation\ValidationException){$status=422;}$ok($status===$expected,$label);};$before=[];foreach(['invoices','pa_obligations','pa_sources','pa_commands','gl_journals','stock_movements','audit_logs']as$t)$before[$t]=$native->table($t)->orderBy('id')->get()->toJson();$all=$callPosition(['per_page'=>100]);$small=$callPosition(['per_page'=>25]);$ok($all['positions']===$small['positions']&&$all['pagination']['total']>0,'known rows same25 and100');$ok($all['balance_basis']==='original_current_payable_engine'&&$all['totals_basis']==='visible_page_only','source and page-total basis explicit');$legacy=$callPosition([],detail:true);foreach($all['positions']as$row)if($row['obligation_id']){$prior=collect($legacy['obligations'])->firstWhere('id',$row['obligation_id']);$ok($prior&&(string)$prior['balance_minor']===$row['balance_minor']&&(string)$prior['principal_minor']===$row['principal_minor']&&$prior['source_conflict']===$row['source_conflict'],'canonical parity obligation '.$row['obligation_id']);$focused=$callPosition(['document_id'=>$row['document_id']],detail:true);$ok(count($focused['obligations'])===1&&$focused['obligations'][0]['id']===$row['obligation_id'],'detail opens exact authorized document');$ok($row['days_overdue']===null&&$row['due_status']==='unknown','unknown due not converted to zero');}
$first=$all['positions'][0];$match=$callPosition(['search'=>$first['reference']]);$ok(collect($match['positions'])->contains('document_id',$first['document_id']),'document search literal');$supplier=$callPosition(['search'=>$first['supplier']]);$ok(collect($supplier['positions'])->contains('document_id',$first['document_id']),'supplier name search');$state=$callPosition(['document_state'=>'validated']);$ok(collect($state['positions'])->every(fn($r)=>$r['document_state']==='validated'),'server filters document state');$empty=$callPosition(['page'=>100000]);$ok($empty['positions']===[],'out-of-range page no fallback');$reject(fn()=>$callPosition(['per_page'=>1000]),422,'unbounded page refused');$reject(fn()=>$callPosition(['tenant_id'=>2]),422,'tenant override refused');$reject(fn()=>$callPosition(['payment_state'=>'settled']),422,'unsupported global payment filter refused');$reject(fn()=>$callPosition(module:'accounting'),409,'wrong active module refused');$reject(fn()=>$callPosition(role:'commercial_prepare'),403,'unapproved financial profile refused');$reject(fn()=>$callPosition(['document_id'=>2147483647],detail:true),403,'foreign document cannot select first position');$native->beginTransaction();try{$copy=(array)$native->table('pa_sources')->first();unset($copy['id']);$copy['source_key']=hash('sha256','ambiguous-read-fixture-'.bin2hex(random_bytes(8)));$native->table('pa_sources')->insert($copy);$reject(fn()=>$callPosition(),409,'ambiguous source identity refused without automatic choice');}finally{$native->rollBack();}foreach($before as$t=>$json)$ok($native->table($t)->orderBy('id')->get()->toJson()===$json,'original '.$t.' unchanged');file_put_contents($work.'/payable-positions-result.json',json_encode(['passed'=>true,'checks'=>$checks,'business_commands'=>0,'new_rights'=>0,'new_demo'=>false],JSON_PRETTY_PRINT));file_put_contents($work.'/payable-positions-sample-private.json',json_encode($all));echo json_encode(['passed'=>true,'checks'=>count($checks),'proof'=>$work.'/payable-positions-result.json','sample'=>$work.'/payable-positions-sample-private.json']).PHP_EOL;