63 lines
4.8 KiB
PHP
63 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Inventory;
|
|
|
|
use App\Modules\Identity\Services\Permissions;
|
|
use App\Shared\Context\CompanyContext;
|
|
use App\Shared\Testing\TestingDatabaseGuard;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
require_once __DIR__.'/../../../packages/inventory-read/bootstrap.php';
|
|
|
|
final class StockReadController
|
|
{
|
|
public function index(Request $r, CompanyContext $context, Permissions $permissions)
|
|
{
|
|
if (config('receipt_qa.stock_read_enabled') === true) {
|
|
return app(CompositionStockRead::class)->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');
|
|
}
|
|
}
|