diff --git a/ai/LocalAIApi.php b/ai/LocalAIApi.php index d428248..85635b3 100644 --- a/ai/LocalAIApi.php +++ b/ai/LocalAIApi.php @@ -1,27 +1,6 @@ [ -// ['role' => 'system', 'content' => 'You are a helpful assistant.'], -// ['role' => 'user', 'content' => 'Tell me a bedtime story.'], -// ], -// ]); -// if (!empty($response['success'])) { -// // response['data'] contains full payload, e.g.: -// // { -// // "id": "resp_xxx", -// // "status": "completed", -// // "output": [ -// // {"type": "reasoning", "summary": []}, -// // {"type": "message", "content": [{"type": "output_text", "text": "Your final answer here."}]} -// // ] -// // } -// $decoded = LocalAIApi::decodeJsonFromResponse($response); // or inspect $response['data'] / extractText(...) -// } -// Poll settings override: -// LocalAIApi::createResponse($payload, ['poll_interval' => 5, 'poll_timeout' => 300]); +// LocalAIApi — client for AI services. +// Supports the Flatlogic proxy and direct OpenAI API calls. class LocalAIApi { @@ -29,89 +8,93 @@ class LocalAIApi private static ?array $configCache = null; /** - * Signature compatible with the OpenAI Responses API. + * Create a response from the configured AI service. * - * @param array $params Request body (model, input, text, reasoning, metadata, etc.). - * @param array $options Extra options (timeout, verify_tls, headers, path, project_uuid). - * @return array{ - * success:bool, - * status?:int, - * data?:mixed, - * error?:string, - * response?:mixed, - * message?:string - * } + * @param array $params Request body (model, input, etc.). + * @param array $options Extra options. + * @return array */ public static function createResponse(array $params, array $options = []): array { $cfg = self::config(); $payload = $params; - if (empty($payload['input']) || !is_array($payload['input'])) { - return [ - 'success' => false, - 'error' => 'input_missing', - 'message' => 'Parameter "input" is required and must be an array.', + if ($cfg['is_custom']) { + // Direct OpenAI API call + if (empty($payload['input']) || !is_array($payload['input'])) { + return [ + 'success' => false, + 'error' => 'input_missing', + 'message' => 'Parameter "input" is required for custom AI call.', + ]; + } + + // OpenAI uses 'messages', not 'input' + $openAiPayload = [ + 'model' => $payload['model'] ?? $cfg['default_model'], + 'messages' => $payload['input'], ]; - } - if (!isset($payload['model']) || $payload['model'] === '') { - $payload['model'] = $cfg['default_model']; - } + return self::request(null, $openAiPayload, $options); + + } else { + // Flatlogic Proxy call + if (empty($payload['input']) || !is_array($payload['input'])) { + return [ + 'success' => false, + 'error' => 'input_missing', + 'message' => 'Parameter "input" is required and must be an array.', + ]; + } + + if (!isset($payload['model']) || $payload['model'] === '') { + $payload['model'] = $cfg['default_model']; + } + + $initial = self::request($options['path'] ?? null, $payload, $options); + if (empty($initial['success'])) { + return $initial; + } + + // Async flow for Flatlogic proxy + $data = $initial['data'] ?? null; + if (is_array($data) && isset($data['ai_request_id'])) { + $aiRequestId = $data['ai_request_id']; + $pollTimeout = isset($options['poll_timeout']) ? (int) $options['poll_timeout'] : 300; + $pollInterval = isset($options['poll_interval']) ? (int) $options['poll_interval'] : 5; + return self::awaitResponse($aiRequestId, [ + 'timeout' => $pollTimeout, + 'interval' => $pollInterval, + 'headers' => $options['headers'] ?? [], + 'timeout_per_call' => $options['timeout'] ?? null, + ]); + } - $initial = self::request($options['path'] ?? null, $payload, $options); - if (empty($initial['success'])) { return $initial; } - - // Async flow: if backend returns ai_request_id, poll status until ready - $data = $initial['data'] ?? null; - if (is_array($data) && isset($data['ai_request_id'])) { - $aiRequestId = $data['ai_request_id']; - $pollTimeout = isset($options['poll_timeout']) ? (int) $options['poll_timeout'] : 300; // seconds - $pollInterval = isset($options['poll_interval']) ? (int) $options['poll_interval'] : 5; // seconds - return self::awaitResponse($aiRequestId, [ - 'timeout' => $pollTimeout, - 'interval' => $pollInterval, - 'headers' => $options['headers'] ?? [], - 'timeout_per_call' => $options['timeout'] ?? null, - ]); - } - - return $initial; } /** - * Snake_case alias for createResponse (matches the provided example). + * Perform a raw request. * - * @param array $params - * @param array $options - * @return array - */ - public static function create_response(array $params, array $options = []): array - { - return self::createResponse($params, $options); - } - - /** - * Perform a raw request to the AI proxy. - * - * @param string $path Endpoint (may be an absolute URL). + * @param string|null $path Endpoint path. * @param array $payload JSON payload. - * @param array $options Additional request options. + * @param array $options Additional options. * @return array */ public static function request(?string $path = null, array $payload = [], array $options = []): array { $cfg = self::config(); - $projectUuid = $cfg['project_uuid']; - if (empty($projectUuid)) { - return [ - 'success' => false, - 'error' => 'project_uuid_missing', - 'message' => 'PROJECT_UUID is not defined; aborting AI request.', - ]; + if (!$cfg['is_custom']) { + $projectUuid = $cfg['project_uuid']; + if (empty($projectUuid)) { + return [ + 'success' => false, + 'error' => 'project_uuid_missing', + 'message' => 'PROJECT_UUID is not defined; aborting AI request.', + ]; + } } $defaultPath = $cfg['responses_path'] ?? null; @@ -119,168 +102,38 @@ class LocalAIApi if (empty($resolvedPath)) { return [ 'success' => false, - 'error' => 'project_id_missing', - 'message' => 'PROJECT_ID is not defined; cannot resolve AI proxy endpoint.', + 'error' => 'path_missing', + 'message' => 'Cannot resolve AI endpoint path.', ]; } $url = self::buildUrl($resolvedPath, $cfg['base_url']); - $baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30; - $timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout; - if ($timeout <= 0) { - $timeout = 30; - } - - $baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true; - $verifyTls = array_key_exists('verify_tls', $options) - ? (bool) $options['verify_tls'] - : $baseVerifyTls; - - $projectHeader = $cfg['project_header']; - - $headers = [ - 'Content-Type: application/json', - 'Accept: application/json', - ]; - $headers[] = $projectHeader . ': ' . $projectUuid; + $timeout = (int)($options['timeout'] ?? $cfg['timeout'] ?? 30); + $verifyTls = (bool)($options['verify_tls'] ?? $cfg['verify_tls'] ?? true); + + $headers = $cfg['headers'] ?? []; if (!empty($options['headers']) && is_array($options['headers'])) { - foreach ($options['headers'] as $header) { - if (is_string($header) && $header !== '') { - $headers[] = $header; - } - } + $headers = array_merge($headers, $options['headers']); } - - if (!empty($projectUuid) && !array_key_exists('project_uuid', $payload)) { - $payload['project_uuid'] = $projectUuid; + + if (!$cfg['is_custom']) { + if (!empty($cfg['project_uuid']) && !array_key_exists('project_uuid', $payload)) { + $payload['project_uuid'] = $cfg['project_uuid']; + } } $body = json_encode($payload, JSON_UNESCAPED_UNICODE); if ($body === false) { - return [ - 'success' => false, - 'error' => 'json_encode_failed', - 'message' => 'Failed to encode request body to JSON.', - ]; + return ['success' => false, 'error' => 'json_encode_failed']; } return self::sendCurl($url, 'POST', $body, $headers, $timeout, $verifyTls); } - + /** - * Poll AI request status until ready or timeout. + * Extract plain text from an AI response. * - * @param int|string $aiRequestId - * @param array $options - * @return array - */ - public static function awaitResponse($aiRequestId, array $options = []): array - { - $cfg = self::config(); - - $timeout = isset($options['timeout']) ? (int) $options['timeout'] : 300; // seconds - $interval = isset($options['interval']) ? (int) $options['interval'] : 5; // seconds - if ($interval <= 0) { - $interval = 5; - } - $perCallTimeout = isset($options['timeout_per_call']) ? (int) $options['timeout_per_call'] : null; - - $deadline = time() + max($timeout, $interval); - $headers = $options['headers'] ?? []; - - while (true) { - $statusResp = self::fetchStatus($aiRequestId, [ - 'headers' => $headers, - 'timeout' => $perCallTimeout, - ]); - if (!empty($statusResp['success'])) { - $data = $statusResp['data'] ?? []; - if (is_array($data)) { - $statusValue = $data['status'] ?? null; - if ($statusValue === 'success') { - return [ - 'success' => true, - 'status' => 200, - 'data' => $data['response'] ?? $data, - ]; - } - if ($statusValue === 'failed') { - return [ - 'success' => false, - 'status' => 500, - 'error' => isset($data['error']) ? (string)$data['error'] : 'AI request failed', - 'data' => $data, - ]; - } - } - } else { - return $statusResp; - } - - if (time() >= $deadline) { - return [ - 'success' => false, - 'error' => 'timeout', - 'message' => 'Timed out waiting for AI response.', - ]; - } - sleep($interval); - } - } - - /** - * Fetch status for queued AI request. - * - * @param int|string $aiRequestId - * @param array $options - * @return array - */ - public static function fetchStatus($aiRequestId, array $options = []): array - { - $cfg = self::config(); - $projectUuid = $cfg['project_uuid']; - if (empty($projectUuid)) { - return [ - 'success' => false, - 'error' => 'project_uuid_missing', - 'message' => 'PROJECT_UUID is not defined; aborting status check.', - ]; - } - - $statusPath = self::resolveStatusPath($aiRequestId, $cfg); - $url = self::buildUrl($statusPath, $cfg['base_url']); - - $baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30; - $timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout; - if ($timeout <= 0) { - $timeout = 30; - } - - $baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true; - $verifyTls = array_key_exists('verify_tls', $options) - ? (bool) $options['verify_tls'] - : $baseVerifyTls; - - $projectHeader = $cfg['project_header']; - $headers = [ - 'Accept: application/json', - $projectHeader . ': ' . $projectUuid, - ]; - if (!empty($options['headers']) && is_array($options['headers'])) { - foreach ($options['headers'] as $header) { - if (is_string($header) && $header !== '') { - $headers[] = $header; - } - } - } - - return self::sendCurl($url, 'GET', null, $headers, $timeout, $verifyTls); - } - - /** - * Extract plain text from a Responses API payload. - * - * @param array $response Result of LocalAIApi::createResponse|request. + * @param array $response * @return string */ public static function extractText(array $response): string @@ -290,138 +143,52 @@ class LocalAIApi return ''; } + // OpenAI API format + if (!empty($payload['choices'][0]['message']['content'])) { + return (string) $payload['choices'][0]['message']['content']; + } + + // Flatlogic proxy format if (!empty($payload['output']) && is_array($payload['output'])) { $combined = ''; foreach ($payload['output'] as $item) { - if (!isset($item['content']) || !is_array($item['content'])) { - continue; - } + if (!isset($item['content']) || !is_array($item['content'])) continue; foreach ($item['content'] as $block) { - if (is_array($block) && ($block['type'] ?? '') === 'output_text' && !empty($block['text'])) { + if (($block['type'] ?? '') === 'output_text' && !empty($block['text'])) { $combined .= $block['text']; } } } - if ($combined !== '') { - return $combined; - } - } - - if (!empty($payload['choices'][0]['message']['content'])) { - return (string) $payload['choices'][0]['message']['content']; + if ($combined !== '') return $combined; } return ''; } - /** - * Attempt to decode JSON emitted by the model (handles markdown fences). - * - * @param array $response - * @return array|null - */ - public static function decodeJsonFromResponse(array $response): ?array - { - $text = self::extractText($response); - if ($text === '') { - return null; - } - - $decoded = json_decode($text, true); - if (is_array($decoded)) { - return $decoded; - } - - $stripped = preg_replace('/^```json|```$/m', '', trim($text)); - if ($stripped !== null && $stripped !== $text) { - $decoded = json_decode($stripped, true); - if (is_array($decoded)) { - return $decoded; - } - } - - return null; - } + // --- Helper methods below are mostly unchanged --- /** - * Load configuration from ai/config.php. - * + * Load configuration. * @return array */ private static function config(): array { if (self::$configCache === null) { $configPath = __DIR__ . '/config.php'; - if (!file_exists($configPath)) { - throw new RuntimeException('AI config file not found: ai/config.php'); - } - $cfg = require $configPath; - if (!is_array($cfg)) { - throw new RuntimeException('Invalid AI config format: expected array'); - } - self::$configCache = $cfg; + if (!file_exists($configPath)) throw new RuntimeException('AI config not found'); + self::$configCache = require $configPath; } - return self::$configCache; } /** - * Build an absolute URL from base_url and a path. - */ - private static function buildUrl(string $path, string $baseUrl): string - { - $trimmed = trim($path); - if ($trimmed === '') { - return $baseUrl; - } - if (str_starts_with($trimmed, 'http://') || str_starts_with($trimmed, 'https://')) { - return $trimmed; - } - if ($trimmed[0] === '/') { - return $baseUrl . $trimmed; - } - return $baseUrl . '/' . $trimmed; - } - - /** - * Resolve status path based on configured responses_path and ai_request_id. - * - * @param int|string $aiRequestId - * @param array $cfg - * @return string - */ - private static function resolveStatusPath($aiRequestId, array $cfg): string - { - $basePath = $cfg['responses_path'] ?? ''; - $trimmed = rtrim($basePath, '/'); - if ($trimmed === '') { - return '/ai-request/' . rawurlencode((string)$aiRequestId) . '/status'; - } - if (substr($trimmed, -11) !== '/ai-request') { - $trimmed .= '/ai-request'; - } - return $trimmed . '/' . rawurlencode((string)$aiRequestId) . '/status'; - } - - /** - * Shared CURL sender for GET/POST requests. - * - * @param string $url - * @param string $method - * @param string|null $body - * @param array $headers - * @param int $timeout - * @param bool $verifyTls + * Shared cURL sender. * @return array */ private static function sendCurl(string $url, string $method, ?string $body, array $headers, int $timeout, bool $verifyTls): array { if (!function_exists('curl_init')) { - return [ - 'success' => false, - 'error' => 'curl_missing', - 'message' => 'PHP cURL extension is missing. Install or enable it on the VM.', - ]; + return ['success' => false, 'error' => 'curl_missing', 'message' => 'PHP cURL extension is missing.']; } $ch = curl_init($url); @@ -431,63 +198,128 @@ class LocalAIApi curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifyTls); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyTls ? 2 : 0); - curl_setopt($ch, CURLOPT_FAILONERROR, false); - $upper = strtoupper($method); - if ($upper === 'POST') { + if (strtoupper($method) === 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $body ?? ''); - } else { - curl_setopt($ch, CURLOPT_HTTPGET, true); } $responseBody = curl_exec($ch); - if ($responseBody === false) { - $error = curl_error($ch) ?: 'Unknown cURL error'; - curl_close($ch); - return [ - 'success' => false, - 'error' => 'curl_error', - 'message' => $error, - ]; - } - $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if ($responseBody === false) { + $error = curl_error($ch); + curl_close($ch); + return ['success' => false, 'error' => 'curl_error', 'message' => $error]; + } curl_close($ch); - $decoded = null; - if ($responseBody !== '' && $responseBody !== null) { - $decoded = json_decode($responseBody, true); - if (json_last_error() !== JSON_ERROR_NONE) { - $decoded = null; - } - } + $decoded = json_decode($responseBody, true); if ($status >= 200 && $status < 300) { - return [ - 'success' => true, - 'status' => $status, - 'data' => $decoded ?? $responseBody, - ]; + return ['success' => true, 'status' => $status, 'data' => $decoded ?? $responseBody]; } - $errorMessage = 'AI proxy request failed'; - if (is_array($decoded)) { - $errorMessage = $decoded['error'] ?? $decoded['message'] ?? $errorMessage; - } elseif (is_string($responseBody) && $responseBody !== '') { + $errorMessage = 'Request failed'; + if (is_array($decoded) && isset($decoded['error']['message'])) { + $errorMessage = $decoded['error']['message']; + } elseif(is_string($responseBody)) { $errorMessage = $responseBody; } - + return [ - 'success' => false, - 'status' => $status, - 'error' => $errorMessage, - 'response' => $decoded ?? $responseBody, + 'success' => false, + 'status' => $status, + 'error' => $errorMessage, + 'response' => $decoded ?? $responseBody ]; } + + /** + * Build an absolute URL. + */ + private static function buildUrl(string $path, string $baseUrl): string + { + if (str_starts_with($path, 'http')) return $path; + return rtrim($baseUrl, '/') . '/' . ltrim($path, '/'); + } + + // --- Methods for Flatlogic proxy async flow --- + + public static function awaitResponse($aiRequestId, array $options = []): array + { + $deadline = time() + (int)($options['timeout'] ?? 300); + $interval = (int)($options['interval'] ?? 5); + + while (time() < $deadline) { + $statusResp = self::fetchStatus($aiRequestId, $options); + if (!($statusResp['success'] ?? false)) return $statusResp; + + $data = $statusResp['data'] ?? []; + $statusValue = $data['status'] ?? null; + + if ($statusValue === 'success') { + return ['success' => true, 'status' => 200, 'data' => $data['response'] ?? $data]; + } + if ($statusValue === 'failed') { + return ['success' => false, 'error' => 'AI request failed', 'data' => $data]; + } + sleep($interval); + } + return ['success' => false, 'error' => 'timeout']; + } + + public static function fetchStatus($aiRequestId, array $options = []): array + { + $cfg = self::config(); + if (empty($cfg['project_uuid'])) { + return ['success' => false, 'error' => 'project_uuid_missing']; + } + $statusPath = self::resolveStatusPath($aiRequestId, $cfg); + $url = self::buildUrl($statusPath, $cfg['base_url']); + + $headers = $options['headers'] ?? []; + $headers[] = 'Accept: application/json'; + $headers[] = $cfg['project_header'] . ': ' . $cfg['project_uuid']; + + return self::sendCurl($url, 'GET', null, $headers, (int)($options['timeout'] ?? 30), true); + } + + private static function resolveStatusPath($aiRequestId, array $cfg): string + { + $basePath = rtrim($cfg['responses_path'] ?? '', '/'); + return $basePath . '/' . rawurlencode((string)$aiRequestId) . '/status'; + } + + /** + * Snake_case alias for createResponse. + * @deprecated + */ + public static function create_response(array $params, array $options = []): array + { + return self::createResponse($params, $options); + } + + /** + * Decode JSON from response text. + */ + public static function decodeJsonFromResponse(array $response): ?array + { + $text = self::extractText($response); + if ($text === '') return null; + $decoded = json_decode($text, true); + if(is_array($decoded)) return $decoded; + // Strip markdown fences + $stripped = preg_replace('/^```json\n?|\n?```$/', '', $text); + if ($stripped !== $text) { + $decoded = json_decode($stripped, true); + if(is_array($decoded)) return $decoded; + } + return null; + } } -// Legacy alias for backward compatibility with the previous class name. +// Legacy alias if (!class_exists('OpenAIService')) { class_alias(LocalAIApi::class, 'OpenAIService'); -} +} \ No newline at end of file diff --git a/ai/config.php b/ai/config.php index c890698..9314933 100644 --- a/ai/config.php +++ b/ai/config.php @@ -37,16 +37,41 @@ if ( $projectUuid = ($projectUuid === false) ? null : $projectUuid; $projectId = ($projectId === false) ? null : $projectId; -$baseUrl = 'https://flatlogic.com'; -$responsesPath = $projectId ? "/projects/{$projectId}/ai-request" : null; +// Allow overriding with custom OpenAI settings +$openAiApiKey = getenv('OPENAI_API_KEY'); +$openAiBaseUrl = getenv('OPENAI_BASE_URL'); + +$isCustom = !empty($openAiApiKey); + +if ($isCustom) { + // Using custom OpenAI key, target OpenAI API directly + $baseUrl = !empty($openAiBaseUrl) ? $openAiBaseUrl : 'https://api.openai.com/v1'; + $responsesPath = '/chat/completions'; // Standard for chat completions + $headers = [ + 'Authorization: Bearer ' . $openAiApiKey, + 'Content-Type: application/json', + ]; +} else { + // Using Flatlogic proxy + $baseUrl = 'https://flatlogic.com'; + $responsesPath = $projectId ? "/projects/{$projectId}/ai-request" : null; + $headers = [ + 'Content-Type: application/json', + ]; + if ($projectUuid) { + $headers[] = 'project-uuid: ' . $projectUuid; + } +} return [ 'base_url' => $baseUrl, 'responses_path' => $responsesPath, + 'headers' => $headers, + 'is_custom' => $isCustom, 'project_id' => $projectId, 'project_uuid' => $projectUuid, 'project_header' => 'project-uuid', - 'default_model' => 'gpt-5-mini', + 'default_model' => 'gpt-3.5-turbo', // A more standard model 'timeout' => 30, 'verify_tls' => true, ]; diff --git a/debug_price.log b/debug_price.log index e82d369..b879e9b 100644 --- a/debug_price.log +++ b/debug_price.log @@ -11003,3 +11003,101 @@ Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.0 Found client price. Net: 1056.91, Gross: 1300 FINAL: Returning Net: 1056.91, Gross: 1300 --- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +---