864 lines
27 KiB
PHP
864 lines
27 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Pages\Onboarding;
|
|
|
|
use App\Filament\Resources\ProviderConnectionResource\Pages\CreateProviderConnection;
|
|
use App\Jobs\Onboarding\OnboardingConsentStatusJob;
|
|
use App\Jobs\Onboarding\OnboardingVerifyPermissionsJob;
|
|
use App\Models\OnboardingEvidence;
|
|
use App\Models\OnboardingSession;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\Auth\CapabilityResolver;
|
|
use App\Services\Intune\AuditLogger;
|
|
use App\Services\Onboarding\LegacyTenantCredentialMigrator;
|
|
use App\Services\Onboarding\OnboardingLockService;
|
|
use App\Services\OperationRunService;
|
|
use App\Support\Auth\Capabilities;
|
|
use App\Support\Onboarding\OnboardingTaskCatalog;
|
|
use App\Support\Onboarding\OnboardingTaskType;
|
|
use App\Support\OperationRunLinks;
|
|
use App\Support\Rbac\UiEnforcement;
|
|
use App\Support\Rbac\UiTooltips;
|
|
use Filament\Actions\Action;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Support\Arr;
|
|
|
|
class TenantOnboardingWizard extends Page
|
|
{
|
|
protected static bool $shouldRegisterNavigation = false;
|
|
|
|
protected static ?string $slug = 'onboarding';
|
|
|
|
protected static ?string $title = 'Onboarding';
|
|
|
|
protected string $view = 'filament.pages.onboarding.tenant-onboarding-wizard';
|
|
|
|
public ?OnboardingSession $session = null;
|
|
|
|
public bool $canStartProviderTasks = false;
|
|
|
|
public bool $canManageProviderConnections = false;
|
|
|
|
public bool $canManageTenant = false;
|
|
|
|
public bool $hasSessionLock = false;
|
|
|
|
public bool $sessionLockedByOther = false;
|
|
|
|
public ?string $sessionLockedByLabel = null;
|
|
|
|
public ?string $sessionLockedUntil = null;
|
|
|
|
public ?int $selectedProviderConnectionId = null;
|
|
|
|
public ?string $verifyPermissionsRunUrl = null;
|
|
|
|
public ?string $consentStatusRunUrl = null;
|
|
|
|
/**
|
|
* @var array<int, string>
|
|
*/
|
|
public array $handoffUserOptions = [];
|
|
|
|
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);
|
|
$this->canManageProviderConnections = $resolver->can($user, $tenant, Capabilities::PROVIDER_MANAGE);
|
|
$this->canManageTenant = $resolver->can($user, $tenant, Capabilities::TENANT_MANAGE);
|
|
|
|
$activeSession = OnboardingSession::query()
|
|
->where('tenant_id', $tenant->getKey())
|
|
->whereIn('status', ['draft', 'in_progress'])
|
|
->latest('id')
|
|
->first();
|
|
|
|
if (! $activeSession instanceof OnboardingSession && $this->canStartProviderTasks) {
|
|
$activeSession = OnboardingSession::query()->create([
|
|
'tenant_id' => $tenant->getKey(),
|
|
'status' => 'draft',
|
|
'current_step' => 1,
|
|
'assigned_to_user_id' => $user->getKey(),
|
|
'metadata' => [],
|
|
]);
|
|
}
|
|
|
|
$this->session = $activeSession;
|
|
|
|
$defaultConnectionId = ProviderConnection::query()
|
|
->where('tenant_id', $tenant->getKey())
|
|
->where('is_default', true)
|
|
->value('id');
|
|
|
|
$this->selectedProviderConnectionId = $this->session?->provider_connection_id
|
|
?? (is_int($defaultConnectionId) ? $defaultConnectionId : null);
|
|
|
|
$this->refreshCollaborationState(attemptAcquire: $this->canStartProviderTasks);
|
|
|
|
if ($this->session instanceof OnboardingSession
|
|
&& $this->hasSessionLock
|
|
&& $this->session->provider_connection_id === null
|
|
&& is_int($this->selectedProviderConnectionId)
|
|
&& $this->tenantHasLegacyCredentials($tenant)
|
|
) {
|
|
$connection = ProviderConnection::query()
|
|
->where('tenant_id', $tenant->getKey())
|
|
->whereKey($this->selectedProviderConnectionId)
|
|
->first();
|
|
|
|
if ($connection instanceof ProviderConnection) {
|
|
$this->session->update(['provider_connection_id' => $connection->getKey()]);
|
|
$this->session->refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
private function tenantHasLegacyCredentials(Tenant $tenant): bool
|
|
{
|
|
return trim((string) ($tenant->app_client_id ?? '')) !== ''
|
|
&& trim((string) ($tenant->app_client_secret ?? '')) !== '';
|
|
}
|
|
|
|
/**
|
|
* @return array<Action>
|
|
*/
|
|
protected function getHeaderActions(): array
|
|
{
|
|
$tenant = Tenant::current();
|
|
|
|
return [
|
|
UiEnforcement::forAction(
|
|
Action::make('takeover_onboarding_session')
|
|
->label('Take over')
|
|
->color('warning')
|
|
->requiresConfirmation()
|
|
->modalHeading('Take over onboarding session')
|
|
->modalDescription('This will take over the onboarding session lock. Use when the current lock holder is unavailable.')
|
|
->action(function (): void {
|
|
$this->takeoverSession();
|
|
}),
|
|
)
|
|
->requireCapability(Capabilities::TENANT_MANAGE)
|
|
->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
|
|
->apply()
|
|
->visible(fn (): bool => $this->session instanceof OnboardingSession && $this->sessionLockedByOther),
|
|
|
|
UiEnforcement::forAction(
|
|
Action::make('handoff_onboarding_session')
|
|
->label('Handoff')
|
|
->color('gray')
|
|
->requiresConfirmation()
|
|
->modalHeading('Handoff onboarding session')
|
|
->modalDescription('Assign onboarding to another tenant member and release your lock.')
|
|
->form([
|
|
Select::make('assigned_to_user_id')
|
|
->label('Assign to')
|
|
->options(fn (): array => $this->handoffUserOptions)
|
|
->searchable()
|
|
->required(),
|
|
])
|
|
->action(function (array $data): void {
|
|
$assignedToUserId = (int) ($data['assigned_to_user_id'] ?? 0);
|
|
$this->handoffSession($assignedToUserId);
|
|
}),
|
|
)
|
|
->requireCapability(Capabilities::TENANT_MANAGE)
|
|
->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
|
|
->apply()
|
|
->visible(fn (): bool => $this->session instanceof OnboardingSession && $this->hasSessionLock),
|
|
|
|
Action::make('release_onboarding_lock')
|
|
->label('Release lock')
|
|
->color('gray')
|
|
->visible(fn (): bool => $this->session instanceof OnboardingSession && $this->hasSessionLock)
|
|
->requiresConfirmation()
|
|
->modalHeading('Release onboarding lock')
|
|
->modalDescription('This will release your lock so another user can take over onboarding.')
|
|
->action(function (): void {
|
|
$this->releaseSessionLock();
|
|
}),
|
|
|
|
UiEnforcement::forAction(
|
|
Action::make('migrate_legacy_credentials')
|
|
->label('Migrate legacy credentials')
|
|
->color('warning')
|
|
->requiresConfirmation()
|
|
->modalHeading('Migrate legacy tenant credentials')
|
|
->modalDescription('This will copy the tenant\'s legacy app client secret into the selected provider connection credentials. The secret is never displayed.')
|
|
->action(function (): void {
|
|
$this->migrateLegacyCredentials();
|
|
}),
|
|
)
|
|
->requireCapability(Capabilities::PROVIDER_MANAGE)
|
|
->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
|
|
->apply()
|
|
->visible(fn (): bool => $this->canOfferLegacyCredentialMigration()),
|
|
];
|
|
}
|
|
|
|
private function refreshCollaborationState(bool $attemptAcquire = false): void
|
|
{
|
|
$tenant = Tenant::current();
|
|
|
|
$user = auth()->user();
|
|
if (! $user instanceof User) {
|
|
abort(403, 'Not allowed');
|
|
}
|
|
|
|
$this->hasSessionLock = false;
|
|
$this->sessionLockedByOther = false;
|
|
$this->sessionLockedByLabel = null;
|
|
$this->sessionLockedUntil = null;
|
|
$this->handoffUserOptions = [];
|
|
|
|
if (! $this->session instanceof OnboardingSession) {
|
|
return;
|
|
}
|
|
|
|
if ($this->canManageTenant) {
|
|
$this->handoffUserOptions = $tenant->users()
|
|
->orderBy('name')
|
|
->orderBy('email')
|
|
->get(['users.id', 'users.name', 'users.email'])
|
|
->mapWithKeys(function (User $member): array {
|
|
$label = trim((string) $member->name) !== ''
|
|
? (string) $member->name
|
|
: (string) $member->email;
|
|
|
|
if (trim((string) $member->email) !== '') {
|
|
$label .= ' <'.$member->email.'>';
|
|
}
|
|
|
|
return [(int) $member->getKey() => $label];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
if ($attemptAcquire) {
|
|
app(OnboardingLockService::class)->acquire($this->session, $user);
|
|
}
|
|
|
|
$this->session->refresh();
|
|
$this->session->loadMissing(['lockedBy']);
|
|
|
|
$this->hasSessionLock = (int) ($this->session->locked_by_user_id ?? 0) === (int) $user->getKey()
|
|
&& $this->session->locked_until?->isFuture();
|
|
|
|
$this->sessionLockedByOther = $this->isLockedByOther($this->session, $user);
|
|
|
|
if ($this->sessionLockedByOther) {
|
|
$lockedBy = $this->session->lockedBy;
|
|
$this->sessionLockedByLabel = $lockedBy instanceof User
|
|
? (trim((string) $lockedBy->name) !== '' ? (string) $lockedBy->name : (string) $lockedBy->email)
|
|
: 'another user';
|
|
}
|
|
|
|
$this->sessionLockedUntil = $this->session->locked_until?->diffForHumans();
|
|
}
|
|
|
|
private function ensureLockForMutation(): bool
|
|
{
|
|
$user = auth()->user();
|
|
if (! $user instanceof User) {
|
|
abort(403, 'Not allowed');
|
|
}
|
|
|
|
if (! $this->session instanceof OnboardingSession) {
|
|
return false;
|
|
}
|
|
|
|
$acquired = app(OnboardingLockService::class)->acquire($this->session, $user);
|
|
$this->refreshCollaborationState(attemptAcquire: false);
|
|
|
|
if ($acquired) {
|
|
return true;
|
|
}
|
|
|
|
Notification::make()
|
|
->title('Session is locked')
|
|
->body('Another user is currently editing onboarding. Take over the lock to make changes.')
|
|
->warning()
|
|
->send();
|
|
|
|
return false;
|
|
}
|
|
|
|
private function isLockedByOther(OnboardingSession $session, User $user): bool
|
|
{
|
|
if ($session->locked_by_user_id === null || $session->locked_until === null) {
|
|
return false;
|
|
}
|
|
|
|
if ($session->locked_until->isPast()) {
|
|
return false;
|
|
}
|
|
|
|
return (int) $session->locked_by_user_id !== (int) $user->getKey();
|
|
}
|
|
|
|
private function takeoverSession(): void
|
|
{
|
|
$tenant = Tenant::current();
|
|
|
|
$user = auth()->user();
|
|
if (! $user instanceof User) {
|
|
abort(403, 'Not allowed');
|
|
}
|
|
|
|
if (! $this->canManageTenant) {
|
|
abort(403);
|
|
}
|
|
|
|
if (! $this->session instanceof OnboardingSession) {
|
|
return;
|
|
}
|
|
|
|
$previousLockHolderId = $this->session->locked_by_user_id;
|
|
|
|
app(OnboardingLockService::class)->takeover($this->session, $user);
|
|
|
|
app(AuditLogger::class)->log(
|
|
tenant: $tenant,
|
|
action: 'onboarding.takeover',
|
|
context: [
|
|
'onboarding_session_id' => (int) $this->session->getKey(),
|
|
'previous_locked_by_user_id' => is_int($previousLockHolderId) ? $previousLockHolderId : null,
|
|
],
|
|
actorId: (int) $user->getKey(),
|
|
actorEmail: $user->email,
|
|
actorName: $user->name,
|
|
status: 'success',
|
|
resourceType: 'onboarding_session',
|
|
resourceId: (string) $this->session->getKey(),
|
|
);
|
|
|
|
$this->refreshCollaborationState(attemptAcquire: false);
|
|
|
|
Notification::make()
|
|
->title('Lock taken over')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
private function handoffSession(int $assignedToUserId): void
|
|
{
|
|
$tenant = Tenant::current();
|
|
|
|
$user = auth()->user();
|
|
if (! $user instanceof User) {
|
|
abort(403, 'Not allowed');
|
|
}
|
|
|
|
if (! $this->canManageTenant) {
|
|
abort(403);
|
|
}
|
|
|
|
if (! $this->session instanceof OnboardingSession) {
|
|
return;
|
|
}
|
|
|
|
if (! $this->ensureLockForMutation()) {
|
|
return;
|
|
}
|
|
|
|
$assignee = $tenant->users()->whereKey($assignedToUserId)->first();
|
|
if (! $assignee instanceof User) {
|
|
Notification::make()
|
|
->title('Assignee not found')
|
|
->danger()
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
$this->session->update(['assigned_to_user_id' => (int) $assignee->getKey()]);
|
|
|
|
app(OnboardingLockService::class)->release($this->session, $user);
|
|
|
|
app(AuditLogger::class)->log(
|
|
tenant: $tenant,
|
|
action: 'onboarding.handoff',
|
|
context: [
|
|
'onboarding_session_id' => (int) $this->session->getKey(),
|
|
'assigned_to_user_id' => (int) $assignee->getKey(),
|
|
],
|
|
actorId: (int) $user->getKey(),
|
|
actorEmail: $user->email,
|
|
actorName: $user->name,
|
|
status: 'success',
|
|
resourceType: 'onboarding_session',
|
|
resourceId: (string) $this->session->getKey(),
|
|
);
|
|
|
|
$this->refreshCollaborationState(attemptAcquire: false);
|
|
|
|
Notification::make()
|
|
->title('Onboarding handed off')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
private function releaseSessionLock(): void
|
|
{
|
|
$user = auth()->user();
|
|
if (! $user instanceof User) {
|
|
abort(403, 'Not allowed');
|
|
}
|
|
|
|
if (! $this->session instanceof OnboardingSession) {
|
|
return;
|
|
}
|
|
|
|
app(OnboardingLockService::class)->release($this->session, $user);
|
|
|
|
$this->refreshCollaborationState(attemptAcquire: false);
|
|
|
|
Notification::make()
|
|
->title('Lock released')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
private function canOfferLegacyCredentialMigration(): bool
|
|
{
|
|
if (! $this->session instanceof OnboardingSession) {
|
|
return false;
|
|
}
|
|
|
|
$tenant = Tenant::current();
|
|
|
|
if (! $this->tenantHasLegacyCredentials($tenant)) {
|
|
return false;
|
|
}
|
|
|
|
$connectionId = $this->session->provider_connection_id;
|
|
|
|
if (! is_int($connectionId)) {
|
|
return false;
|
|
}
|
|
|
|
$connection = ProviderConnection::query()
|
|
->where('tenant_id', $tenant->getKey())
|
|
->with('credential')
|
|
->whereKey($connectionId)
|
|
->first();
|
|
|
|
if (! $connection instanceof ProviderConnection) {
|
|
return false;
|
|
}
|
|
|
|
$credential = $connection->credential;
|
|
|
|
if ($credential === null) {
|
|
return true;
|
|
}
|
|
|
|
if ($credential->type !== 'client_secret') {
|
|
return false;
|
|
}
|
|
|
|
$payload = $credential->payload;
|
|
if (! is_array($payload)) {
|
|
return true;
|
|
}
|
|
|
|
$clientId = trim((string) Arr::get($payload, 'client_id'));
|
|
$clientSecret = trim((string) Arr::get($payload, 'client_secret'));
|
|
|
|
return $clientId === '' || $clientSecret === '';
|
|
}
|
|
|
|
private function migrateLegacyCredentials(): void
|
|
{
|
|
if (! $this->canManageProviderConnections) {
|
|
abort(403);
|
|
}
|
|
|
|
if (! $this->session instanceof OnboardingSession) {
|
|
return;
|
|
}
|
|
|
|
if (! $this->ensureLockForMutation()) {
|
|
return;
|
|
}
|
|
|
|
$tenant = Tenant::current();
|
|
|
|
$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;
|
|
}
|
|
|
|
$outcome = app(LegacyTenantCredentialMigrator::class)->migrate($tenant, $connection);
|
|
|
|
$this->refreshCollaborationState(attemptAcquire: false);
|
|
|
|
Notification::make()
|
|
->title($outcome['migrated'] ? 'Credentials migrated' : 'Migration not needed')
|
|
->body($outcome['message'])
|
|
->color($outcome['migrated'] ? 'success' : 'gray')
|
|
->send();
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{id: int, label: string}>
|
|
*/
|
|
public function providerConnections(): array
|
|
{
|
|
$tenant = Tenant::current();
|
|
|
|
return ProviderConnection::query()
|
|
->where('tenant_id', $tenant->getKey())
|
|
->orderByDesc('is_default')
|
|
->orderBy('provider')
|
|
->orderBy('display_name')
|
|
->get()
|
|
->map(function (ProviderConnection $connection): array {
|
|
$entraName = Arr::get(is_array($connection->metadata) ? $connection->metadata : [], 'entra_tenant_name');
|
|
$entraSuffix = is_string($entraName) && trim($entraName) !== '' ? ' — '.trim($entraName) : '';
|
|
|
|
$label = ($connection->display_name ?: ucfirst($connection->provider))
|
|
.$entraSuffix
|
|
.($connection->is_default ? ' (default)' : '');
|
|
|
|
return [
|
|
'id' => (int) $connection->getKey(),
|
|
'label' => $label,
|
|
];
|
|
})
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
public function updatedSelectedProviderConnectionId(): void
|
|
{
|
|
if (! $this->canStartProviderTasks) {
|
|
abort(403);
|
|
}
|
|
|
|
$tenant = Tenant::current();
|
|
|
|
if (! $this->session instanceof OnboardingSession) {
|
|
return;
|
|
}
|
|
|
|
if (! $this->ensureLockForMutation()) {
|
|
return;
|
|
}
|
|
|
|
if (! is_int($this->selectedProviderConnectionId)) {
|
|
$this->session->update(['provider_connection_id' => null]);
|
|
$this->session->refresh();
|
|
|
|
return;
|
|
}
|
|
|
|
$connection = ProviderConnection::query()
|
|
->where('tenant_id', $tenant->getKey())
|
|
->whereKey($this->selectedProviderConnectionId)
|
|
->first();
|
|
|
|
if (! $connection instanceof ProviderConnection) {
|
|
Notification::make()
|
|
->title('Connection not found')
|
|
->danger()
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
$this->session->update([
|
|
'provider_connection_id' => $connection->getKey(),
|
|
]);
|
|
$this->session->refresh();
|
|
|
|
$this->refreshCollaborationState(attemptAcquire: false);
|
|
|
|
Notification::make()
|
|
->title('Provider connection selected')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{task_type: string, title: string, step: int, prerequisites: array<int, string>}>
|
|
*/
|
|
public function planTasks(): array
|
|
{
|
|
return OnboardingTaskCatalog::all();
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
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;
|
|
}
|
|
|
|
public function startVerifyPermissions(): 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->ensureLockForMutation()) {
|
|
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;
|
|
}
|
|
|
|
if ($this->session->current_step < 4) {
|
|
$this->session->update(['current_step' => 4]);
|
|
$this->session->refresh();
|
|
}
|
|
|
|
$taskType = OnboardingTaskType::VerifyPermissions;
|
|
|
|
/** @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->verifyPermissionsRunUrl = OperationRunLinks::view($run, $tenant);
|
|
|
|
if ($run->wasRecentlyCreated) {
|
|
OnboardingVerifyPermissionsJob::dispatch(
|
|
tenantId: (int) $tenant->getKey(),
|
|
userId: (int) $user->getKey(),
|
|
providerConnectionId: (int) $connection->getKey(),
|
|
onboardingSessionId: (int) $this->session->getKey(),
|
|
operationRun: $run,
|
|
);
|
|
|
|
Notification::make()
|
|
->title('Verify permissions queued')
|
|
->body('Run queued. Use the link below to monitor progress.')
|
|
->success()
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
Notification::make()
|
|
->title('Verify permissions already queued')
|
|
->body('A run is already queued or running. Use the link below to monitor progress.')
|
|
->warning()
|
|
->send();
|
|
}
|
|
|
|
public function startConsentStatus(): 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) {
|
|
return;
|
|
}
|
|
|
|
if (! $this->ensureLockForMutation()) {
|
|
return;
|
|
}
|
|
|
|
if ($this->session->current_step < 4) {
|
|
$this->session->update(['current_step' => 4]);
|
|
$this->session->refresh();
|
|
}
|
|
|
|
if (! OnboardingTaskCatalog::prerequisitesMet(
|
|
taskType: OnboardingTaskType::ConsentStatus,
|
|
latestEvidenceStatusByTaskType: $this->latestEvidenceStatusByTaskType(),
|
|
)) {
|
|
Notification::make()
|
|
->title('Prerequisites not met')
|
|
->body('Run “Verify permissions” 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;
|
|
}
|
|
|
|
$taskType = OnboardingTaskType::ConsentStatus;
|
|
|
|
/** @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->consentStatusRunUrl = OperationRunLinks::view($run, $tenant);
|
|
|
|
if ($run->wasRecentlyCreated) {
|
|
OnboardingConsentStatusJob::dispatch(
|
|
tenantId: (int) $tenant->getKey(),
|
|
userId: (int) $user->getKey(),
|
|
providerConnectionId: (int) $connection->getKey(),
|
|
onboardingSessionId: (int) $this->session->getKey(),
|
|
operationRun: $run,
|
|
);
|
|
|
|
Notification::make()
|
|
->title('Consent status queued')
|
|
->success()
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
Notification::make()
|
|
->title('Consent status already queued')
|
|
->warning()
|
|
->send();
|
|
}
|
|
|
|
public function createProviderConnectionUrl(): string
|
|
{
|
|
return CreateProviderConnection::getUrl(tenant: Tenant::current());
|
|
}
|
|
}
|