operationRun = $operationRun; } public function middleware(): array { return [new TrackOperationRun]; } /** * Execute the job. */ public function handle(DriftFindingGenerator $generator, BulkOperationService $bulkOperationService): void { Log::info('GenerateDriftFindingsJob: started', [ 'tenant_id' => $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); if ($this->operationRun) { /** @var OperationRunService $opService */ $opService = app(OperationRunService::class); $opService->updateRun( $this->operationRun, 'completed', 'succeeded', ['findings_created' => $created] ); } $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()); // TrackOperationRun middleware might catch this, but explicit fail ensures structure if ($this->operationRun) { /** @var OperationRunService $opService */ $opService = app(OperationRunService::class); $opService->failRun($this->operationRun, $e); } $this->notifyStatus($run->refresh()); throw $e; } } private function notifyStatus(BulkOperationRun $run): void { try { if (! $run->relationLoaded('user')) { $run->loadMissing('user'); } if (! $run->user) { return; } $tenant = Tenant::query()->find((int) $run->tenant_id); if (! $tenant instanceof Tenant) { return; } $status = $run->statusBucket(); $title = match ($status) { 'queued' => 'Drift generation queued', 'running' => 'Drift generation started', 'succeeded' => 'Drift generation completed', 'partially succeeded' => 'Drift generation completed (partial)', default => 'Drift generation failed', }; $body = sprintf( 'Total: %d, processed: %d, succeeded: %d, failed: %d, skipped: %d.', (int) $run->total_items, (int) $run->processed_items, (int) $run->succeeded, (int) $run->failed, (int) $run->skipped, ); $notification = Notification::make() ->title($title) ->body($body) ->actions([ Action::make('view_run') ->label('View run') ->url($this->operationRun ? OperationRunLinks::view($this->operationRun, $tenant) : OperationRunLinks::index($tenant)), ]); match ($status) { 'succeeded' => $notification->success(), 'partially succeeded' => $notification->warning(), 'queued', 'running' => $notification->info(), default => $notification->danger(), }; $notification ->sendToDatabase($run->user) ->send(); } 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(), ]); } } }