index($r, $context, $permissions); } $d = $r->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($r->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)]; $scope = $context->identifiers(); $unit = $context->unitId(); abort_unless($unit,403); $permissions->require($context->userId(), $scope['tenant_id'], $scope['company_id'], 'stock.view', $unit); $key = $scope['tenant_id'].':'.$scope['company_id'].':'.$unit; $binding = config('stock_read.synthetic_sources.'.$key); 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'); // A config edit cannot bind this experimental reader to a client database. abort_unless(app()->environment('testing'),403); TestingDatabaseGuard::assertSafe(); try { $connection=DB::connection(); if(isset($binding['connection'])) { // QA integration may observe the actual native receipt book through // a SELECT-only role, instead of copying movements to another book. abort_unless($binding['connection']==='stock_receipt_source'&&($binding['schema']??null)==='public',503,'Fonte de stock incompatível.'); $connection=DB::connection('stock_receipt_source'); abort_unless($connection->getDriverName()==='pgsql'&&$connection->getConfig('host')==='127.0.0.1'&&(int)$connection->getConfig('port')===55439,403); $i=$connection->selectOne("SELECT current_database() AS name,current_user AS role,current_setting('cluster_name') AS cluster,current_setting('default_transaction_read_only') AS read_only"); $run=(string)getenv('SGF_ISOLATED_REGRESSION_RUN'); abort_unless(preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D',$i->name,$m)&&$i->role==='stock_receipt_read_'.$m[1] &&preg_match('/^[a-f0-9]{12}$/D',$run)&&$i->cluster==='sgf_modular_regression_'.$run&&$i->read_only==='on',403); $rights=$connection->selectOne("SELECT rolsuper OR rolcreatedb OR rolcreaterole OR rolreplication OR rolbypassrls AS elevated, has_table_privilege(current_user,'public.stock_movements','INSERT,UPDATE,DELETE') AS writes FROM pg_roles WHERE rolname=current_user"); abort_if($rights->elevated||$rights->writes,403); // Bind the token to the actual book too, not just the logical connection name. $binding['physical_source']=['database'=>$i->name,'role'=>$i->role,'cluster'=>$i->cluster]; } else abort_unless(preg_match('/^sgf_stock_read_[a-f0-9]{12}$/D',$binding['schema']??''),503,'Fonte de stock incompatível.'); abort_unless(($binding['append_only']??false)===true,503,'Fonte de stock incompatível.'); if ($filters['warehouse_id'] !== null) abort_unless(in_array($filters['warehouse_id'], $binding['warehouse_ids'],true),403); $fingerprint = hash('sha256', json_encode([$scope,$unit,$context->userId(),$binding,array_diff_key($filters,['page'=>true])],JSON_THROW_ON_ERROR)); $result = (new \Tempo\Inventory\InventoryReadSession($connection))->read($binding,$fingerprint,$filters,$d['snapshot'] ?? null); } catch (\PDOException $e) { $sqlstate=$e->errorInfo[0] ?? (string)$e->getCode(); if (!str_starts_with($sqlstate,'08')&&!in_array($sqlstate,['28P01','3D000','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'); } }