|null $types * @param array|null $policyIds */ public function __construct( public readonly int $tenantId, public readonly ?array $types = null, public readonly ?array $policyIds = null, ?OperationRun $operationRun = null ) { $this->operationRun = $operationRun; } public function middleware(): array { return [new TrackOperationRun]; } public function handle(PolicySyncService $service, BulkOperationService $bulkOperationService): void { $tenant = Tenant::findOrFail($this->tenantId); if ($this->policyIds !== null) { $ids = collect($this->policyIds) ->map(static fn ($id): int => (int) $id) ->unique() ->sort() ->values(); $syncedCount = 0; $skippedCount = 0; $failureSummary = []; foreach ($ids as $policyId) { $policy = Policy::query() ->whereKey($policyId) ->where('tenant_id', $tenant->getKey()) ->first(); if (! $policy) { $failureSummary[] = [ 'code' => 'policy.not_found', 'message' => $bulkOperationService->sanitizeFailureReason("Policy {$policyId} not found"), ]; continue; } if ($policy->ignored_at !== null) { $skippedCount++; continue; } try { $service->syncPolicy($tenant, $policy); $syncedCount++; } catch (\Throwable $e) { $failureSummary[] = [ 'code' => 'policy.sync_failed', 'message' => $bulkOperationService->sanitizeFailureReason($e->getMessage()), ]; } } $failureCount = count($failureSummary); $outcome = match (true) { $failureCount === 0 => 'succeeded', $syncedCount > 0 => 'partially_succeeded', default => 'failed', }; if ($this->operationRun) { /** @var OperationRunService $opService */ $opService = app(OperationRunService::class); $opService->updateRun( $this->operationRun, 'completed', $outcome, [ 'policies_total' => $ids->count(), 'policies_synced' => $syncedCount, 'policies_skipped' => $skippedCount, 'policies_failed' => $failureCount, ], $failureSummary ); } return; } $supported = config('tenantpilot.supported_policy_types', []); if ($this->types !== null) { $supported = array_values(array_filter($supported, fn ($type) => in_array($type['type'], $this->types, true))); } $result = $service->syncPoliciesWithReport($tenant, $supported); $syncedCount = count($result['synced'] ?? []); $failures = $result['failures'] ?? []; $failureCount = count($failures); $outcome = match (true) { $failureCount === 0 => 'succeeded', $syncedCount > 0 => 'partially_succeeded', default => 'failed', }; $failureSummary = []; foreach ($failures as $failure) { if (! is_array($failure)) { continue; } $policyType = (string) ($failure['policy_type'] ?? 'unknown'); $status = is_numeric($failure['status'] ?? null) ? (int) $failure['status'] : null; $errors = $failure['errors'] ?? null; $firstErrorMessage = null; if (is_array($errors) && isset($errors[0]) && is_array($errors[0])) { $firstErrorMessage = $errors[0]['message'] ?? null; } $message = $status !== null ? "{$policyType}: Graph returned {$status}" : "{$policyType}: Graph request failed"; if (is_string($firstErrorMessage) && $firstErrorMessage !== '') { $message .= ' - '.trim($firstErrorMessage); } $failureSummary[] = [ 'code' => $status !== null ? "GRAPH_HTTP_{$status}" : 'GRAPH_ERROR', 'message' => $bulkOperationService->sanitizeFailureReason($message), ]; } if ($this->operationRun) { /** @var OperationRunService $opService */ $opService = app(OperationRunService::class); $opService->updateRun( $this->operationRun, 'completed', $outcome, [ 'policy_types_total' => count($supported), 'policies_synced' => $syncedCount, 'policy_types_failed' => $failureCount, ], $failureSummary ); } } }