50 lines
4.0 KiB
PHP
50 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Enums\PurchasingRole;
|
|
use App\Models\User;
|
|
use App\Services\OrganizationAccess;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
final class CommonStockReadController extends Controller
|
|
{
|
|
public function index(Request $request)
|
|
{
|
|
// Native authority, reloaded on every request; no SGF grants or identity assumptions.
|
|
$user=User::query()->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');
|
|
}
|
|
}
|