findOrFail($request->user()->id); abort_unless($user->tenant_id!==null && $user->hasFunctions([PurchasingRole::Admin,PurchasingRole::Warehouse,PurchasingRole::Procurement,PurchasingRole::Finance]) && !app(OrganizationAccess::class)->maintenanceOnly($user),403); $d=$request->validate(['view'=>'sometimes|in:items,warehouses,balances,movements','search'=>'nullable|string|max:100', 'warehouse_id'=>'sometimes|integer|min:1','page'=>'sometimes|integer|min:1|max:100000','per_page'=>'sometimes|integer|min:1|max:100','snapshot'=>'sometimes|string|max:8192']); abort_if(array_diff(array_keys($request->query()),array_keys($d))!==[],422); $filters=['view'=>$d['view']??'balances','search'=>$d['search']??'','warehouse_id'=>isset($d['warehouse_id'])?(int)$d['warehouse_id']:null, 'page'=>(int)($d['page']??1),'per_page'=>(int)($d['per_page']??25)]; $binding=config('stock_read.synthetic_sources.'.$user->tenant_id.':'.$user->id); if(!$binding)return response()->json(['status'=>'unavailable','message'=>'Fonte autorizada de stock ainda não ligada a este contexto.'],503)->header('Cache-Control','no-store'); abort_unless(app()->environment('testing')&&DB::connection()->getDriverName()==='pgsql',403); $identity=DB::selectOne("SELECT current_database() AS name,current_user AS role,inet_server_port() AS port,current_setting('cluster_name') AS cluster"); abort_unless(preg_match('/^stock_read_compras_([a-f0-9]{12})$/D',$identity->name,$m) && $identity->role==='stock_read_qa_'.$m[1] && (int)$identity->port===55439 && preg_match('/^[a-f0-9]{12}$/D',(string)config('stock_read.qa_run')) && $identity->cluster==='sgf_modular_regression_'.config('stock_read.qa_run'),403); abort_unless(preg_match('/^sgf_stock_read_[a-f0-9]{12}$/D',$binding['schema']??'')&&($binding['append_only']??false)===true && ($binding['source_tenant_id']??null)===(int)$user->tenant_id,503,'Fonte de stock incompatível.'); if($filters['warehouse_id']!==null)abort_unless(in_array($filters['warehouse_id'],$binding['warehouse_ids'],true),403); $root=base_path('packages/inventory-read/releases/0.1.0'); abort_unless(is_file($root.'/manifest.json')&&hash_equals(config('stock_read.package_manifest_sha256'),hash_file('sha256',$root.'/manifest.json')),503,'Pacote de consulta incompatível.'); $manifest=json_decode(file_get_contents($root.'/manifest.json'),true,512,JSON_THROW_ON_ERROR); foreach(['bootstrap.php','src/InventoryReader.php','src/InventoryReadSession.php'] as $file) abort_unless(is_file($root.'/'.$file)&&isset($manifest['files'][$file])&&hash_equals($manifest['files'][$file],hash_file('sha256',$root.'/'.$file)),503,'Pacote de consulta incompatível.'); require_once $root.'/bootstrap.php'; $fingerprint=hash('sha256',json_encode(['compras',$user->tenant_id,$user->id,$request->session()->get('work_context'),$binding,array_diff_key($filters,['page'=>true])],JSON_THROW_ON_ERROR)); try { $result=(new \Tempo\Inventory\InventoryReadSession(DB::connection()))->read($binding,$fingerprint,$filters,$d['snapshot']??null); } catch(\Illuminate\Database\QueryException $e){ if(!in_array($e->errorInfo[0]??null,['42P01','42703','42883','57014'],true))throw $e; return response()->json(['status'=>'unavailable','message'=>'A fonte de stock está indisponível ou incompatível. Actualize a consulta mais tarde.'],503)->header('Cache-Control','no-store'); } return response()->json($result)->header('Cache-Control','no-store'); } }