*/ public array $runUrls = []; public function mount(): void { $tenant = Tenant::current(); $user = auth()->user(); if (! $user instanceof User) { abort(403, 'Not allowed'); } /** @var CapabilityResolver $resolver */ $resolver = app(CapabilityResolver::class); $this->canStartProviderTasks = $resolver->can($user, $tenant, Capabilities::PROVIDER_RUN); $activeSession = OnboardingSession::query() ->where('tenant_id', $tenant->getKey()) ->whereIn('status', ['draft', 'in_progress']) ->latest('id') ->first(); if (! $activeSession instanceof OnboardingSession) { $this->redirect(TenantOnboardingWizard::getUrl(tenant: $tenant)); return; } $this->session = $activeSession; if ($activeSession->current_step < 4) { $this->redirect(TenantOnboardingWizard::getUrl(tenant: $tenant)); } } /** * @return array */ public function latestEvidenceStatusByTaskType(): array { $tenant = Tenant::current(); $evidence = OnboardingEvidence::query() ->where('tenant_id', $tenant->getKey()) ->whereIn('task_type', OnboardingTaskType::all()) ->orderByDesc('recorded_at') ->get(); $byTask = []; foreach ($evidence as $row) { if (! isset($byTask[$row->task_type])) { $byTask[$row->task_type] = $row->status; } } return $byTask; } /** * @return array */ public function latestEvidenceByTaskType(): array { $tenant = Tenant::current(); $evidence = OnboardingEvidence::query() ->where('tenant_id', $tenant->getKey()) ->whereIn('task_type', OnboardingTaskType::all()) ->orderByDesc('recorded_at') ->get(); $byTask = []; foreach ($evidence as $row) { if (! isset($byTask[$row->task_type])) { $byTask[$row->task_type] = $row; } } return $byTask; } /** * @return array, * status: string, * badge: \App\Support\Badges\BadgeSpec, * evidence: OnboardingEvidence|null, * prerequisites_met: bool, * unmet_prerequisites: array, * }> */ public function taskRows(): array { $statuses = $this->latestEvidenceStatusByTaskType(); $evidenceByTask = $this->latestEvidenceByTaskType(); return collect(OnboardingTaskCatalog::all()) ->map(function (array $task) use ($statuses, $evidenceByTask): array { $taskType = $task['task_type']; $status = $statuses[$taskType] ?? 'unknown'; $unmet = OnboardingTaskCatalog::unmetPrerequisites($taskType, $statuses); return [ 'task_type' => $taskType, 'title' => $task['title'], 'step' => $task['step'], 'prerequisites' => $task['prerequisites'], 'status' => $status, 'badge' => BadgeCatalog::spec(BadgeDomain::OnboardingTaskStatus, $status), 'evidence' => $evidenceByTask[$taskType] ?? null, 'prerequisites_met' => count($unmet) === 0, 'unmet_prerequisites' => $unmet, ]; }) ->values() ->all(); } /** * @return array */ public function fixHintsFor(?string $reasonCode): array { return OnboardingFixHints::forReason($reasonCode); } /** * @return array */ public function recentEvidenceRows(int $limit = 20): array { $tenant = Tenant::current(); $evidence = OnboardingEvidence::query() ->where('tenant_id', $tenant->getKey()) ->orderByDesc('recorded_at') ->limit($limit) ->with('operationRun') ->get(); return $evidence ->map(function (OnboardingEvidence $row) use ($tenant): array { $runUrl = null; if ($row->operationRun) { $runUrl = OperationRunLinks::view($row->operationRun, $tenant); } return [ 'recorded_at' => $row->recorded_at?->toDateTimeString() ?? '', 'task_type' => $row->task_type, 'status' => $row->status, 'badge' => BadgeCatalog::spec(BadgeDomain::OnboardingTaskStatus, $row->status), 'reason_code' => $row->reason_code, 'message' => $row->message, 'run_url' => $runUrl, ]; }) ->values() ->all(); } public function startTask(string $taskType): void { if (! $this->canStartProviderTasks) { abort(403); } $tenant = Tenant::current(); $user = auth()->user(); if (! $user instanceof User) { abort(403, 'Not allowed'); } if (! $this->session instanceof OnboardingSession) { Notification::make() ->title('No onboarding session') ->danger() ->send(); return; } if ($this->session->current_step < 4) { $this->redirect(TenantOnboardingWizard::getUrl(tenant: $tenant)); return; } if (! in_array($taskType, OnboardingTaskType::all(), true)) { Notification::make() ->title('Unknown task') ->danger() ->send(); return; } $latestStatuses = $this->latestEvidenceStatusByTaskType(); if (! OnboardingTaskCatalog::prerequisitesMet($taskType, $latestStatuses)) { Notification::make() ->title('Prerequisites not met') ->body('Complete required tasks first.') ->warning() ->send(); return; } $connectionId = $this->session->provider_connection_id; if (! is_int($connectionId)) { Notification::make() ->title('Select a provider connection first') ->warning() ->send(); return; } $connection = ProviderConnection::query() ->where('tenant_id', $tenant->getKey()) ->whereKey($connectionId) ->first(); if (! $connection instanceof ProviderConnection) { Notification::make() ->title('Selected provider connection not found') ->danger() ->send(); return; } /** @var OperationRunService $runs */ $runs = app(OperationRunService::class); $run = $runs->ensureRunWithIdentity( tenant: $tenant, type: $taskType, identityInputs: ['task_type' => $taskType], context: [ 'task_type' => $taskType, 'onboarding_session_id' => (int) $this->session->getKey(), 'provider_connection_id' => (int) $connection->getKey(), ], initiator: $user, ); $this->runUrls[$taskType] = OperationRunLinks::view($run, $tenant); if (! $run->wasRecentlyCreated) { Notification::make() ->title('Task already queued') ->body('A run is already queued or running. Use the link to monitor progress.') ->warning() ->send(); return; } match ($taskType) { OnboardingTaskType::VerifyPermissions => OnboardingVerifyPermissionsJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) $user->getKey(), providerConnectionId: (int) $connection->getKey(), onboardingSessionId: (int) $this->session->getKey(), operationRun: $run, ), OnboardingTaskType::ConsentStatus => OnboardingConsentStatusJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) $user->getKey(), providerConnectionId: (int) $connection->getKey(), onboardingSessionId: (int) $this->session->getKey(), operationRun: $run, ), OnboardingTaskType::ConnectionDiagnostics => OnboardingConnectionDiagnosticsJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) $user->getKey(), providerConnectionId: (int) $connection->getKey(), onboardingSessionId: (int) $this->session->getKey(), operationRun: $run, ), OnboardingTaskType::InitialSync => OnboardingInitialSyncJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) $user->getKey(), providerConnectionId: (int) $connection->getKey(), onboardingSessionId: (int) $this->session->getKey(), operationRun: $run, ), default => null, }; Notification::make() ->title('Task queued') ->success() ->send(); } }