50 lines
6.9 KiB
PHP
50 lines
6.9 KiB
PHP
<?php
|
|
namespace App\Modules\Accounting;
|
|
|
|
use App\Modules\Identity\Services\Permissions;
|
|
use App\Shared\Context\CompanyContext;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/** SGF observes the canonical physical/valuation book with a SELECT-only QA role. */
|
|
final class StockValuationRead
|
|
{
|
|
public function index(Request $r,CompanyContext $context,Permissions $permissions)
|
|
{
|
|
$d=$r->validate(['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);
|
|
$s=$context->identifiers();$unit=$context->unitId();abort_unless($unit,403);
|
|
foreach(['stock.view','general_ledger.view']as$p)$permissions->require($context->userId(),$s['tenant_id'],$s['company_id'],$p,$unit);
|
|
$binding=config('stock_ledger.read_sources.'.$s['tenant_id'].':'.$s['company_id'].':'.$unit);
|
|
if(!$binding)return response()->json(['status'=>'unavailable','message'=>'Livro de valorização autorizado ainda não ligado a este contexto.'],503)->header('Cache-Control','no-store');
|
|
abort_unless(app()->environment('budget-testing'),403);
|
|
$host=DB::connection();$h=$host->getConfig();$run=(string)getenv('SGF_ISOLATED_REGRESSION_RUN');
|
|
abort_unless(preg_match('/^[a-f0-9]{12}$/D',$run)&&($h['driver']??null)==='pgsql'&&($h['host']??null)==='127.0.0.1'&&(string)($h['port']??'')==='55439'&&preg_match('/^budget_core_sgf_([a-f0-9]{12})$/D',$h['database']??'',$hm)&&($h['username']??null)==='stock_ledger_sgf_'.$hm[1]&&empty($h['url'])&&empty($h['read'])&&empty($h['write']),403);
|
|
$hi=$host->selectOne("SELECT current_database() db,current_user role,current_setting('cluster_name') cluster");abort_unless($hi->db===$h['database']&&$hi->role===$h['username']&&$hi->cluster==='sgf_modular_regression_'.$run,403);
|
|
abort_unless(($binding['connection']??null)==='stock_valuation_source'&&is_int($binding['book_id']??null)&&($binding['book_id']??0)>0,503);
|
|
$c=DB::connection('stock_valuation_source');$cfg=$c->getConfig();
|
|
abort_unless(($cfg['driver']??null)==='pgsql'&&($cfg['host']??null)==='127.0.0.1'&&(string)($cfg['port']??'')==='55439'&&preg_match('/^compras_receipts_test_([a-f0-9]{12})$/D',$cfg['database']??'',$m)&&($cfg['username']??null)==='stock_valuation_read_'.$m[1]&&empty($cfg['url'])&&empty($cfg['read'])&&empty($cfg['write'])&&($cfg['search_path']??'public')==='public',403);
|
|
$native=$binding['native_scope']??[];abort_unless(array_keys($native)===['tenant_id','company_id','unit_id']&&is_int($native['tenant_id'])&&$native['tenant_id']>0&&$native['company_id']===0&&$native['unit_id']===0,503);
|
|
try{$data=$c->transaction(function()use($c,$cfg,$run,$native,$binding,$d){
|
|
abort_unless($c->transactionLevel()===1,503);$c->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
|
$i=$c->selectOne("SELECT current_database() db,current_user role,current_setting('cluster_name') cluster,current_setting('default_transaction_read_only') ro,rolsuper OR rolcreatedb OR rolcreaterole OR rolreplication OR rolbypassrls elevated FROM pg_roles WHERE rolname=current_user");
|
|
abort_unless($i->db===$cfg['database']&&$i->role===$cfg['username']&&$i->cluster==='sgf_modular_regression_'.$run&&$i->ro==='on'&&!$i->elevated,403);
|
|
foreach(['stock_movements','gl_journals','gl_lines','gl_stock_rates','gl_stock_books']as$table)abort_if($c->selectOne("SELECT has_table_privilege(current_user,?,'INSERT,UPDATE,DELETE') writes",[$table])->writes,403);
|
|
$book=$c->table('gl_stock_books')->where($native)->where('id',$binding['book_id'])->first();abort_unless($book,503,'Livro composto incompatível.');
|
|
abort_unless($c->table('stock_items')->where(['tenant_id'=>$native['tenant_id'],'id'=>$book->stock_item_id,'unit'=>$book->unit_code])->exists()
|
|
&&$c->table('warehouses')->where(['tenant_id'=>$native['tenant_id'],'id'=>$book->warehouse_id])->exists(),503,'Artigo/armazém/unidade da origem incompatíveis.');
|
|
$page=(int)($d['page']??1);$size=(int)($d['per_page']??25);
|
|
$physical=$c->table('stock_movements')->where(['tenant_id'=>$native['tenant_id'],'stock_item_id'=>$book->stock_item_id,'warehouse_id'=>$book->warehouse_id]);
|
|
$journals=$c->table('gl_journals')->where($native)->where('snapshot->source->stock_book_id',$book->id);$count=(clone$journals)->count();
|
|
$rows=(clone$journals)->orderBy('id')->offset(($page-1)*$size)->limit($size)->get();$ids=$rows->pluck('id')->all();$lines=$c->table('gl_lines')->where($native)->whereIn('journal_id',$ids)->orderBy('journal_id')->orderBy('line_no')->get();
|
|
$postedIds=(clone$journals)->selectRaw("(snapshot->'source'->>'stock_movement_id')::bigint");
|
|
$unposted=(clone$physical)->whereNotIn('id',$postedIds)->count();
|
|
$rate=$c->table('gl_stock_rates')->where($native)->where('book_id',$book->id)->orderByDesc('version')->first();$review=$rate?$c->table('gl_stock_rate_reviews')->where($native)->where('rate_id',$rate->id)->first():null;
|
|
$stale=$rate?(clone$journals)->where('snapshot->source->stock_rate_id','<>',$rate->id)->count():$count;
|
|
$net=$c->table('gl_lines as l')->join('gl_accounts as a','a.id','=','l.account_id')->where(['l.tenant_id'=>$native['tenant_id'],'l.company_id'=>0,'l.unit_id'=>0,'a.code'=>'QA-STOCK'])->whereIn('l.journal_id',(clone$journals)->select('id'))->selectRaw('coalesce(sum(l.debit_minor-l.credit_minor),0) net')->first();
|
|
return ['synthetic_only'=>true,'single_canonical_book'=>true,'status'=>$rate&&$review&&$review->decision==='approved'&&$unposted===0&&$stale===0?'reconciled':'pending','reconciliation_scope'=>'physical_movement_coverage_and_rate_ids_only','current_source_dependencies_validated'=>false,'book'=>['id'=>$book->id,'stock_item_id'=>$book->stock_item_id,'warehouse_id'=>$book->warehouse_id,'unit'=>$book->unit_code,'period_key'=>$book->period_key],'physical_quantity_milli'=>(int)(clone$physical)->sum('quantity_milli'),'physical_revision'=>(int)(clone$physical)->max('id'),'posted_stock_value_minor'=>(int)$net->net,'currency'=>'MZN','unposted_movements'=>$unposted,'stale_rate_journals'=>$stale,'journals'=>$rows->all(),'lines'=>$lines->all(),'pagination'=>['page'=>$page,'per_page'=>$size,'total'=>$count],'source_identity'=>['database'=>$i->db,'role'=>$i->role],'operation_executed'=>false];
|
|
});}catch(\PDOException $e){$state=$e->errorInfo[0]??$e->getCode();if(str_starts_with((string)$state,'08')||in_array($state,['28P01','3D000','42P01','42703','57014'],true))return response()->json(['status'=>'unavailable','message'=>'Livro de valorização indisponível ou incompatível.'],503)->header('Cache-Control','no-store');throw$e;}
|
|
if($r->is('api/*'))return response()->json($data)->header('Cache-Control','no-store');
|
|
return response()->view('accounting.stock-valuation-read',['data'=>$data])->header('Cache-Control','no-store');
|
|
}
|
|
}
|