fetchesBooking = $fetchesBooking; $this->generatesTransactionBillNumber = $generatesTransactionBillNumber; $this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor; $this->uploadsOpenAIFiles = $uploadsOpenAIFiles; $this->createsChatGPTResponseWithFiles = $createsChatGPTResponseWithFiles; } /** * @param UploadedFile[] $files * @param int $bookingId * @return array|null */ public function execute(array $files, ?int $bookingId = null): ?array { $tempPaths = []; try { foreach ($files as $index => $fileBase64) { // Strip prefix if present if (preg_match('/^data:application\/pdf;base64,/', $fileBase64)) { $fileBase64 = preg_replace('/^data:application\/pdf;base64,/', '', $fileBase64); } // Decode Base64 $fileContent = base64_decode($fileBase64); if ($fileContent === false) { Log::warning("Invalid Base64 for file index $index"); continue; } // Ensure temp directory exists $tempDir = storage_path('app/documents'); if (!file_exists($tempDir)) { mkdir($tempDir, 0755, true); } // Generate unique temp file path $fileName = uniqid("file_{$index}_") . '.pdf'; $filePath = $tempDir . '/' . $fileName; // Save file to disk file_put_contents($filePath, $fileContent); // Track temp path $tempPaths[] = $filePath; } Log::info('tempPaths: ' . json_encode($tempPaths)); // Upload to OpenAI $fileIds = $this->uploadsOpenAIFiles->execute($tempPaths); $kvp = KeyValuePair::where( 'key', KVPKey::CHATGPT_PROMPT_PREFIX . 'EXTRACT_1' )->first(); if (! $kvp) { throw new ResourceNotFoundException('AI Setup incomplete!'); } $userPrompt = $kvp->value; $response = $this->createsChatGPTResponseWithFiles->execute($userPrompt, $fileIds); // Extract the text content from the first output message $textContent = $response['output'][0]['content'][0]['text'] ?? '[]'; // Remove ```json fences if present if (preg_match('/```json\s*(.*?)\s*```/s', $textContent, $matches)) { $jsonString = $matches[1]; } else { $jsonString = $textContent; } // Decode JSON $data = json_decode($jsonString, true); if (json_last_error() !== JSON_ERROR_NONE) { Log::error('Failed to decode OpenAI JSON response', [ 'error' => json_last_error_msg(), 'textContent' => $textContent ]); $data = []; } // Optional: convert keys to snake_case $data = array_map(function($item) { $newItem = []; foreach ($item as $key => $value) { $newKey = strtolower(str_replace(' ', '_', $key)); $newItem[$newKey] = $value; } return $newItem; }, $data); if($bookingId){ /** @var Booking $booking */ $booking = $this->fetchesBooking->execute(['id' => $bookingId]); $billNumber = $this->generatesTransactionBillNumber->execute('PO-'); $total = collect($data)->sum(function($product){ return $product['quantity'] * floatval(str_replace(',', '', $product['unit_price'])); }); $object = new TransactionObject($billNumber, TransactionType::PURCHASE_ORDER, $booking->company->id, 1, 1, PaymentMethodType::CASH, $total, $total, $booking->fix_currency_id, $booking->fix_currency_id, 1, 0, 0, null, ApprovalStatus::PENDING_SUBMISSION, $data); $transaction = $this->createPurchaseOrderTransactionProcessor->execute($booking, $object); } return $data; } catch (\Exception $e) { Log::error('Error processing PDF files with ChatGPT', [ 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); throw $e; } finally { // Clean up foreach ($tempPaths as $path) { if (file_exists($path)) { unlink($path); } } } } function convertKeysToSnakeCase(array $items): array { return array_map(function($item) { $newItem = []; foreach ($item as $key => $value) { $newKey = strtolower(str_replace(' ', '_', $key)); $newItem[$newKey] = $value; } return $newItem; }, $items); } }