Files
SGF/scripts/invoice-progress-read-body.php
T
2026-11-04 09:37:20 +02:00

8 lines
5.4 KiB
PHP

<?php
$fingerprint=function($connection){$result=[];foreach($connection->select("SELECT tablename FROM pg_tables WHERE schemaname='public' ORDER BY tablename")as$table){if(!preg_match('/^[a-z0-9_]+$/D',$table->tablename))throw new LogicException('Unexpected fixture table');$name=$table->tablename;$row=$connection->selectOne('SELECT count(*) n,md5(coalesce(string_agg(row_to_json(t)::text,\'\' ORDER BY row_to_json(t)::text),\'\')) h FROM "'.$name.'" t');$result[$name]=[(int)$row->n,$row->h];}$result['sequences']=$connection->select("SELECT sequencename,last_value FROM pg_sequences WHERE schemaname='public' ORDER BY sequencename");return $result;};
$before=['host'=>$fingerprint(DB::connection()),'native'=>$fingerprint($db)];$id=(int)$db->table('purchase_orders')->where('tenant_id',$n['tenant'])->orderBy('id')->value('id');if(!$id)throw new LogicException('Completed source order required');$url='/api/purchasing-invoices/orders/'.$id;$r=$call('issuer',$url);$view=$r['data'];$ok($r['status']===200&&count($view['matching_progress'])===1,'authorised order exposes one matching projection per original line');$row=$view['matching_progress'][0];$ok($row['ordered_milli']===1000&&$row['accepted_milli']===1000&&$row['returned_milli']===0&&$row['reversed_milli']===0,'native source order and accepted physical quantity are preserved');
if($s['progress_variant']==='available'){$ok($row['validated_milli']===0&&$row['pending_milli']===0&&$row['remaining_milli']===1000,'accepted unbilled1UN is available without inventing invoice or reservation');}else{$ok($row['validated_milli']===1000&&$row['pending_milli']===700&&$row['remaining_milli']===0,'validated40+60 exhaust1UN while incompatible pending70 remains separately visible');$pending=array_values(array_filter($view['invoices'],fn($v)=>$v['status']==='pending'));$ok(count($pending)===1&&$pending[0]['items'][0]['quantity_milli']===700,'original pending document is neither hidden rewritten nor deducted as a reservation');$r=$call('reviewer',$url);$pending=array_values(array_filter($r['data']['invoices'],fn($v)=>$v['status']==='pending'));$ok($r['status']===200&&$pending[0]['can_validate']===true,'read discrepancy does not replace original independent-review permission decision');}
$ok($call('actor',$url)['status']===403&&$call('validator',$url)['status']===403,'procurement and warehouse cannot read economic matching projection');$foreign=$db->table('purchase_orders')->where('tenant_id','<>',$n['tenant'])->value('id')??999999;$ok($call('issuer','/api/purchasing-invoices/orders/'.$foreign)['status']===403,'foreign or unknown order remains outside authorised origin');$enter('issuer',1,['foreign'=>1]);$ok($call('issuer',$url,null,1)['status']===403,'unmapped unit cannot reuse matching projection');$ok($call('issuer',$url,null,0,['X-SGF-Context'=>'old'])['status']===409,'obsolete context header cannot read matching projection');
$user=App\Models\User::findOrFail($f['issuer']);auth()->setUser($user);request()->setUserResolver(fn()=>$user);app(App\Shared\Context\CompanyContext::class)->selectFor($user->id,$f['tenant'],$f['company'],$f['unit']);$expected=(new App\Modules\Purchasing\ReceiptRuntime)->run('purchasing_invoices.view',function()use($id){$controller=new App\Modules\Purchasing\InvoiceController;$method=new ReflectionMethod($controller,'state');$state=$method->invoke($controller,Tempo\Purchasing\Embedded\Models\PurchaseOrder::findOrFail($id));return hash('sha256',Tempo\Finance\Payables\Payables::canonical([$state]));});$ok($expected===$view['revision'],'single observed state yields same original source revision before UI capability decoration');config(['receipt_qa.invoice_progress_enabled'=>false]);$legacy=(new App\Modules\Purchasing\InvoiceController)->show($id,new App\Modules\Purchasing\ReceiptRuntime)->getData(true);$ok($legacy['matching_progress']===null&&$legacy['revision']===$expected,'disabled projection preserves original revision and invoice display');config(['receipt_qa.invoice_progress_enabled'=>true]);$projection=new App\Modules\Purchasing\InvoiceMatchingProgress;$p=$projection->quantities(1000,1000,0,0,400,700);$ok($p['remaining_milli']===600&&$p['pending_milli']===700,'pending70 does not reserve or consume remaining60 after validated40');$p=$projection->quantities(1000,1200,0,0,400,0);$ok($p['remaining_milli']===600,'remaining quantity cannot exceed frozen order even with extra accepted input');$p=$projection->quantities(1000,1000,200,100,400,0);$ok($p['accepted_net_milli']===700&&$p['remaining_milli']===300,'existing return and reversal facts reduce accepted matching capacity');$p=$projection->quantities(1000,1000,200,100,800,0);$ok($p['remaining_milli']===0&&$p['deficit_milli']===100,'read deficit is explicit and remaining quantity is not negative');
$after=['host'=>$fingerprint(DB::connection()),'native'=>$fingerprint($db)];$ok(json_encode($before)===json_encode($after),'all reused source tables and sequences unchanged by read projection');file_put_contents($out,json_encode(['passed'=>true,'checks'=>$checks,'variant'=>$s['progress_variant'],'order_id'=>$id,'quantities'=>$row,'financial_commands_repeated'=>0,'before_sha256'=>hash('sha256',json_encode($before)),'after_sha256'=>hash('sha256',json_encode($after)),'tables_checked'=>count($before['host'])+count($before['native'])-2,'new_right_names'=>[],'visual_review'=>false,'deployment_capacity_validated'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo count($checks)." invoice progress read checks passed.\n";