fetchesBooking = $fetchesBooking; $this->generatesTransactionBillNumber = $generatesTransactionBillNumber; $this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor; $this->createsChatGPTResponse = $createsChatGPTResponse; $this->createsClaudeResponse = $createsClaudeResponse; } /** * @param array $products * @param array $productsMetadata * @param int|null $bookingId * @return array * @throws \App\Classes\Exceptions\MalformedRequestException * @throws \App\Classes\Exceptions\ResourceNotFoundException * @throws \Exception */ public function execute(array $products, array $productsMetadata, ?int $bookingId = null) { foreach ($products as &$product) { $product['quantity'] = (int) $product['quantity']; $product['unit_price'] = (float) $product['unit_price']; } // Translate descriptions if they contain Chinese characters $products = collect($products)->map(function ($product) { if (isset($product['description']) && preg_match("/\p{Han}+/u", $product['description'])) { $kvp = KeyValuePair::where('key', KVPKey::CLAUDE_PROMPT_PREFIX . "TRANSLATE_1")->first(); $userPrompt = ''; if($kvp){ $templateFromDb = $kvp->value; $productsMetadataJson = json_encode($product['description'], JSON_UNESCAPED_UNICODE); $userPrompt = str_replace('{{productDescription}}', $productsMetadataJson, $templateFromDb); } else{ throw new ResourceNotFoundException('AI Setup incomplete!'); } // $result = $this->createsChatGPTResponse->execute($userPrompt); $result = $this->createsClaudeResponse->execute($userPrompt); $content = $result['choices'][0]['message']['content'] ?? null; // 1. Try raw JSON $decoded = json_decode($content, true); if (json_last_error() === JSON_ERROR_NONE) { $json = $content; } // 2. Fallback to fenced ```json block elseif (preg_match('/```json\s*(\[[\s\S]*?\]|\{[\s\S]*?\})\s*```/i', $content, $matches)) { $json = $matches[1]; } else { Log::warning('No JSON found in AI service response', ['content' => $content]); return; } $translations = json_decode($json, true); if($translations){ $translatedText = $translations[0]; $product['description'] = $translatedText; } } return $product; })->toArray(); if (is_array($productsMetadata) && count($productsMetadata) > 0) { $kvp = KeyValuePair::where('key', KVPKey::CLAUDE_PROMPT_PREFIX . "EXTRACT_1")->first(); $prompt = ''; if($kvp){ $templateFromDb = $kvp->value; $productsMetadataJson = json_encode($productsMetadata, JSON_UNESCAPED_UNICODE); $prompt = str_replace('{{productsMetadata}}', $productsMetadataJson, $templateFromDb); } else{ throw new ResourceNotFoundException('AI Setup incomplete!'); } // $result = $this->createsChatGPTResponse->execute($prompt); $result = $this->createsClaudeResponse->execute($prompt); $content = $result['choices'][0]['message']['content'] ?? null; $freight = 0; $preferential = 0; if ($content) { if (preg_match('/```json\s*(\{.*?\})\s*```/s', $content, $matches)) { //If wrapped in ```json, extract it $json = $matches[1]; } else { $json = trim($content); } $fees = json_decode($json); if (json_last_error() === JSON_ERROR_NONE) { $freight = $fees->Freight ?? 0; $preferential = $fees->Preferential ?? 0; } else { Log::error('JSON decode failed', [ 'error' => json_last_error_msg(), 'content' => $content ]); } } // Add Freight as a separate product if($freight){ $products[] = [ 'stockCode' => '', 'description' => 'First-Mile Delivery', 'quantity' => 1, 'unit_price' => $freight, ]; } // Add Preferential as a separate product (as negative price if needed) if($preferential){ $products[] = [ 'stockCode' => '', 'description' => 'Discount', 'quantity' => 1, 'unit_price' => $preferential, ]; } } if($bookingId){ /** @var Booking $booking */ $booking = $this->fetchesBooking->execute(['id' => $bookingId]); $billNumber = $this->generatesTransactionBillNumber->execute('PO-'); $total = collect($products)->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, $products); $transaction = $this->createPurchaseOrderTransactionProcessor->execute($booking, $object); } return $products; } }