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('Valorização QA

Stock → valorização → diário QA

Entrada2UN/100MZN, saída1UN/50, devolução0,5UN/25. Taxa50/UN e revisão distinta; nenhuma operação real.

Preparar · Rever · Consultar

')->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;