true,'sales_ledger.synthetic_enabled'=>true,'cost_ledger.synthetic_enabled'=>true,'industrial_ledger.synthetic_enabled'=>true]); $finance=function($role,$action,Closure $fn)use($app,$profiles,$f){Auth::forgetGuards();Auth::shouldUse('web');$u=App\Models\User::findOrFail($profiles[$role]['user_id']);Auth::guard('web')->setUser($u);$session=app('session')->driver();$session->start();$session->put('company_context',$f['scope']+['token'=>'finance-'.$role,'module_id'=>'finance']);$r=Illuminate\Http\Request::create('/api/finance/payables','POST');$r->setLaravelSession($session);$r->setUserResolver(fn()=>$u);$app->instance('request',$r);app(CompanyContext::class)->selectFor($u->id,$f['tenant'],$f['company'],$f['unit']);try{return app(ComposedFinance::class)->run($action,$fn);}finally{app(CompanyContext::class)->clear();}}; $source=(int)$native->table('pa_sources')->where('tenant_id',$nf['tenant'])->orderByDesc('id')->value('local_source_id');$before=$native->table('pa_obligations')->orderBy('id')->get()->toJson();$physical=$native->table('stock_movements')->orderBy('id')->get()->toJson();$native->beginTransaction(); try{ $position=$finance('finance_prepare','view',fn($e)=>$e->positionForSource($source));$ob=$position['id'];$revision=$position['source_revision']; $payInput=['obligation_id'=>$ob,'source_revision'=>$revision,'amount'=>'20.00','currency'=>'MZN','paid_on'=>'2026-11-10','fact_reference'=>'SIM-INTEGRADO-'.strtoupper(bin2hex(random_bytes(4))),'evidence_reference'=>'SIM-EVIDENCIA-'.strtoupper(bin2hex(random_bytes(4))),'reason'=>'Facto sintético de20MZN, sem execução monetária.'];$key=(string)Str::uuid(); $payment=$finance('finance_prepare','payment-prepare',fn($e,$p)=>$p->command('payment-prepare',$payInput,$key));$retry=$finance('finance_prepare','payment-prepare',fn($e,$p)=>$p->command('payment-prepare',$payInput,$key));$ok($payment['payment_id']===$retry['payment_id'],'financial preparation recovers the original20MZN fact without duplicating it'); $denied=false;try{$finance('finance_prepare','payment-decide',fn()=>null);}catch(Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e){$denied=$e->getStatusCode()===403;}$ok($denied,'preparation profile cannot confirm its own financial fact'); $finance('finance_review','payment-decide',fn($e,$p)=>$p->command('payment-decide',['payment_id'=>$payment['payment_id'],'decision'=>'confirmed','evidence_reference'=>'SIM-CONF-'.strtoupper(bin2hex(random_bytes(4))),'reason'=>'Revisão distinta do facto artificial.'],(string)Str::uuid())); $unchanged=$finance('finance_prepare','view',fn($e)=>$e->positionForSource($source));$ok($unchanged['balance_minor']===10000,'confirmed payment alone leaves the original100MZN balance unchanged'); $allocation=$finance('finance_prepare','allocation-prepare',fn($e,$p)=>$p->command('allocation-prepare',['payment_id'=>$payment['payment_id'],'reference'=>'SIM-AFECTACAO-'.strtoupper(bin2hex(random_bytes(4))),'allocations'=>[['obligation_id'=>$ob,'source_revision'=>$revision,'amount'=>'20.00']],'reason'=>'Proposta individual sobre a obrigação original, sem outra dívida.'],(string)Str::uuid())); $decision=$finance('finance_review','allocation-decide',fn($e,$p)=>$p->command('allocation-decide',['proposal_id'=>$allocation['proposal_id'],'decision'=>'confirmed','reason'=>'Afectação de20MZN conferida por outra pessoa.'],(string)Str::uuid())); $position=$finance('finance_prepare','view',fn($e)=>$e->positionForSource($source));$ok($position['balance_minor']===8000,'one confirmed allocation reduces the same original balance to80MZN'); $allocationId=(int)$native->table('ps_allocations')->where('decision_id',$decision['decision_id'])->value('id'); $postEffect=function($kind,$id)use($call){$action=$kind==='supplier_payable.recognised'?'prepare':'effect-prepare';$d=$kind==='supplier_payable.recognised'?['obligation_id'=>$id]:['effect_type'=>$kind,'source_id'=>$id];$p=$call('accounting_prepare','prepare',fn($e)=>$e->command($action,$d+['journal_on'=>'2026-11-10','period_key'=>'QA-2026-INTEGRADO','rule_key'=>$kind,'reason'=>'Origem financeira revalidada no diário comum.'],(string)Str::uuid()));$call('accounting_review','validate',fn($e)=>$e->command('validate',['proposal_id'=>$p['proposal_id'],'revision'=>$p['revision'],'decision'=>'approved','reason'=>'Revisão contabilística distinta.'],(string)Str::uuid()));return $call('accounting_prepare','post',fn($e)=>$e->command('post',['proposal_id'=>$p['proposal_id'],'revision'=>$p['revision'],'reason'=>'Aplicação única da fotografia revista.'],(string)Str::uuid()));}; $postEffect('supplier_payable.recognised',$ob);$postEffect('allocation.confirmed',$allocationId);$ok($native->table('gl_journals')->count()===2,'recognition and financial allocation share one ledger with qualified financial scope'); $proposal=$finance('finance_prepare','reversal-prepare',fn($e,$p)=>$p->command('reversal-prepare',['allocation_id'=>$allocationId,'source_revision'=>$revision,'reason'=>'Reversão explícita da afectação artificial, preservando o original.'],(string)Str::uuid())); $reverse=$finance('finance_review','reversal-confirm',fn($e,$p)=>$p->command('reversal-confirm',['proposal_id'=>$proposal['proposal_id'],'reason'=>'Conferência independente da reversão integral.'],(string)Str::uuid()));$reversalId=(int)$native->table('ps_reversals')->where('proposal_id',$proposal['proposal_id'])->value('id'); $postEffect('allocation.reversed',$reversalId);$position=$finance('finance_prepare','view',fn($e)=>$e->positionForSource($source));$ok($position['balance_minor']===10000,'reversal restores100MZN without deleting original allocation or journals'); $native->statement('SET CONSTRAINTS ALL IMMEDIATE');$ok($native->table('pa_obligations')->orderBy('id')->get()->toJson()===$before&&$native->table('stock_movements')->orderBy('id')->get()->toJson()===$physical,'three balanced journals retain original debt records and physical stock'); $denied=false;try{$finance('read_only','payment-decide',fn()=>null);}catch(Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e){$denied=$e->getStatusCode()===403;}$ok($denied,'read-only host profile cannot borrow native legacy review rights'); }finally{while($native->transactionLevel()>0)$native->rollBack();} $ok($native->table('ps_payments')->count()===0&&$native->table('gl_journals')->count()===0,'regression rolls back all payment and journal effects');file_put_contents($work.'/payments-ledger-result.json',json_encode(['passed'=>true,'checks'=>$checks,'native_scope_preserved'=>true,'new_database'=>false,'target8085_financial_commands'=>0],JSON_PRETTY_PRINT));echo json_encode(['passed'=>true,'checks'=>count($checks),'proof'=>$work.'/payments-ledger-result.json']).PHP_EOL;