operationRun = $operationRun; } /** * Get the middleware the job should pass through. * * @return array */ public function middleware(): array { return [new TrackOperationRun]; } /** * Execute the job. */ public function handle(InventorySyncService $inventorySyncService, AuditLogger $auditLogger, OperationRunService $operationRunService): void { $tenant = Tenant::query()->find($this->tenantId); if (! $tenant instanceof Tenant) { throw new RuntimeException('Tenant not found.'); } $user = User::query()->find($this->userId); if (! $user instanceof User) { throw new RuntimeException('User not found.'); } $run = InventorySyncRun::query()->find($this->inventorySyncRunId); if (! $run instanceof InventorySyncRun) { throw new RuntimeException('InventorySyncRun not found.'); } $policyTypes = is_array($run->selection_payload['policy_types'] ?? null) ? $run->selection_payload['policy_types'] : []; if (! is_array($policyTypes)) { $policyTypes = []; } $processedPolicyTypes = []; $successCount = 0; $failedCount = 0; // Note: The TrackOperationRun middleware will automatically set status to 'running' at start. // It will also handle success completion if no exceptions thrown. // However, InventorySyncService execution logic might be complex with partial failures. // We might want to explicitly update the OperationRun if partial failures occur. $run = $inventorySyncService->executePendingRun( $run, $tenant, function (string $policyType, bool $success, ?string $errorCode) use (&$processedPolicyTypes, &$successCount, &$failedCount): void { $processedPolicyTypes[] = $policyType; if ($success) { $successCount++; return; } $failedCount++; }, ); if ($run->status === InventorySyncRun::STATUS_SUCCESS) { if ($this->operationRun) { $operationRunService->updateRun( $this->operationRun, status: OperationRunStatus::Completed->value, outcome: OperationRunOutcome::Succeeded->value, summaryCounts: [ 'total' => count($policyTypes), 'processed' => count($policyTypes), 'succeeded' => count($policyTypes), 'failed' => 0, // Reuse allowed keys for inventory item stats. 'items' => (int) $run->items_observed_count, 'updated' => (int) $run->items_upserted_count, ], ); } $auditLogger->log( tenant: $tenant, action: 'inventory.sync.completed', context: [ 'metadata' => [ 'inventory_sync_run_id' => $run->id, 'selection_hash' => $run->selection_hash, 'observed' => $run->items_observed_count, 'upserted' => $run->items_upserted_count, ], ], actorId: $user->id, actorEmail: $user->email, actorName: $user->name, resourceType: 'inventory_sync_run', resourceId: (string) $run->id, ); Notification::make() ->title('Inventory sync completed') ->body('Inventory sync finished successfully.') ->success() ->actions($this->operationRun ? [ \Filament\Actions\Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($this->operationRun, $tenant)), ] : []) ->sendToDatabase($user) ->send(); return; } if ($run->status === InventorySyncRun::STATUS_PARTIAL) { if ($this->operationRun) { $operationRunService->updateRun( $this->operationRun, status: OperationRunStatus::Completed->value, outcome: OperationRunOutcome::PartiallySucceeded->value, summaryCounts: [ 'total' => count($policyTypes), 'processed' => count($policyTypes), 'succeeded' => max(0, count($policyTypes) - (int) $run->errors_count), 'failed' => (int) $run->errors_count, 'items' => (int) $run->items_observed_count, 'updated' => (int) $run->items_upserted_count, ], failures: [ ['code' => 'inventory.partial', 'message' => "Errors: {$run->errors_count}"], ], ); } $auditLogger->log( tenant: $tenant, action: 'inventory.sync.partial', context: [ 'metadata' => [ 'inventory_sync_run_id' => $run->id, 'selection_hash' => $run->selection_hash, 'observed' => $run->items_observed_count, 'upserted' => $run->items_upserted_count, 'errors' => $run->errors_count, ], ], actorId: $user->id, actorEmail: $user->email, actorName: $user->name, status: 'failure', resourceType: 'inventory_sync_run', resourceId: (string) $run->id, ); Notification::make() ->title('Inventory sync completed with errors') ->body('Inventory sync finished with some errors. Review the run details for error codes.') ->warning() ->actions($this->operationRun ? [ \Filament\Actions\Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($this->operationRun, $tenant)), ] : []) ->sendToDatabase($user) ->send(); return; } if ($run->status === InventorySyncRun::STATUS_SKIPPED) { $reason = (string) (($run->error_codes ?? [])[0] ?? 'skipped'); if ($this->operationRun) { $operationRunService->updateRun( $this->operationRun, status: OperationRunStatus::Completed->value, outcome: OperationRunOutcome::Failed->value, summaryCounts: [ 'total' => count($policyTypes), 'processed' => count($policyTypes), 'succeeded' => 0, 'failed' => 0, 'skipped' => count($policyTypes), ], failures: [ ['code' => 'inventory.skipped', 'message' => $reason], ], ); } $auditLogger->log( tenant: $tenant, action: 'inventory.sync.skipped', context: [ 'metadata' => [ 'inventory_sync_run_id' => $run->id, 'selection_hash' => $run->selection_hash, 'reason' => $reason, ], ], actorId: $user->id, actorEmail: $user->email, actorName: $user->name, resourceType: 'inventory_sync_run', resourceId: (string) $run->id, ); Notification::make() ->title('Inventory sync skipped') ->body('Inventory sync could not start due to locks or concurrency limits.') ->warning() ->actions($this->operationRun ? [ \Filament\Actions\Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($this->operationRun, $tenant)), ] : []) ->sendToDatabase($user) ->send(); return; } $reason = (string) (($run->error_codes ?? [])[0] ?? 'failed'); $missingPolicyTypes = array_values(array_diff($policyTypes, array_unique($processedPolicyTypes))); if ($this->operationRun) { $operationRunService->updateRun( $this->operationRun, status: OperationRunStatus::Completed->value, outcome: OperationRunOutcome::Failed->value, summaryCounts: [ 'total' => count($policyTypes), 'processed' => count($policyTypes), 'succeeded' => $successCount, 'failed' => max($failedCount, count($missingPolicyTypes)), ], failures: [ ['code' => 'inventory.failed', 'message' => $reason], ], ); } $auditLogger->log( tenant: $tenant, action: 'inventory.sync.failed', context: [ 'metadata' => [ 'inventory_sync_run_id' => $run->id, 'selection_hash' => $run->selection_hash, 'reason' => $reason, ], ], actorId: $user->id, actorEmail: $user->email, actorName: $user->name, status: 'failure', resourceType: 'inventory_sync_run', resourceId: (string) $run->id, ); Notification::make() ->title('Inventory sync failed') ->body('Inventory sync finished with errors.') ->danger() ->actions($this->operationRun ? [ \Filament\Actions\Action::make('view_run') ->label('View run') ->url(OperationRunLinks::view($this->operationRun, $tenant)), ] : []) ->sendToDatabase($user) ->send(); } }