Files
SGF/scripts/stock-ledger-http.php
2026-11-04 09:37:20 +02:00

23 lines
5.8 KiB
PHP

<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\{DB,Route};
use Tempo\Finance\GeneralLedger\{StockWorkspace,ReportsWorkspace};
// Explicit QA HTTP composition, never registered in an installed host.
$app->instance('env','payable-testing');
$actor=function(Request $r)use($s){abort_unless($r->user() instanceof App\Models\User&&$r->session()->get('stock_ledger_qa')===$s['tag'],403);return $r->user();};
$context=function(Request $r){abort_unless(hash_equals((string)$r->session()->get('stock_ledger_context'),(string)$r->header('X-Stock-Context')),409,'O contexto da conferência mudou.');};
$respond=function(callable $work){try{return response()->json($work())->header('Cache-Control','no-store');}catch(InvalidArgumentException $e){abort(422,$e->getMessage());}catch(DomainException $e){abort(409,$e->getMessage());}catch(Illuminate\Database\QueryException $e){if(($e->errorInfo[0]??null)==='55P03')return response()->json(['code'=>'stock_valuation_busy','message'=>'Conferência ocupada; volte a conferir antes de repetir.'],423)->header('Cache-Control','no-store');throw$e;}};
$read=function(callable $work){return DB::transaction(function()use($work){if(DB::transactionLevel()===1)DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');return $work();});};
Route::middleware('web')->get('/qa/stock',fn()=>response('<!doctype html><html lang="pt"><meta charset="utf-8"><title>Valorização QA</title><main><h1>Stock → valorização → diário QA</h1><p>Entrada2UN/100MZN, saída1UN/50, devolução0,5UN/25. Taxa50/UN e revisão distinta; nenhuma operação real.</p><p><a href="/qa/stock-enter/prepare?open=1">Preparar</a> · <a href="/qa/stock-enter/validate?open=1">Rever</a> · <a href="/qa/stock-enter/read?open=1">Consultar</a></p></main></html>')->header('Cache-Control','no-store'));
Route::middleware('web')->get('/qa/stock-enter/{role}',function(Request $r,string $role)use($f,$s){abort_unless(isset($f['actors'][$role]),404);auth()->login(App\Models\User::findOrFail($f['actors'][$role]));$r->session()->regenerate();$token=bin2hex(random_bytes(24));$r->session()->put('stock_ledger_qa',$s['tag']);$r->session()->put('stock_ledger_context',$token);if($r->query('open')==='1')return redirect('/stock-valuation');return response()->json(['csrf'=>csrf_token(),'context'=>$token])->header('Cache-Control','no-store');});
Route::middleware(['web','auth'])->group(function()use($actor,$context,$respond,$read,$f){
$view=function(Request $r)use($actor,$f){$u=$actor($r);$c=Tempo\Integration\Purchasing\StockValuationAdapter::for($u);$d=$r->validate(['book_id'=>'sometimes|integer|min:1','page'=>'sometimes|integer|min:1|max:100000','per_page'=>'sometimes|integer|in:25,100']);abort_if(array_diff(array_keys($r->query()),array_keys($d))!==[],422);$page=(int)($d['page']??1);$stock=$c->stock->page((int)($d['book_id']??$f['book_id']),$page,(int)($d['per_page']??25));$scope=$f['scope'];$can=DB::table('pa_access')->where($scope)->where('user_id',$u->id)->where('action','like','general_ledger.%')->pluck('action')->map(fn($v)=>substr($v,15))->all();return [['stock'=>$stock,'ledger'=>$c->engine()->listing(['page'=>$page]),'cycle'=>$c->engine()->cycleListing($page)],['host'=>'Livro físico nativo QA','csrf'=>csrf_token(),'context'=>$r->session()->get('stock_ledger_context'),'actor_id'=>$u->id,'can'=>$can,'return_url'=>'/qa/stock','stock_account_id'=>$f['accounts']['QA-STOCK']]];};
Route::get('/stock-valuation',function(Request $r)use($read,$view){[$data,$settings]=$read(fn()=>$view($r));return response(StockWorkspace::render($data,$settings))->header('Cache-Control','no-store');});
Route::get('/api/stock-valuation',function(Request $r)use($context,$respond,$read,$view){$context($r);return $respond(fn()=>$read(fn()=>$view($r)[0]));});
Route::get('/api/stock-valuation/receipts/{domain}/{key}',function(Request $r,string $domain,string $key)use($actor,$context,$respond,$read){$context($r);abort_if($r->query()!==[]||!in_array($domain,['ledger','rate'],true),422);return $respond(fn()=>$read(function()use($r,$actor,$domain,$key){$c=Tempo\Integration\Purchasing\StockValuationAdapter::for($actor($r));$value=$domain==='rate'?$c->stock->receipt($key):$c->engine()->receipt($key);abort_unless($value!==null,404);return$value;}));});
Route::post('/api/stock-valuation/{action}',function(Request $r,string $action)use($actor,$context,$respond){$context($r);abort_unless(in_array($action,['rate-prepare','rate-validate','effect-prepare','validate','post','period-prepare','period-validate','period-close'],true),422);return $respond(function()use($actor,$r,$action){$c=Tempo\Integration\Purchasing\StockValuationAdapter::for($actor($r));$key=(string)$r->header('Idempotency-Key');return str_starts_with($action,'rate-')?$c->stock->command($action,$r->all(),$key):$c->engine()->command($action,$r->all(),$key);});});
$report=function(Request $r,string $kind)use($actor,$read){abort_unless(in_array($kind,['trial-balance','account-ledger'],true),404);$d=$r->validate(['from_on'=>'required|date_format:Y-m-d','to_on'=>'required|date_format:Y-m-d','account_id'=>'sometimes|integer|min:1','page'=>'sometimes|integer|min:1|max:100000','per_page'=>'sometimes|integer|in:25,100']);abort_if(array_diff(array_keys($r->query()),array_keys($d))!==[],422);$data=$read(function()use($actor,$r,$kind,$d){$engine=Tempo\Integration\Purchasing\StockValuationAdapter::for($actor($r))->engine();return $kind==='trial-balance'?$engine->trialBalance($d):$engine->accountLedger($d);});return response(ReportsWorkspace::render($data,['host'=>'Stock valorizado QA','return_url'=>'/stock-valuation']))->header('Cache-Control','no-store');};
Route::get('/stock-valuation/reports/{kind}',$report);Route::get('/general-ledger/{kind}',$report)->whereIn('kind',['trial-balance','account-ledger']);
});
$app->handleRequest(Request::capture());exit;