environment(); DB::setDefaultConnection($host->getName());Schema::clearResolvedInstance('db.schema');Auth::shouldUse('web');Auth::guard('web')->setUser($user);app()->instance('env','budget-testing'); try{return $work();}finally{DB::setDefaultConnection($default);Schema::clearResolvedInstance('db.schema');Auth::shouldUse($guard);if($nativeUser)Auth::guard($guard)->setUser($nativeUser);app()->instance('env',$environment);} } private function source(object $actor,array $binding,array $financialScope,int $id):array { $o=DB::table('pa_obligations')->where($financialScope)->where('id',$id)->first()??throw new \DomainException('Obrigação fora da origem financeira autorizada.'); $s=DB::table('pa_sources')->where($financialScope)->where('id',$o->source_id)->first()??throw new \DomainException('Origem financeira ausente.'); $orders=PurchaseOrder::visibleTo($actor)->where(fn($q)=>$q->whereIn('id',$binding['order_ids']??[])->orWhereIn('requisition_id',RequisitionAccess::ids($binding))); abort_unless(Invoice::where('tenant_id',$actor->tenant_id)->whereIn('purchase_order_id',$orders->select('id'))->where('id',$s->local_source_id)->exists(),403); $service=app(SupplierPayables::class);$service->readContext($actor);$document=$service->document($actor,(int)$s->local_source_id);$position=$service->engine($actor)->positionForSource((int)$s->local_source_id); if(!$position||(int)$position['id']!==$id||$position['source_conflict'])throw new \DomainException('A fonte financeira mudou; exige nova conferência.'); $proposal=DB::table('pa_proposals')->where($financialScope)->where('id',$o->proposal_id)->first()??throw new \DomainException('Reconhecimento sem conferência.'); return ['effect_type'=>'supplier_payable.recognised','origin_key'=>$s->source_key,'obligation_id'=>$id,'local_source_id'=>(int)$s->local_source_id,'document_revision'=>$o->source_revision,'recognition_revision'=>hash('sha256',Payables::canonical(['obligation'=>(array)$o,'recognition_snapshot'=>$proposal->snapshot])),'principal_minor'=>(int)$o->principal_minor,'currency'=>$o->currency,'due_on'=>$o->due_on,'tax_minor'=>$document['tax_minor'],'compatible'=>true,'history_imported'=>$position['history_imported']]; } public function run(string $action,\Closure $work):mixed { CompositionRuntime::assert();abort_unless(config('sgf_single_installation.ledger_enabled')===true,404); abort_unless(request()->session()->get('company_context.module_id')==='accounting',409,'Seleccione Contabilidade.'); $context=app(CompanyContext::class);$scope=$context->identifiers()+['unit_id'=>$context->unitId()];$hostUser=$context->userId();$host=DB::connection();$hostIdentity=CompositionRuntime::physical($host); $generation=request()->session()->get('company_context.token');$hostActor=\App\Models\User::findOrFail($hostUser); try{return $host->transaction(function()use($action,$work,$context,$scope,$hostUser,$hostActor,$host,$hostIdentity,$generation){ $host->statement("SET LOCAL lock_timeout='5s'");$host->table('companies')->where('tenant_id',$scope['tenant_id'])->where('id',$scope['company_id'])->lockForUpdate()->firstOrFail(); return app(ReceiptRuntime::class)->run('general_ledger.'.$action,function($actor,$binding,$revision,$origin)use($action,$work,$context,$scope,$hostUser,$hostActor,$host,$hostIdentity,$generation){ $native=DB::connection();LedgerSchema::assertSynthetic($native);$r=$native->table('gl_sgf_composition')->where('tenant_id',$actor->tenant_id)->first(); abort_unless($r&&$r->host_database===$hostIdentity['database']&&(int)$r->company_id===$scope['company_id']&&(int)$r->unit_id===$scope['unit_id'],403,'Livro financeiro ligado a outro contexto SGF.'); $ledgerScope=['tenant_id'=>(int)$actor->tenant_id,'company_id'=>$scope['company_id'],'unit_id'=>$scope['unit_id']];$financialScope=['tenant_id'=>(int)$actor->tenant_id,'company_id'=>0,'unit_id'=>0]; // The legacy payable book has no SGF unit column. Never expose its // listings or settlement facts until every obligation is qualified. $orders=PurchaseOrder::visibleTo($actor)->where(fn($q)=>$q->whereIn('id',$binding['order_ids']??[])->orWhereIn('requisition_id',RequisitionAccess::ids($binding))); $unqualified=$native->table('pa_obligations as o')->where(['o.tenant_id'=>$actor->tenant_id,'o.company_id'=>0,'o.unit_id'=>0])->whereNotExists(function($q)use($orders,$actor){ $q->selectRaw('1')->from('pa_sources as s')->join('invoices as i','i.id','=','s.local_source_id')->whereColumn('s.id','o.source_id')->whereColumn('s.tenant_id','o.tenant_id')->whereColumn('s.company_id','o.company_id')->whereColumn('s.unit_id','o.unit_id')->where('i.tenant_id',$actor->tenant_id)->whereIn('i.purchase_order_id',$orders->select('id')); })->exists(); abort_if($unqualified,503,'Há origens financeiras sem unidade conferida; a partição exige decisão explícita.'); $identity=app(SupplierPayables::class)->readContext($actor);$person=$identity['person']; $authorize=function(string $right)use($host,$hostUser,$scope,$context,$actor,$person,$ledgerScope,$generation){ abort_unless($context->userId()===$hostUser&&$context->identifiers()+['unit_id'=>$context->unitId()]===$scope&&request()->session()->get('company_context.token')===$generation,409,'Contexto SGF alterado.'); foreach(['general_ledger.view','general_ledger.'.$right] as $permission)abort_unless($host->table('permission_grants')->where($scope+['user_id'=>$hostUser,'permission'=>$permission])->exists(),403); $fresh=app(CurrentActor::class)->lock($actor,['finance']);abort_if(app(OrganizationAccess::class)->maintenanceOnly($fresh),403); abort_unless(DB::table('pa_access')->where($ledgerScope)->where(['user_id'=>$actor->id,'action'=>'general_ledger.'.$right,'person'=>$person])->exists(),403); }; $authorize('view'); $isolation=fn()=>app(ComposedMaterialIsolation::class)->check($host,$native,$ledgerScope); $stock=new StockValuation($native,$ledgerScope,$actor->id,$person,$authorize,fn()=>$authorize('view'),$isolation); $additional=function($kind,$id)use($host,$hostActor,$stock,$isolation){ if(str_starts_with($kind,'stock.'))return $stock->source($kind,$id); if(str_starts_with($kind,'workshop.')||str_starts_with($kind,'industry.'))$isolation(); return $this->withHost($host,$hostActor,function()use($hostActor,$kind,$id){ if(str_starts_with($kind,'cash.'))return app(CashLedgerSource::class)->source($hostActor,$kind,$id);if(str_starts_with($kind,'workshop.'))return $kind==='workshop.direct_cost.validated'?app(CostLedgerSource::class)->source($hostActor,$id):app(CostLedgerSource::class)->correctionSource($hostActor,$kind,$id); if(str_starts_with($kind,'industry.'))return $kind==='industry.cost.validated'?app(IndustrialLedgerSource::class)->source($hostActor,$id):app(IndustrialLedgerSource::class)->correctionSource($hostActor,$kind,$id); return app(SalesLedgerSource::class)->source($hostActor,$kind,$id); }); }; $period=function($p)use($host,$hostActor,$stock){$extra=$this->withHost($host,$hostActor,function()use($p,$hostActor){$facts=0;$pending=0;$h=hash_init('sha256');foreach([SalesLedgerSource::class,CostLedgerSource::class,IndustrialLedgerSource::class,CashLedgerSource::class] as $class){$v=app($class)->period($hostActor,$p);$facts+=$v['facts'];$pending+=$v['pending'];hash_update($h,$v['hash']);}return ['facts'=>$facts,'pending'=>$pending,'hash'=>hash_final($h)];});$physical=$stock->period($p);return ['facts'=>$extra['facts']+$physical['facts'],'pending'=>$extra['pending']+$physical['pending'],'hash'=>hash('sha256',$extra['hash'].$physical['hash'])];}; $engine=new PayableJournal($native,$ledgerScope,$actor->id,$person,$authorize,fn()=>$authorize('view'),fn(int $id)=>$this->source($actor,$binding,$financialScope,$id),null,$additional,$period,$financialScope); return $work($engine,$actor,$binding,$origin+['sgf_host_user'=>$hostUser,'sgf_host_scope'=>$scope,'binding_revision'=>$revision],$ledgerScope,$stock,fn(\Closure $read)=>$this->withHost($host,$hostActor,$read)); }); });}catch(\Illuminate\Database\QueryException $e){if(!in_array($e->errorInfo[0]??null,['55P03','57014'],true))throw $e;return response()->json(['message'=>'Livro ocupado. Recupere a mesma operação, sem gerar outra chave.'],503)->header('Retry-After','5')->header('Cache-Control','no-store');} } }