setupTenantConnection(); } public function __construct($recordId, $tempFilePath, $originalFileName, $type = 'OUT', $clientName = null) { $this->recordId = $recordId; $this->tempFilePath = $tempFilePath; $this->originalFileName = $originalFileName; $this->type = strtolower($type); $this->clientName = $clientName ?: session('clientName', 'default'); $this->clientName = Str::slug($this->clientName, '_'); } public function handle(RecordFileService $recordFileService) { try { Log::info("=== PROCESSING ATTACHMENT JOB START ==="); Log::info("Client: {$this->clientName}"); Log::info("Record ID: {$this->recordId}"); Log::info("Temp file: {$this->tempFilePath}"); Log::info("Original name: {$this->originalFileName}"); Log::info("Type: {$this->type}"); DB::table('records') ->where('id', $this->recordId) ->update([ 'attachment_status' => 'processing', 'updated_at' => now() ]); if (!Storage::disk('s3')->exists($this->tempFilePath)) { Log::error("Temp file not found on S3: {$this->tempFilePath}"); try { $tempFiles = Storage::disk('s3')->files("{$this->clientName}/temp/uploads"); Log::info("Available temp files for client '{$this->clientName}' on S3: " . json_encode($tempFiles)); } catch (\Exception $e) { Log::error("Could not list temp files for client '{$this->clientName}': " . $e->getMessage()); } throw new \Exception("Temp file not found on S3: {$this->tempFilePath}"); } $tempFileSize = Storage::disk('s3')->size($this->tempFilePath); Log::info("Temp file size: {$tempFileSize} bytes"); $extension = pathinfo($this->originalFileName, PATHINFO_EXTENSION); $fileName = time() . '_' . Str::random(10) . '.' . $extension; $finalPath = "{$this->clientName}/records/{$this->type}/{$this->recordId}/attachments/{$fileName}"; Log::info("Final path: {$finalPath}"); $copySuccess = $this->copyFileOnS3($this->tempFilePath, $finalPath); if (!$copySuccess) { throw new \Exception("Failed to copy file from {$this->tempFilePath} to {$finalPath}"); } if (!Storage::disk('s3')->exists($finalPath)) { throw new \Exception("Final file not found after copy: {$finalPath}"); } $finalFileSize = Storage::disk('s3')->size($finalPath); Log::info("Final file size: {$finalFileSize} bytes"); if ($finalFileSize !== $tempFileSize) { Log::warning("File size mismatch! Temp: {$tempFileSize}, Final: {$finalFileSize}"); } else { Log::info("File sizes match - copy successful"); } DB::table('records') ->where('id', $this->recordId) ->update([ 'attachment' => $finalPath, 'attachment_status' => 'completed', 'updated_at' => now() ]); $this->cleanupTempFile($this->tempFilePath); Log::info("Attachment processing completed successfully for record {$this->recordId}: {$finalPath}"); Log::info("=== PROCESSING ATTACHMENT JOB END ==="); } catch (\Exception $e) { Log::error("Failed to process attachment for record {$this->recordId}: " . $e->getMessage()); Log::error("Stack trace: " . $e->getTraceAsString()); DB::table('records') ->where('id', $this->recordId) ->update([ 'attachment_status' => 'failed', 'updated_at' => now() ]); $this->cleanupTempFile($this->tempFilePath); throw $e; } } /** * Enhanced S3 copy with multiple fallback approaches */ private function copyFileOnS3($sourcePath, $destinationPath) { Log::info("Attempting S3 copy from {$sourcePath} to {$destinationPath}"); try { Log::info("Trying Method 1: Standard S3 copy"); $copyResult = Storage::disk('s3')->copy($sourcePath, $destinationPath); if ($copyResult && Storage::disk('s3')->exists($destinationPath)) { Log::info("Method 1 successful: Standard S3 copy"); return true; } else { Log::warning("Method 1 failed: Standard S3 copy returned " . ($copyResult ? 'true' : 'false')); } } catch (\Exception $e) { Log::warning("Method 1 exception: " . $e->getMessage()); } try { Log::info("Trying Method 2: Read and write"); $fileContent = Storage::disk('s3')->get($sourcePath); if (!$fileContent) { throw new \Exception("Could not read source file content"); } $writeResult = Storage::disk('s3')->put($destinationPath, $fileContent); if ($writeResult && Storage::disk('s3')->exists($destinationPath)) { Log::info("Method 2 successful: Read and write"); return true; } else { Log::warning("Method 2 failed: Write returned " . ($writeResult ? 'true' : 'false')); } } catch (\Exception $e) { Log::warning("Method 2 exception: " . $e->getMessage()); } try { Log::info("Trying Method 3: Stream copy"); $sourceStream = Storage::disk('s3')->readStream($sourcePath); if (!$sourceStream) { throw new \Exception("Could not open source stream"); } $writeResult = Storage::disk('s3')->writeStream($destinationPath, $sourceStream); if (is_resource($sourceStream)) { fclose($sourceStream); } if ($writeResult && Storage::disk('s3')->exists($destinationPath)) { Log::info(" Method 3 successful: Stream copy"); return true; } else { Log::warning("Method 3 failed: Stream write returned " . ($writeResult ? 'true' : 'false')); } } catch (\Exception $e) { Log::warning("Method 3 exception: " . $e->getMessage()); } Log::error("All S3 copy methods failed"); return false; } /** * Clean up temp file with error handling */ private function cleanupTempFile($tempPath) { try { if (Storage::disk('s3')->exists($tempPath)) { $deleted = Storage::disk('s3')->delete($tempPath); if ($deleted) { Log::info("Temp file deleted: {$tempPath}"); } else { Log::warning("Failed to delete temp file: {$tempPath}"); } } else { Log::info("Temp file already gone: {$tempPath}"); } } catch (\Exception $e) { Log::error("Error deleting temp file {$tempPath}: " . $e->getMessage()); } } public function failed(\Exception $exception) { Log::error("=== JOB PERMANENTLY FAILED ==="); Log::error("Client: {$this->clientName}"); Log::error("Record ID: {$this->recordId}"); Log::error("Exception: " . $exception->getMessage()); DB::table('records') ->where('id', $this->recordId) ->update([ 'attachment_status' => 'failed', 'updated_at' => now() ]); $this->cleanupTempFile($this->tempFilePath); } /** * Get job tags for monitoring */ public function tags() { return [ 'attachment', 'client:' . $this->clientName, 'record:' . $this->recordId, 'type:' . $this->type, 'file:' . basename($this->tempFilePath) ]; } }