$this->tenantId, 'baseline_run_id' => $this->baselineRunId, 'current_run_id' => $this->currentRunId, 'scope_key' => $this->scopeKey, 'bulk_operation_run_id' => $this->bulkOperationRunId, ]); $tenant = Tenant::query()->find($this->tenantId); if (! $tenant instanceof Tenant) { throw new RuntimeException('Tenant not found.'); } $baseline = InventorySyncRun::query()->find($this->baselineRunId); if (! $baseline instanceof InventorySyncRun) { throw new RuntimeException('Baseline run not found.'); } $current = InventorySyncRun::query()->find($this->currentRunId); if (! $current instanceof InventorySyncRun) { throw new RuntimeException('Current run not found.'); } $run = BulkOperationRun::query() ->where('tenant_id', $tenant->getKey()) ->find($this->bulkOperationRunId); if (! $run instanceof BulkOperationRun) { throw new RuntimeException('Bulk operation run not found.'); } $bulkOperationService->start($run); try { $created = $generator->generate( tenant: $tenant, baseline: $baseline, current: $current, scopeKey: $this->scopeKey, ); Log::info('GenerateDriftFindingsJob: completed', [ 'tenant_id' => $this->tenantId, 'baseline_run_id' => $this->baselineRunId, 'current_run_id' => $this->currentRunId, 'scope_key' => $this->scopeKey, 'bulk_operation_run_id' => $this->bulkOperationRunId, 'created_findings_count' => $created, ]); $bulkOperationService->recordSuccess($run); $bulkOperationService->complete($run); $this->notifyStatus($run->refresh()); } catch (Throwable $e) { Log::error('GenerateDriftFindingsJob: failed', [ 'tenant_id' => $this->tenantId, 'baseline_run_id' => $this->baselineRunId, 'current_run_id' => $this->currentRunId, 'scope_key' => $this->scopeKey, 'bulk_operation_run_id' => $this->bulkOperationRunId, 'error' => $e->getMessage(), ]); $bulkOperationService->recordFailure( run: $run, itemId: $this->scopeKey, reason: $e->getMessage(), reasonCode: 'unknown', ); $bulkOperationService->fail($run, $e->getMessage()); $this->notifyStatus($run->refresh()); throw $e; } } private function notifyStatus(BulkOperationRun $run): void { try { if (! $run->relationLoaded('user')) { $run->loadMissing('user'); } if (! $run->user) { return; } $status = 'failed'; try { $status = $run->statusBucket(); } catch (Throwable) { $failureEntries = $run->failures ?? []; $hasNonSkippedFailure = false; foreach ($failureEntries as $entry) { if (! is_array($entry)) { continue; } if (($entry['type'] ?? 'failed') !== 'skipped') { $hasNonSkippedFailure = true; break; } } $failedCount = (int) ($run->failed ?? 0); $succeededCount = (int) ($run->succeeded ?? 0); $hasFailures = $failedCount > 0 || $hasNonSkippedFailure; if ($succeededCount > 0 && $hasFailures) { $status = 'partially succeeded'; } elseif ($succeededCount === 0 && $hasFailures) { $status = 'failed'; } else { $status = match ($run->status) { 'pending' => 'queued', 'running' => 'running', 'completed', 'completed_with_errors' => 'succeeded', default => 'failed', }; } } $run->user->notify(new RunStatusChangedNotification([ 'tenant_id' => (int) $run->tenant_id, 'run_type' => 'bulk_operation', 'run_id' => (int) $run->getKey(), 'status' => $status, 'counts' => [ 'total' => (int) $run->total_items, 'processed' => (int) $run->processed_items, 'succeeded' => (int) $run->succeeded, 'failed' => (int) $run->failed, 'skipped' => (int) $run->skipped, ], ])); } catch (Throwable $e) { Log::warning('GenerateDriftFindingsJob: status notification failed', [ 'tenant_id' => (int) $run->tenant_id, 'bulk_operation_run_id' => (int) $run->getKey(), 'error' => $e->getMessage(), ]); } } }