,person:string} */ private function context(User $user): array { abort_unless(app()->environment('payable-testing') && config('general-ledger.synthetic_enabled') === true, 403); $package = config('general-ledger.path'); abort_unless(is_string($package) && is_file($package.'/bootstrap.php'), 503, 'Pacote contabilístico não composto.'); require_once $package.'/bootstrap.php'; LedgerSchema::assertSynthetic(DB::connection()); $context = app(SupplierPayables::class)->readContext($user); abort_unless(DB::table('pa_access')->where($context['scope'])->where(['user_id' => $user->id, 'action' => 'general_ledger.view', 'person' => $context['person']])->exists(), 403); return $context; } public function can(User $user, string $action): bool { $c = $this->context($user); return DB::table('pa_access')->where($c['scope'])->where(['user_id' => $user->id, 'action' => 'general_ledger.'.$action, 'person' => $c['person']])->exists(); } public function engine(User $user, ?\Closure $fault = null): PayableJournal { $c = $this->context($user); $scope = $c['scope']; $person = $c['person']; $authorize = function (string $action, bool $lock = true) use ($user, $scope, $person) { if ($lock) { app(ConflictGuard::class)->lockCompany($user); app(CurrentActor::class)->lock($user, [PurchasingRole::Finance, PurchasingRole::Admin]); } $fresh = $this->context($user); abort_unless($fresh['scope'] === $scope && $fresh['person'] === $person, 409, 'Pessoa ou contexto alterados.'); abort_unless($this->can($user, $action), 403); }; return new PayableJournal(DB::connection(), $scope, $user->id, $person, fn ($a) => $authorize($a), fn () => $authorize('view', false), fn ($id) => $this->source($user, $scope, $id), $fault); } /** @param array $scope * @return array */ private function source(User $user, array $scope, int $id): array { $o = DB::table('pa_obligations')->where($scope)->where('id', $id)->lockForUpdate()->first() ?? throw new \DomainException('Obrigação indisponível nesta organização.'); $origin = DB::table('pa_sources')->where($scope)->where('id', $o->source_id)->lockForUpdate()->first() ?? throw new \DomainException('Origem ausente.'); $finance = app(SupplierPayables::class); $doc = $finance->document($user, (int) $origin->local_source_id); $position = $finance->engine($user)->positionForSource((int) $origin->local_source_id); if (! $position || (int) $position['id'] !== $id || $position['source_conflict']) { throw new \DomainException('Origem financeira mudou; nova conferência necessária.'); } $p = DB::table('pa_proposals')->where($scope)->where('id', $o->proposal_id)->first() ?? throw new \DomainException('Conferência original ausente.'); return ['effect_type' => 'supplier_payable.recognised', 'origin_key' => $origin->source_key, 'obligation_id' => $id, 'local_source_id' => (int) $origin->local_source_id, 'document_revision' => $o->source_revision, 'recognition_revision' => hash('sha256', Payables::canonical(['obligation' => (array) $o, 'recognition_snapshot' => $p->snapshot])), 'principal_minor' => (int) $o->principal_minor, 'currency' => $o->currency, 'due_on' => $o->due_on, 'tax_minor' => $doc['tax_minor'], 'compatible' => true, 'history_imported' => $position['history_imported']]; } /** @param array $options * @return array */ public function report(User $user, string $kind, array $options): array { if (! in_array($kind, ['trial-balance', 'account-ledger'], true)) { throw new \InvalidArgumentException('Relatório desconhecido.'); } $own = DB::transactionLevel() === 0; return DB::transaction(function () use ($user, $kind, $options, $own) { if ($own) { DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY'); } $engine = $this->engine($user); abort_unless(class_exists(ReportsWorkspace::class), 503, 'Relatórios não incluídos no pacote composto.'); foreach (LedgerSchema::TABLES as $table) { abort_unless(DB::getSchemaBuilder()->hasTable($table), 503, 'Fonte contabilística indisponível neste ensaio.'); } return $kind === 'trial-balance' ? $engine->trialBalance($options) : $engine->accountLedger($options); }); } /** @return array */ public function workspace(User $user, int $page = 1): array { abort_unless(config('general-ledger.cycle_enabled') === true, 403); $own = DB::transactionLevel() === 0; return DB::transaction(function () use ($user, $page, $own) { if ($own) { DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY'); } $engine = $this->engine($user); $data = $engine->cycleListing($page); $data['book'] = $engine->listing(['page' => $page]); $actions = ['prepare', 'validate', 'post', 'correct_prepare', 'correct_validate', 'correct_post', 'period_prepare', 'period_validate', 'period_close']; $data['permissions'] = array_combine($actions, array_map(fn ($a) => $this->can($user, $a), $actions)); return $data; }); } /** @param array $options * @return array */ public function listing(User $user, array $options = []): array { $own = DB::transactionLevel() === 0; return DB::transaction(function () use ($user, $options, $own) { if ($own) { DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY'); } return $this->engine($user)->listing($options); }); } }