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(BulkOperationService $bulkOperationService, InventorySyncService $inventorySyncService, AuditLogger $auditLogger): 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.'); } $bulkRun = BulkOperationRun::query()->find($this->bulkRunId); if (! $bulkRun instanceof BulkOperationRun) { throw new RuntimeException('BulkOperationRun not found.'); } $run = InventorySyncRun::query()->find($this->inventorySyncRunId); if (! $run instanceof InventorySyncRun) { throw new RuntimeException('InventorySyncRun not found.'); } $bulkOperationService->start($bulkRun); $processedPolicyTypes = []; // 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 ($bulkOperationService, $bulkRun, &$processedPolicyTypes): void { $processedPolicyTypes[] = $policyType; if ($success) { $bulkOperationService->recordSuccess($bulkRun); return; } $bulkOperationService->recordFailure($bulkRun, $policyType, $errorCode ?? 'failed'); }, ); $policyTypes = is_array($bulkRun->item_ids ?? null) ? $bulkRun->item_ids : []; if ($policyTypes === []) { $policyTypes = is_array($run->selection_payload['policy_types'] ?? null) ? $run->selection_payload['policy_types'] : []; } // --- Helper to update OperationRun with rich context --- $updateOpRun = function (string $outcome, array $counts = [], array $failures = []) { if ($this->operationRun) { /** @var OperationRunService $opService */ $opService = app(OperationRunService::class); $opService->updateRun( $this->operationRun, 'completed', $outcome, $counts, $failures ); } }; // ----------------------------------------------------- if ($run->status === InventorySyncRun::STATUS_SUCCESS) { $bulkOperationService->complete($bulkRun); // Update Operation Run explicitly to provide counts $updateOpRun('succeeded', [ 'observed' => $run->items_observed_count, 'upserted' => $run->items_upserted_count, ]); $auditLogger->log( tenant: $tenant, action: 'inventory.sync.completed', context: [ 'metadata' => [ 'inventory_sync_run_id' => $run->id, 'bulk_run_id' => $bulkRun->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) { $bulkOperationService->complete($bulkRun); $updateOpRun('partially_succeeded', [ 'observed' => $run->items_observed_count, 'upserted' => $run->items_upserted_count, 'errors' => $run->errors_count, ], [ // Minimal error summary ['code' => 'PARTIAL_SYNC', 'message' => "Errors: {$run->errors_count}"], ]); $auditLogger->log( tenant: $tenant, action: 'inventory.sync.partial', context: [ 'metadata' => [ 'inventory_sync_run_id' => $run->id, 'bulk_run_id' => $bulkRun->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'); foreach ($policyTypes as $policyType) { $bulkOperationService->recordSkippedWithReason($bulkRun, (string) $policyType, $reason); } $bulkOperationService->complete($bulkRun); $updateOpRun('failed', [], [['code' => 'SKIPPED', 'message' => $reason]]); $auditLogger->log( tenant: $tenant, action: 'inventory.sync.skipped', context: [ 'metadata' => [ 'inventory_sync_run_id' => $run->id, 'bulk_run_id' => $bulkRun->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))); foreach ($missingPolicyTypes as $policyType) { $bulkOperationService->recordFailure($bulkRun, (string) $policyType, $reason); } $bulkOperationService->complete($bulkRun); $updateOpRun('failed', [], [['code' => 'FAILED', 'message' => $reason]]); $auditLogger->log( tenant: $tenant, action: 'inventory.sync.failed', context: [ 'metadata' => [ 'inventory_sync_run_id' => $run->id, 'bulk_run_id' => $bulkRun->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(); } }