hasTable('pa_source_histories') && (new HistoricalPayments(DB::connection(), []))->available(); } public function payments(User $user, int $localId): array { abort_unless($this->available(), 503, 'Recibos históricos não ligados neste ensaio.'); $adapter = app(SupplierPayables::class); $scope = $adapter->scope($user); abort_unless($adapter->can($user, 'view'), 403); $source = $adapter->document($user, $localId); $receipt = DB::table('pa_source_histories')->where($scope)->where('local_source_id', $localId)->orderByDesc('version')->first(); return $this->decode($source, $receipt); } /** Local read snapshots only; no command obtains its source through this batch. */ public function page(User $user, array $documents, bool $validatedCapture = false): array { abort_unless($this->available(), 503); $adapter = app(SupplierPayables::class); $scope = $adapter->scope($user); abort_unless($adapter->can($user, 'view'), 403); $receipts = DB::table('pa_source_histories')->where($scope)->whereIn('local_source_id', array_keys($documents)) ->distinct('local_source_id')->orderBy('local_source_id')->orderByDesc('version')->get()->keyBy('local_source_id'); $result = []; foreach ($documents as $id => $source) { try { $result[$id] = $this->decode($source, $receipts->get($id), $validatedCapture ? $scope : null); } catch (\DomainException $e) { $result[$id] = $e; } } return $result; } private function decode(array $source, ?object $receipt, ?array $captureScope = null): array|HistoricalCapture { if (! $receipt) { throw new \DomainException('Histórico não reconciliado para esta origem e contexto.'); } $payload = json_decode($receipt->payload, true, 64, JSON_THROW_ON_ERROR); if (! is_array($payload) || ! hash_equals($receipt->receipt_hash, hash('sha256', Payables::canonical($payload)))) { throw new \DomainException('Recibo histórico divergente.'); } if (!(($payload['version'] ?? null) === self::VERSION && ($payload['source_key'] ?? null) === Payables::identity($source) && ($payload['source_revision'] ?? null) === Payables::revision($source) && ($payload['receipt_version'] ?? null) === (int) $receipt->version && is_array($payload['payments'] ?? null) && array_is_list($payload['payments']))) { throw new \DomainException('Recibo histórico antigo ou incompatível; exige nova conferência da origem.'); } $value = $captureScope === null ? null : HistoricalCapture::fromPayments($captureScope, $source, $payload['payments']); $capture = $value ? $value->forSource($captureScope, $source) : HistoricalPayments::capture($source, $payload['payments']); if (($payload['history_hash'] ?? null) !== $capture['history_hash']) { throw new \DomainException('Fotografia histórica incompatível com os pagamentos originais.'); } return $value ?? $capture['payments']; } }