*/ public function page(User $actor, ?string $cursor = null, int $limit = 50): array { abort_unless(auth('web')->id() === $actor->id && $actor->tenant_id !== null && $actor->hasFunctions(['admin', 'warehouse_request_read', 'warehouse_request_prepare', 'warehouse_request_review', 'warehouse_request_deliver']), 403); $contexts = app(WorkContext::class); $enforced = $contexts->enforced($actor); abort_unless(! $enforced || $contexts->active(request()) === 'procurement.internal', 403); abort_unless(Schema::hasTable('warehouse_request_events'), 503, 'Instalação das entregas pendente.'); abort_unless($limit >= 1 && $limit <= 100, 422); $permissions = app(OrganizationAccess::class)->permissions($actor); sort($permissions); $permissionFingerprint = hash('sha256', json_encode($permissions, JSON_THROW_ON_ERROR)); $scope = [self::SCHEMA, $actor->tenant_id, $actor->id, $enforced ? 'procurement.internal' : 'legacy', $actor->department_id, $permissionFingerprint]; $after = 0; if ($cursor !== null) { try { $decoded = json_decode(Crypt::decryptString($cursor), true, 8, JSON_THROW_ON_ERROR); } catch (DecryptException|\JsonException $e) { throw ValidationException::withMessages(['cursor' => 'Cursor inválido. Reinicie a leitura.']); } if (! is_array($decoded) || ($decoded['scope'] ?? null) !== $scope || ! is_int($decoded['after'] ?? null) || $decoded['after'] < 0) { throw ValidationException::withMessages(['cursor' => 'O cursor pertence a outro âmbito. Reinicie a leitura.']); } $after = $decoded['after']; } return DB::transaction(function () use ($actor, $enforced, $scope, $after, $limit) { if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) { DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY'); } $requests = app(WarehouseWorkdesk::class)->visible($actor)->where('tenant_id', $actor->tenant_id); $requests->where(fn ($q) => $q->where('work_context_key', 'procurement.internal') ->when(! $enforced, fn ($q) => $q->orWhereNull('work_context_key'))); $events = WarehouseRequestEvent::query()->where('tenant_id', $actor->tenant_id) ->whereIn('warehouse_request_id', (clone $requests)->select('id')) ->whereIn('action', ['deliver', 'receive'])->where('id', '>', $after)->orderBy('id')->limit($limit + 1)->get(); $more = $events->count() > $limit; $events = $events->take($limit); $sources = $requests->whereIn('id', $events->pluck('warehouse_request_id'))->get()->keyBy('id'); $items = []; foreach ($events as $event) { $source = $sources->get($event->getAttribute('warehouse_request_id')); abort_unless($source instanceof WarehouseRequest, 409, 'Origem da entrega indisponível.'); $items[] = $this->envelope($source, $event); } return ['schema' => self::SCHEMA, 'items' => $items, 'has_more' => $more, 'next_cursor' => Crypt::encryptString(json_encode(['scope' => $scope, 'after' => $events->max('id') ?? $after], JSON_THROW_ON_ERROR))]; }); } /** @return array */ private function envelope(WarehouseRequest $request, WarehouseRequestEvent $event): array { $key = 'sgc:'.$request->tenant_id.':warehouse-event:'.$event->id; $snapshot = $request->snapshot; $lines = []; if ($event->getAttribute('action') === 'deliver') { foreach ($event->data['lines'] as $line) { $source = $snapshot['lines'][$line['index']]; $lines[] = ['request_line_index' => $line['index'], 'item' => $source['item'], 'quantity_milli' => $line['quantity_milli'], 'use' => $source['use'], 'destination' => $source['destination'], 'equipment' => $source['equipment'], 'stock_operation_id' => $line['stock_operation_id']]; } } $payload = ['schema' => 'sgc.warehouse.fulfilment-event.v1', 'idempotency_key' => $key, 'tenant_id' => $request->tenant_id, 'event_id' => $event->id, 'event_version' => $event->getAttribute('version'), 'action' => $event->getAttribute('action'), 'occurred_at' => CarbonImmutable::parse($event->getAttribute('created_at'))->utc()->toIso8601String(), 'actor' => $event->data['actor'], 'request' => ['id' => $request->id, 'reference' => 'RA-'.$request->id, 'document_type' => 'internal_warehouse_request', 'work_context_key' => $request->getAttribute('work_context_key'), 'requester' => $snapshot['requester'], 'warehouse' => $snapshot['warehouse'], 'cost_center' => $snapshot['cost_center'], 'destination' => $snapshot['destination']], 'lines' => $lines, 'delivery_event_key' => $event->getAttribute('action') === 'receive' ? 'sgc:'.$request->tenant_id.':warehouse-event:'.$event->data['delivery_event_id'] : null]; return [...$payload, 'payload_sha256' => hash('sha256', json_encode($payload, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))]; } }