Implement Google Price Insights with update functionality
Major rewrite using correct Google Merchant API: - Use price_insights_product_view table (correct API endpoint) - Fetch suggested_price and predicted performance changes - Show predicted impact on impressions, clicks, conversions New features: - Individual "Update" button per product - Bulk update with checkbox selection - Pagination (50 products per page) - Sort by potential gain (highest first) Price handling: - Always use tax-inclusive prices for comparison with Google - Convert back to store format when saving (handles tax-exclusive stores) - Set as sale price when updating UI improvements: - Color-coded gain/loss values - Color-coded predicted changes - Summary stats showing products that can increase/decrease - Total potential gain calculation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,8 @@ class Informatiq_SP_Admin {
|
||||
add_action( 'wp_ajax_informatiq_sp_test_connection', array( $this, 'handle_test_connection' ) );
|
||||
add_action( 'wp_ajax_informatiq_sp_revoke_auth', array( $this, 'handle_revoke_auth' ) );
|
||||
add_action( 'wp_ajax_informatiq_sp_compare_products', array( $this, 'handle_compare_products' ) );
|
||||
add_action( 'wp_ajax_informatiq_sp_update_price', array( $this, 'handle_update_price' ) );
|
||||
add_action( 'wp_ajax_informatiq_sp_bulk_update_prices', array( $this, 'handle_bulk_update_prices' ) );
|
||||
|
||||
// Enqueue admin assets.
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
|
||||
@@ -601,20 +603,42 @@ class Informatiq_SP_Admin {
|
||||
|
||||
<div id="informatiq-sp-comparison-results" style="display: none;">
|
||||
<div id="informatiq-sp-comparison-summary" class="notice notice-info" style="margin: 10px 0; padding: 10px;"></div>
|
||||
<table class="widefat striped">
|
||||
|
||||
<p style="margin: 10px 0;">
|
||||
<button type="button" class="button button-primary" id="informatiq-sp-bulk-update" disabled>
|
||||
<?php esc_html_e( 'Bulk Update Selected', 'informatiq-smart-pricing' ); ?>
|
||||
</button>
|
||||
<label style="margin-left: 15px;">
|
||||
<input type="checkbox" id="informatiq-sp-select-all">
|
||||
<?php esc_html_e( 'Select all with suggestions', 'informatiq-smart-pricing' ); ?>
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<table class="widefat striped" id="informatiq-sp-comparison-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30px;"><input type="checkbox" id="informatiq-sp-select-all-header"></th>
|
||||
<th><?php esc_html_e( 'Product', 'informatiq-smart-pricing' ); ?></th>
|
||||
<th><?php esc_html_e( 'Your Price', 'informatiq-smart-pricing' ); ?></th>
|
||||
<th><?php esc_html_e( 'Competitor', 'informatiq-smart-pricing' ); ?></th>
|
||||
<th><?php esc_html_e( 'Recommended', 'informatiq-smart-pricing' ); ?></th>
|
||||
<th><?php esc_html_e( 'Potential +/-', 'informatiq-smart-pricing' ); ?></th>
|
||||
<th><?php esc_html_e( 'Status', 'informatiq-smart-pricing' ); ?></th>
|
||||
<th><?php esc_html_e( 'Suggested', 'informatiq-smart-pricing' ); ?></th>
|
||||
<th><?php esc_html_e( 'Gain/Loss', 'informatiq-smart-pricing' ); ?></th>
|
||||
<th title="<?php esc_attr_e( 'Predicted change in impressions', 'informatiq-smart-pricing' ); ?>">
|
||||
<?php esc_html_e( 'Impr.', 'informatiq-smart-pricing' ); ?>
|
||||
</th>
|
||||
<th title="<?php esc_attr_e( 'Predicted change in clicks', 'informatiq-smart-pricing' ); ?>">
|
||||
<?php esc_html_e( 'Clicks', 'informatiq-smart-pricing' ); ?>
|
||||
</th>
|
||||
<th title="<?php esc_attr_e( 'Predicted change in conversions', 'informatiq-smart-pricing' ); ?>">
|
||||
<?php esc_html_e( 'Conv.', 'informatiq-smart-pricing' ); ?>
|
||||
</th>
|
||||
<th><?php esc_html_e( 'Action', 'informatiq-smart-pricing' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="informatiq-sp-comparison-tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="informatiq-sp-pagination" style="margin-top: 15px; text-align: center;"></div>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<p class="description" style="color: #d63638;">
|
||||
@@ -830,40 +854,15 @@ class Informatiq_SP_Admin {
|
||||
$this->logger
|
||||
);
|
||||
|
||||
// Get benchmark prices from Price Competitiveness Report.
|
||||
$benchmark_data = $google_api->get_all_benchmark_prices();
|
||||
$this->logger->info( sprintf( 'Loaded %d benchmark prices from Google', count( $benchmark_data ) ) );
|
||||
// Get price insights from Google (suggested prices + predicted impact).
|
||||
$price_insights = $google_api->get_price_insights();
|
||||
$this->logger->info( sprintf( 'Loaded %d price insights from Google', count( $price_insights ) ) );
|
||||
|
||||
// Also get all products for matching purposes.
|
||||
$google_products_raw = $google_api->get_all_products();
|
||||
$google_products = array();
|
||||
|
||||
foreach ( $google_products_raw as $gp ) {
|
||||
// Index by offerId (top-level field).
|
||||
if ( ! empty( $gp['offerId'] ) ) {
|
||||
$google_products['offer_' . $gp['offerId']] = $gp;
|
||||
}
|
||||
|
||||
// Index by GTIN - check attributes.gtin which is an ARRAY.
|
||||
if ( ! empty( $gp['attributes']['gtin'] ) && is_array( $gp['attributes']['gtin'] ) ) {
|
||||
foreach ( $gp['attributes']['gtin'] as $gtin_value ) {
|
||||
$google_products['gtin_' . $gtin_value] = $gp;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check attributes.gtins (alternative field name).
|
||||
if ( ! empty( $gp['attributes']['gtins'] ) && is_array( $gp['attributes']['gtins'] ) ) {
|
||||
foreach ( $gp['attributes']['gtins'] as $gtin_value ) {
|
||||
$google_products['gtin_' . $gtin_value] = $gp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get WooCommerce in-stock products (limit to 50 for performance).
|
||||
// Get all WooCommerce in-stock products (no limit - load all).
|
||||
$wc_products = wc_get_products( array(
|
||||
'status' => 'publish',
|
||||
'stock_status' => 'instock',
|
||||
'limit' => 50,
|
||||
'limit' => -1,
|
||||
'return' => 'objects',
|
||||
'type' => array( 'simple', 'variable' ),
|
||||
) );
|
||||
@@ -876,115 +875,66 @@ class Informatiq_SP_Admin {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get local price (sale price priority, then regular).
|
||||
// Always calculate tax-inclusive price for comparison with Google (which is always tax-inclusive).
|
||||
$product_id = $product->get_id();
|
||||
|
||||
// Get local price (tax-inclusive for comparison with Google).
|
||||
$sale_price = $product->get_sale_price();
|
||||
$regular_price = $product->get_regular_price();
|
||||
$base_price = ! empty( $sale_price ) ? $sale_price : $regular_price;
|
||||
$price_type = ! empty( $sale_price ) ? 'sale' : 'regular';
|
||||
|
||||
// Get price including tax for fair comparison with Google.
|
||||
$local_price = null;
|
||||
$local_price_incl_tax = null;
|
||||
if ( $base_price ) {
|
||||
$local_price = wc_get_price_including_tax( $product, array( 'price' => $base_price ) );
|
||||
$local_price_incl_tax = wc_get_price_including_tax( $product, array( 'price' => $base_price ) );
|
||||
}
|
||||
|
||||
// Try to find matching Google product and benchmark.
|
||||
$google_product = null;
|
||||
$benchmark_info = null;
|
||||
$match_type = '';
|
||||
$product_id = $product->get_id();
|
||||
|
||||
// Try offerId match (using WooCommerce product ID).
|
||||
if ( isset( $google_products['offer_' . $product_id] ) ) {
|
||||
$google_product = $google_products['offer_' . $product_id];
|
||||
$match_type = 'product_id';
|
||||
}
|
||||
// Try offerId match with SKU.
|
||||
elseif ( isset( $google_products['offer_' . $sku] ) ) {
|
||||
$google_product = $google_products['offer_' . $sku];
|
||||
$match_type = 'offerId';
|
||||
}
|
||||
// Try GTIN match (when SKU is a barcode).
|
||||
elseif ( isset( $google_products['gtin_' . $sku] ) ) {
|
||||
$google_product = $google_products['gtin_' . $sku];
|
||||
$match_type = 'gtin';
|
||||
// Try to find matching price insight by WooCommerce product ID (which is Google's offerId).
|
||||
$insight = null;
|
||||
if ( isset( $price_insights[ 'offer_' . $product_id ] ) ) {
|
||||
$insight = $price_insights[ 'offer_' . $product_id ];
|
||||
}
|
||||
|
||||
// Get benchmark data (competitor price).
|
||||
if ( isset( $benchmark_data['offer_' . $product_id] ) ) {
|
||||
$benchmark_info = $benchmark_data['offer_' . $product_id];
|
||||
} elseif ( isset( $benchmark_data['offer_' . $sku] ) ) {
|
||||
$benchmark_info = $benchmark_data['offer_' . $sku];
|
||||
} elseif ( isset( $benchmark_data['gtin_' . $sku] ) ) {
|
||||
$benchmark_info = $benchmark_data['gtin_' . $sku];
|
||||
}
|
||||
// Calculate potential gain and determine if update is beneficial.
|
||||
$suggested_price = $insight['suggested_price'] ?? null;
|
||||
$potential_gain = null;
|
||||
$should_update = false;
|
||||
|
||||
// Get Google's own price from product data.
|
||||
$google_own_price = null;
|
||||
if ( $google_product ) {
|
||||
if ( isset( $google_product['attributes']['price']['amountMicros'] ) ) {
|
||||
$google_own_price = (float) $google_product['attributes']['price']['amountMicros'] / 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
// Get competitor benchmark price.
|
||||
$benchmark_price = $benchmark_info['benchmark_price'] ?? null;
|
||||
|
||||
// Calculate recommended price (slightly below competitor).
|
||||
$recommended_price = null;
|
||||
$potential_gain = null;
|
||||
$price_status = 'unknown';
|
||||
|
||||
if ( $benchmark_price && $local_price ) {
|
||||
// Recommended: 20 cents below competitor (or 0.5% whichever is less).
|
||||
$offset = min( 0.20, $benchmark_price * 0.005 );
|
||||
$recommended_price = round( $benchmark_price - $offset, 2 );
|
||||
|
||||
// Calculate potential gain per unit.
|
||||
$potential_gain = round( $recommended_price - $local_price, 2 );
|
||||
|
||||
// Determine status.
|
||||
if ( $local_price < $benchmark_price * 0.98 ) {
|
||||
$price_status = 'cheaper'; // More than 2% cheaper - could increase.
|
||||
} elseif ( $local_price <= $benchmark_price ) {
|
||||
$price_status = 'competitive'; // Within 2% below - good.
|
||||
} else {
|
||||
$price_status = 'expensive'; // Above competitor.
|
||||
}
|
||||
if ( $suggested_price && $local_price_incl_tax ) {
|
||||
$potential_gain = round( $suggested_price - $local_price_incl_tax, 2 );
|
||||
// Suggest update if Google recommends a different price.
|
||||
$should_update = abs( $potential_gain ) > 0.05;
|
||||
}
|
||||
|
||||
$comparison[] = array(
|
||||
'id' => $product_id,
|
||||
'name' => $product->get_name(),
|
||||
'sku' => $sku,
|
||||
'local_price' => $local_price ? (float) $local_price : null,
|
||||
'price_type' => $price_type,
|
||||
'google_own_price' => $google_own_price,
|
||||
'benchmark_price' => $benchmark_price,
|
||||
'recommended_price' => $recommended_price,
|
||||
'potential_gain' => $potential_gain,
|
||||
'price_status' => $price_status,
|
||||
'match_type' => $match_type,
|
||||
'found' => ! empty( $google_product ),
|
||||
'has_benchmark' => ! empty( $benchmark_price ),
|
||||
'google_offer' => $google_product ? ( $google_product['offerId'] ?? '' ) : '',
|
||||
'id' => $product_id,
|
||||
'name' => $product->get_name(),
|
||||
'sku' => $sku,
|
||||
'local_price' => $local_price_incl_tax ? round( $local_price_incl_tax, 2 ) : null,
|
||||
'price_type' => $price_type,
|
||||
'google_price' => $insight['google_price'] ?? null,
|
||||
'suggested_price' => $suggested_price,
|
||||
'potential_gain' => $potential_gain,
|
||||
'predicted_impressions_change' => $insight['predicted_impressions_change'] ?? null,
|
||||
'predicted_clicks_change' => $insight['predicted_clicks_change'] ?? null,
|
||||
'predicted_conversions_change' => $insight['predicted_conversions_change'] ?? null,
|
||||
'has_insight' => ! empty( $insight ),
|
||||
'should_update' => $should_update,
|
||||
);
|
||||
}
|
||||
|
||||
// Get sample of indexed keys for debugging.
|
||||
$sample_keys = array_slice( array_keys( $google_products ), 0, 10 );
|
||||
// Sort by potential gain (highest first).
|
||||
usort( $comparison, function( $a, $b ) {
|
||||
$gain_a = $a['potential_gain'] ?? 0;
|
||||
$gain_b = $b['potential_gain'] ?? 0;
|
||||
return $gain_b <=> $gain_a;
|
||||
} );
|
||||
|
||||
wp_send_json_success( array(
|
||||
'products' => $comparison,
|
||||
'google_count' => count( $google_products_raw ),
|
||||
'wc_count' => count( $wc_products ),
|
||||
'currency' => get_woocommerce_currency_symbol(),
|
||||
'debug' => array(
|
||||
'sample_google_keys' => $sample_keys,
|
||||
'sample_google_product' => ! empty( $google_products_raw[0] ) ? $google_products_raw[0] : null,
|
||||
'index_count' => count( $google_products ),
|
||||
),
|
||||
'products' => $comparison,
|
||||
'insights_count' => count( $price_insights ),
|
||||
'wc_count' => count( $wc_products ),
|
||||
'currency' => get_woocommerce_currency_symbol(),
|
||||
'currency_code' => get_woocommerce_currency(),
|
||||
) );
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
@@ -992,6 +942,147 @@ class Informatiq_SP_Admin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle individual price update AJAX request.
|
||||
*/
|
||||
public function handle_update_price() {
|
||||
check_ajax_referer( 'informatiq_sp_admin', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
||||
}
|
||||
|
||||
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0;
|
||||
$new_price = isset( $_POST['new_price'] ) ? floatval( $_POST['new_price'] ) : 0;
|
||||
|
||||
if ( ! $product_id || $new_price <= 0 ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Invalid product ID or price.', 'informatiq-smart-pricing' ) ) );
|
||||
}
|
||||
|
||||
$product = wc_get_product( $product_id );
|
||||
if ( ! $product ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Product not found.', 'informatiq-smart-pricing' ) ) );
|
||||
}
|
||||
|
||||
// Convert tax-inclusive price to the format WooCommerce expects.
|
||||
$price_to_set = $new_price;
|
||||
if ( ! wc_prices_include_tax() ) {
|
||||
// Store prices exclude tax, so we need to reverse-calculate.
|
||||
$tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
|
||||
if ( ! empty( $tax_rates ) ) {
|
||||
$tax_rate = 0;
|
||||
foreach ( $tax_rates as $rate ) {
|
||||
$tax_rate += (float) $rate['rate'];
|
||||
}
|
||||
$price_to_set = $new_price / ( 1 + ( $tax_rate / 100 ) );
|
||||
}
|
||||
}
|
||||
|
||||
$price_to_set = round( $price_to_set, wc_get_price_decimals() );
|
||||
|
||||
// Store old price for logging.
|
||||
$old_price = $product->get_sale_price() ?: $product->get_regular_price();
|
||||
|
||||
// Set as sale price (since we're adjusting to be competitive).
|
||||
$product->set_sale_price( $price_to_set );
|
||||
$product->save();
|
||||
|
||||
// Log the update.
|
||||
$this->logger->log_price_update(
|
||||
$product_id,
|
||||
$product->get_name(),
|
||||
$old_price,
|
||||
$price_to_set,
|
||||
$new_price,
|
||||
'Manual update to Google suggested price'
|
||||
);
|
||||
|
||||
wp_send_json_success( array(
|
||||
'message' => sprintf(
|
||||
__( 'Price updated for %s: %s', 'informatiq-smart-pricing' ),
|
||||
$product->get_name(),
|
||||
wc_price( $price_to_set )
|
||||
),
|
||||
'new_price' => $price_to_set,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bulk price update AJAX request.
|
||||
*/
|
||||
public function handle_bulk_update_prices() {
|
||||
check_ajax_referer( 'informatiq_sp_admin', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
||||
}
|
||||
|
||||
$updates = isset( $_POST['updates'] ) ? $_POST['updates'] : array();
|
||||
|
||||
if ( empty( $updates ) || ! is_array( $updates ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'No updates provided.', 'informatiq-smart-pricing' ) ) );
|
||||
}
|
||||
|
||||
$updated = 0;
|
||||
$errors = 0;
|
||||
|
||||
foreach ( $updates as $update ) {
|
||||
$product_id = absint( $update['product_id'] ?? 0 );
|
||||
$new_price = floatval( $update['new_price'] ?? 0 );
|
||||
|
||||
if ( ! $product_id || $new_price <= 0 ) {
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$product = wc_get_product( $product_id );
|
||||
if ( ! $product ) {
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert tax-inclusive price to the format WooCommerce expects.
|
||||
$price_to_set = $new_price;
|
||||
if ( ! wc_prices_include_tax() ) {
|
||||
$tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
|
||||
if ( ! empty( $tax_rates ) ) {
|
||||
$tax_rate = 0;
|
||||
foreach ( $tax_rates as $rate ) {
|
||||
$tax_rate += (float) $rate['rate'];
|
||||
}
|
||||
$price_to_set = $new_price / ( 1 + ( $tax_rate / 100 ) );
|
||||
}
|
||||
}
|
||||
|
||||
$price_to_set = round( $price_to_set, wc_get_price_decimals() );
|
||||
$old_price = $product->get_sale_price() ?: $product->get_regular_price();
|
||||
|
||||
$product->set_sale_price( $price_to_set );
|
||||
$product->save();
|
||||
|
||||
$this->logger->log_price_update(
|
||||
$product_id,
|
||||
$product->get_name(),
|
||||
$old_price,
|
||||
$price_to_set,
|
||||
$new_price,
|
||||
'Bulk update to Google suggested price'
|
||||
);
|
||||
|
||||
$updated++;
|
||||
}
|
||||
|
||||
wp_send_json_success( array(
|
||||
'message' => sprintf(
|
||||
__( 'Bulk update complete: %d updated, %d errors.', 'informatiq-smart-pricing' ),
|
||||
$updated,
|
||||
$errors
|
||||
),
|
||||
'updated' => $updated,
|
||||
'errors' => $errors,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin assets.
|
||||
*
|
||||
|
||||
@@ -2,20 +2,14 @@
|
||||
* Admin JavaScript for Informatiq Smart Pricing
|
||||
*/
|
||||
|
||||
console.log('Informatiq Smart Pricing: Script loaded');
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
console.log('Informatiq Smart Pricing: IIFE executed, jQuery available:', typeof $ !== 'undefined');
|
||||
|
||||
var InformatiqSP = {
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
init: function() {
|
||||
console.log('Informatiq Smart Pricing: init() called');
|
||||
console.log('informatiqSP object:', typeof informatiqSP !== 'undefined' ? informatiqSP : 'NOT DEFINED');
|
||||
this.bindEvents();
|
||||
},
|
||||
|
||||
@@ -23,17 +17,10 @@ console.log('Informatiq Smart Pricing: Script loaded');
|
||||
* Bind event handlers
|
||||
*/
|
||||
bindEvents: function() {
|
||||
console.log('Informatiq Smart Pricing: bindEvents() called');
|
||||
|
||||
var $compareBtn = $('#informatiq-sp-compare-products');
|
||||
console.log('Compare products button found:', $compareBtn.length > 0);
|
||||
|
||||
$('#informatiq-sp-manual-sync').on('click', this.handleManualSync);
|
||||
$('#informatiq-sp-test-connection').on('click', this.handleTestConnection);
|
||||
$('#informatiq-sp-revoke-auth').on('click', this.handleRevokeAuth);
|
||||
$compareBtn.on('click', this.handleCompareProducts);
|
||||
|
||||
console.log('Informatiq Smart Pricing: All event handlers bound');
|
||||
$('#informatiq-sp-compare-products').on('click', this.handleCompareProducts);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -195,167 +182,50 @@ console.log('Informatiq Smart Pricing: Script loaded');
|
||||
});
|
||||
},
|
||||
|
||||
// Store comparison data for pagination and bulk updates.
|
||||
comparisonData: null,
|
||||
currentPage: 1,
|
||||
perPage: 50,
|
||||
|
||||
/**
|
||||
* Handle compare products button click
|
||||
*/
|
||||
handleCompareProducts: function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
console.log('=== COMPARE PRODUCTS CLICKED ===');
|
||||
|
||||
var self = InformatiqSP;
|
||||
var $button = $(this);
|
||||
var $spinner = $button.next('.spinner');
|
||||
var $results = $('#informatiq-sp-comparison-results');
|
||||
var $tbody = $('#informatiq-sp-comparison-tbody');
|
||||
var $status = $('#informatiq-sp-sync-status');
|
||||
|
||||
console.log('Button:', $button.length);
|
||||
console.log('Spinner:', $spinner.length);
|
||||
console.log('Results container:', $results.length);
|
||||
console.log('informatiqSP:', informatiqSP);
|
||||
|
||||
// Show loading status
|
||||
$status
|
||||
.removeClass('notice-success notice-error')
|
||||
.addClass('notice-info')
|
||||
.html('<p><strong>Loading product comparison...</strong> This may take a moment.</p>')
|
||||
.html('<p><strong>Loading price insights from Google...</strong> This may take a moment.</p>')
|
||||
.show();
|
||||
|
||||
// Disable button and show spinner
|
||||
$button.prop('disabled', true);
|
||||
$spinner.addClass('is-active');
|
||||
|
||||
console.log('Making AJAX request to:', informatiqSP.ajaxUrl);
|
||||
console.log('With nonce:', informatiqSP.nonce);
|
||||
|
||||
// Make AJAX request
|
||||
$.ajax({
|
||||
url: informatiqSP.ajaxUrl,
|
||||
type: 'POST',
|
||||
timeout: 120000, // 2 minute timeout
|
||||
timeout: 180000, // 3 minute timeout
|
||||
data: {
|
||||
action: 'informatiq_sp_compare_products',
|
||||
nonce: informatiqSP.nonce
|
||||
},
|
||||
beforeSend: function(xhr) {
|
||||
console.log('AJAX beforeSend - request starting');
|
||||
},
|
||||
success: function(response) {
|
||||
console.log('AJAX success callback');
|
||||
console.log('AJAX response:', response);
|
||||
|
||||
// Log debug info if available.
|
||||
if (response.data && response.data.debug) {
|
||||
console.log('=== DEBUG INFO ===');
|
||||
console.log('Sample Google product:', response.data.debug.sample_google_product);
|
||||
console.log('Sample indexed keys:', response.data.debug.sample_google_keys);
|
||||
console.log('Total indexed entries:', response.data.debug.index_count);
|
||||
console.log('==================');
|
||||
}
|
||||
|
||||
console.log('Price insights response:', response);
|
||||
$status.hide();
|
||||
|
||||
if (response.success) {
|
||||
var data = response.data;
|
||||
var html = '';
|
||||
|
||||
// Stats counters.
|
||||
var stats = {
|
||||
total: 0,
|
||||
withBenchmark: 0,
|
||||
cheaper: 0,
|
||||
competitive: 0,
|
||||
expensive: 0,
|
||||
totalPotentialGain: 0
|
||||
};
|
||||
|
||||
if (data.products.length === 0) {
|
||||
html = '<tr><td colspan="6">No in-stock products with SKU found.</td></tr>';
|
||||
} else {
|
||||
$.each(data.products, function(i, product) {
|
||||
stats.total++;
|
||||
|
||||
var localPrice = product.local_price ? data.currency + parseFloat(product.local_price).toFixed(2) : '-';
|
||||
var benchmarkPrice = product.benchmark_price ? data.currency + parseFloat(product.benchmark_price).toFixed(2) : '-';
|
||||
var recommendedPrice = product.recommended_price ? data.currency + parseFloat(product.recommended_price).toFixed(2) : '-';
|
||||
var potentialGain = '-';
|
||||
var priceLabel = product.price_type === 'sale' ? ' <small>(sale)</small>' : '';
|
||||
|
||||
// Status and colors.
|
||||
var statusText = '-';
|
||||
var statusStyle = '';
|
||||
var localPriceStyle = '';
|
||||
var gainStyle = '';
|
||||
|
||||
if (product.has_benchmark) {
|
||||
stats.withBenchmark++;
|
||||
|
||||
if (product.price_status === 'cheaper') {
|
||||
stats.cheaper++;
|
||||
statusText = 'Cheaper';
|
||||
statusStyle = 'color: #00a32a; font-weight: bold;';
|
||||
localPriceStyle = 'color: #00a32a;';
|
||||
} else if (product.price_status === 'competitive') {
|
||||
stats.competitive++;
|
||||
statusText = 'Competitive';
|
||||
statusStyle = 'color: #2271b1; font-weight: bold;';
|
||||
localPriceStyle = 'color: #2271b1;';
|
||||
} else if (product.price_status === 'expensive') {
|
||||
stats.expensive++;
|
||||
statusText = 'Expensive';
|
||||
statusStyle = 'color: #d63638; font-weight: bold;';
|
||||
localPriceStyle = 'color: #d63638;';
|
||||
}
|
||||
|
||||
if (product.potential_gain !== null) {
|
||||
var gain = parseFloat(product.potential_gain);
|
||||
stats.totalPotentialGain += gain;
|
||||
if (gain > 0) {
|
||||
potentialGain = '+' + data.currency + gain.toFixed(2);
|
||||
gainStyle = 'color: #00a32a; font-weight: bold;';
|
||||
} else if (gain < 0) {
|
||||
potentialGain = '-' + data.currency + Math.abs(gain).toFixed(2);
|
||||
gainStyle = 'color: #d63638;';
|
||||
} else {
|
||||
potentialGain = data.currency + '0.00';
|
||||
}
|
||||
}
|
||||
} else if (product.found) {
|
||||
statusText = 'No benchmark';
|
||||
statusStyle = 'color: #dba617;';
|
||||
} else {
|
||||
statusText = 'Not in Google';
|
||||
statusStyle = 'color: #888;';
|
||||
}
|
||||
|
||||
html += '<tr>';
|
||||
html += '<td><a href="post.php?post=' + product.id + '&action=edit" title="SKU: ' + product.sku + '">' + product.name + '</a></td>';
|
||||
html += '<td style="' + localPriceStyle + '">' + localPrice + priceLabel + '</td>';
|
||||
html += '<td>' + benchmarkPrice + '</td>';
|
||||
html += '<td>' + recommendedPrice + '</td>';
|
||||
html += '<td style="' + gainStyle + '">' + potentialGain + '</td>';
|
||||
html += '<td style="' + statusStyle + '">' + statusText + '</td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
}
|
||||
|
||||
$tbody.html(html);
|
||||
self.comparisonData = response.data;
|
||||
self.currentPage = 1;
|
||||
self.renderComparison();
|
||||
$results.show();
|
||||
|
||||
// Show summary with statistics.
|
||||
var $summary = $('#informatiq-sp-comparison-summary');
|
||||
var summaryHtml = '<strong>Summary:</strong> ';
|
||||
summaryHtml += stats.withBenchmark + ' of ' + stats.total + ' products have competitor data. ';
|
||||
summaryHtml += '<span style="color:#00a32a;">' + stats.cheaper + ' cheaper</span>, ';
|
||||
summaryHtml += '<span style="color:#2271b1;">' + stats.competitive + ' competitive</span>, ';
|
||||
summaryHtml += '<span style="color:#d63638;">' + stats.expensive + ' expensive</span>. ';
|
||||
if (stats.totalPotentialGain > 0) {
|
||||
summaryHtml += '<br><strong style="color:#00a32a;">Total potential gain if optimized: +' + data.currency + stats.totalPotentialGain.toFixed(2) + ' per sale</strong>';
|
||||
} else if (stats.totalPotentialGain < 0) {
|
||||
summaryHtml += '<br><strong>Current margin vs recommended: ' + data.currency + stats.totalPotentialGain.toFixed(2) + '</strong>';
|
||||
}
|
||||
$summary.html(summaryHtml).show();
|
||||
|
||||
} else {
|
||||
$status
|
||||
.removeClass('notice-info notice-success')
|
||||
@@ -365,20 +235,275 @@ console.log('Informatiq Smart Pricing: Script loaded');
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
console.error('AJAX error:', textStatus, errorThrown, jqXHR.responseText);
|
||||
console.error('AJAX error:', textStatus, errorThrown);
|
||||
$status
|
||||
.removeClass('notice-info notice-success')
|
||||
.addClass('notice-error')
|
||||
.html('<p>Error: ' + errorThrown + ' - ' + textStatus + '</p>')
|
||||
.html('<p>Error: ' + errorThrown + '</p>')
|
||||
.show();
|
||||
},
|
||||
complete: function() {
|
||||
console.log('AJAX request complete');
|
||||
$button.prop('disabled', false);
|
||||
$spinner.removeClass('is-active');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render comparison table with pagination
|
||||
*/
|
||||
renderComparison: function() {
|
||||
var self = this;
|
||||
var data = this.comparisonData;
|
||||
var $tbody = $('#informatiq-sp-comparison-tbody');
|
||||
var $summary = $('#informatiq-sp-comparison-summary');
|
||||
var $pagination = $('#informatiq-sp-pagination');
|
||||
|
||||
if (!data || !data.products) return;
|
||||
|
||||
var products = data.products;
|
||||
var totalPages = Math.ceil(products.length / this.perPage);
|
||||
var start = (this.currentPage - 1) * this.perPage;
|
||||
var end = start + this.perPage;
|
||||
var pageProducts = products.slice(start, end);
|
||||
|
||||
// Stats
|
||||
var stats = {
|
||||
total: products.length,
|
||||
withInsight: 0,
|
||||
canIncrease: 0,
|
||||
shouldDecrease: 0,
|
||||
totalPotentialGain: 0
|
||||
};
|
||||
|
||||
products.forEach(function(p) {
|
||||
if (p.has_insight) {
|
||||
stats.withInsight++;
|
||||
if (p.potential_gain > 0) {
|
||||
stats.canIncrease++;
|
||||
stats.totalPotentialGain += p.potential_gain;
|
||||
} else if (p.potential_gain < 0) {
|
||||
stats.shouldDecrease++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Render summary
|
||||
var summaryHtml = '<strong>Summary:</strong> ' + stats.withInsight + ' of ' + stats.total + ' products have Google price insights. ';
|
||||
summaryHtml += '<span style="color:#00a32a;">' + stats.canIncrease + ' can increase price</span>, ';
|
||||
summaryHtml += '<span style="color:#d63638;">' + stats.shouldDecrease + ' should decrease price</span>. ';
|
||||
if (stats.totalPotentialGain > 0) {
|
||||
summaryHtml += '<br><strong style="color:#00a32a;">Total potential gain: +' + data.currency + stats.totalPotentialGain.toFixed(2) + ' per sale cycle</strong>';
|
||||
}
|
||||
$summary.html(summaryHtml);
|
||||
|
||||
// Render table rows
|
||||
var html = '';
|
||||
if (pageProducts.length === 0) {
|
||||
html = '<tr><td colspan="9">No products found.</td></tr>';
|
||||
} else {
|
||||
pageProducts.forEach(function(product) {
|
||||
var localPrice = product.local_price ? data.currency + parseFloat(product.local_price).toFixed(2) : '-';
|
||||
var suggestedPrice = product.suggested_price ? data.currency + parseFloat(product.suggested_price).toFixed(2) : '-';
|
||||
var priceLabel = product.price_type === 'sale' ? ' <small>(sale)</small>' : '';
|
||||
|
||||
// Gain/loss styling
|
||||
var gainHtml = '-';
|
||||
var gainStyle = '';
|
||||
if (product.potential_gain !== null) {
|
||||
var gain = parseFloat(product.potential_gain);
|
||||
if (gain > 0) {
|
||||
gainHtml = '+' + data.currency + gain.toFixed(2);
|
||||
gainStyle = 'color: #00a32a; font-weight: bold;';
|
||||
} else if (gain < 0) {
|
||||
gainHtml = '-' + data.currency + Math.abs(gain).toFixed(2);
|
||||
gainStyle = 'color: #d63638;';
|
||||
} else {
|
||||
gainHtml = data.currency + '0.00';
|
||||
}
|
||||
}
|
||||
|
||||
// Predicted changes
|
||||
var imprChange = product.predicted_impressions_change !== null
|
||||
? (product.predicted_impressions_change > 0 ? '+' : '') + product.predicted_impressions_change.toFixed(1) + '%'
|
||||
: '-';
|
||||
var clickChange = product.predicted_clicks_change !== null
|
||||
? (product.predicted_clicks_change > 0 ? '+' : '') + product.predicted_clicks_change.toFixed(1) + '%'
|
||||
: '-';
|
||||
var convChange = product.predicted_conversions_change !== null
|
||||
? (product.predicted_conversions_change > 0 ? '+' : '') + product.predicted_conversions_change.toFixed(1) + '%'
|
||||
: '-';
|
||||
|
||||
var imprStyle = product.predicted_impressions_change > 0 ? 'color:#00a32a;' : (product.predicted_impressions_change < 0 ? 'color:#d63638;' : '');
|
||||
var clickStyle = product.predicted_clicks_change > 0 ? 'color:#00a32a;' : (product.predicted_clicks_change < 0 ? 'color:#d63638;' : '');
|
||||
var convStyle = product.predicted_conversions_change > 0 ? 'color:#00a32a;' : (product.predicted_conversions_change < 0 ? 'color:#d63638;' : '');
|
||||
|
||||
// Checkbox and action button
|
||||
var checkbox = product.has_insight && product.should_update
|
||||
? '<input type="checkbox" class="informatiq-sp-select-product" data-product-id="' + product.id + '" data-new-price="' + product.suggested_price + '">'
|
||||
: '';
|
||||
var actionBtn = product.has_insight && product.should_update
|
||||
? '<button type="button" class="button button-small informatiq-sp-update-single" data-product-id="' + product.id + '" data-new-price="' + product.suggested_price + '">Update</button>'
|
||||
: '-';
|
||||
|
||||
html += '<tr>';
|
||||
html += '<td>' + checkbox + '</td>';
|
||||
html += '<td><a href="post.php?post=' + product.id + '&action=edit" title="SKU: ' + product.sku + '">' + self.truncate(product.name, 40) + '</a></td>';
|
||||
html += '<td>' + localPrice + priceLabel + '</td>';
|
||||
html += '<td>' + suggestedPrice + '</td>';
|
||||
html += '<td style="' + gainStyle + '">' + gainHtml + '</td>';
|
||||
html += '<td style="' + imprStyle + '">' + imprChange + '</td>';
|
||||
html += '<td style="' + clickStyle + '">' + clickChange + '</td>';
|
||||
html += '<td style="' + convStyle + '">' + convChange + '</td>';
|
||||
html += '<td>' + actionBtn + '</td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
}
|
||||
|
||||
$tbody.html(html);
|
||||
|
||||
// Render pagination
|
||||
if (totalPages > 1) {
|
||||
var paginationHtml = '<span>Page ' + this.currentPage + ' of ' + totalPages + '</span> ';
|
||||
if (this.currentPage > 1) {
|
||||
paginationHtml += '<button type="button" class="button button-small informatiq-sp-page" data-page="' + (this.currentPage - 1) + '">« Prev</button> ';
|
||||
}
|
||||
if (this.currentPage < totalPages) {
|
||||
paginationHtml += '<button type="button" class="button button-small informatiq-sp-page" data-page="' + (this.currentPage + 1) + '">Next »</button>';
|
||||
}
|
||||
$pagination.html(paginationHtml).show();
|
||||
} else {
|
||||
$pagination.hide();
|
||||
}
|
||||
|
||||
// Bind events for this page
|
||||
self.bindComparisonEvents();
|
||||
},
|
||||
|
||||
/**
|
||||
* Truncate text
|
||||
*/
|
||||
truncate: function(str, len) {
|
||||
if (!str) return '';
|
||||
return str.length > len ? str.substring(0, len) + '...' : str;
|
||||
},
|
||||
|
||||
/**
|
||||
* Bind comparison table events
|
||||
*/
|
||||
bindComparisonEvents: function() {
|
||||
var self = this;
|
||||
|
||||
// Single update buttons
|
||||
$('.informatiq-sp-update-single').off('click').on('click', function() {
|
||||
var $btn = $(this);
|
||||
var productId = $btn.data('product-id');
|
||||
var newPrice = $btn.data('new-price');
|
||||
|
||||
$btn.prop('disabled', true).text('Updating...');
|
||||
|
||||
$.ajax({
|
||||
url: informatiqSP.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'informatiq_sp_update_price',
|
||||
nonce: informatiqSP.nonce,
|
||||
product_id: productId,
|
||||
new_price: newPrice
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$btn.text('Done!').addClass('button-primary');
|
||||
$btn.closest('tr').css('background-color', '#d4edda');
|
||||
} else {
|
||||
alert('Error: ' + response.data.message);
|
||||
$btn.prop('disabled', false).text('Update');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('Request failed');
|
||||
$btn.prop('disabled', false).text('Update');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Pagination
|
||||
$('.informatiq-sp-page').off('click').on('click', function() {
|
||||
self.currentPage = $(this).data('page');
|
||||
self.renderComparison();
|
||||
$('html, body').animate({ scrollTop: $('#informatiq-sp-comparison-results').offset().top - 50 }, 200);
|
||||
});
|
||||
|
||||
// Select all checkboxes
|
||||
$('#informatiq-sp-select-all, #informatiq-sp-select-all-header').off('change').on('change', function() {
|
||||
var checked = $(this).prop('checked');
|
||||
$('.informatiq-sp-select-product').prop('checked', checked);
|
||||
$('#informatiq-sp-select-all, #informatiq-sp-select-all-header').prop('checked', checked);
|
||||
self.updateBulkButton();
|
||||
});
|
||||
|
||||
// Individual checkbox
|
||||
$('.informatiq-sp-select-product').off('change').on('change', function() {
|
||||
self.updateBulkButton();
|
||||
});
|
||||
|
||||
// Bulk update button
|
||||
$('#informatiq-sp-bulk-update').off('click').on('click', function() {
|
||||
self.handleBulkUpdate();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update bulk button state
|
||||
*/
|
||||
updateBulkButton: function() {
|
||||
var count = $('.informatiq-sp-select-product:checked').length;
|
||||
$('#informatiq-sp-bulk-update').prop('disabled', count === 0).text('Bulk Update Selected (' + count + ')');
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle bulk update
|
||||
*/
|
||||
handleBulkUpdate: function() {
|
||||
var updates = [];
|
||||
$('.informatiq-sp-select-product:checked').each(function() {
|
||||
updates.push({
|
||||
product_id: $(this).data('product-id'),
|
||||
new_price: $(this).data('new-price')
|
||||
});
|
||||
});
|
||||
|
||||
if (updates.length === 0) return;
|
||||
|
||||
if (!confirm('Update prices for ' + updates.length + ' products?')) return;
|
||||
|
||||
var $btn = $('#informatiq-sp-bulk-update');
|
||||
$btn.prop('disabled', true).text('Updating...');
|
||||
|
||||
$.ajax({
|
||||
url: informatiqSP.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'informatiq_sp_bulk_update_prices',
|
||||
nonce: informatiqSP.nonce,
|
||||
updates: updates
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert(response.data.message);
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error: ' + response.data.message);
|
||||
$btn.prop('disabled', false).text('Bulk Update Selected');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('Request failed');
|
||||
$btn.prop('disabled', false).text('Bulk Update Selected');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
// Initialize when document is ready
|
||||
|
||||
@@ -457,27 +457,32 @@ class Informatiq_SP_Google_API {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all benchmark prices from Price Competitiveness Report.
|
||||
* Get price insights from Google's price_insights_product_view.
|
||||
*
|
||||
* @return array Associative array of offer_id => benchmark_price.
|
||||
* Returns suggested prices and predicted performance impact.
|
||||
*
|
||||
* @return array Associative array of offer_id => price insight data.
|
||||
*/
|
||||
public function get_all_benchmark_prices() {
|
||||
public function get_price_insights() {
|
||||
try {
|
||||
$endpoint = "/reports/v1beta/accounts/{$this->merchant_id}/reports:search";
|
||||
|
||||
$all_benchmarks = array();
|
||||
$all_insights = array();
|
||||
$page_token = null;
|
||||
|
||||
do {
|
||||
// New Merchant API uses snake_case table names.
|
||||
// Fields from product are prefixed with product_view.
|
||||
$query = array(
|
||||
'query' => "SELECT
|
||||
product_view.offer_id,
|
||||
product_view.gtin,
|
||||
product_view.title,
|
||||
benchmark_price
|
||||
FROM price_competitiveness_product_view",
|
||||
id,
|
||||
offer_id,
|
||||
title,
|
||||
brand,
|
||||
price,
|
||||
suggested_price,
|
||||
predicted_impressions_change_fraction,
|
||||
predicted_clicks_change_fraction,
|
||||
predicted_conversions_change_fraction
|
||||
FROM price_insights_product_view",
|
||||
);
|
||||
|
||||
if ( $page_token ) {
|
||||
@@ -486,53 +491,56 @@ class Informatiq_SP_Google_API {
|
||||
|
||||
$response = $this->api_request( 'POST', $endpoint, $query );
|
||||
|
||||
$this->logger->info( 'Benchmark API response keys: ' . wp_json_encode( array_keys( $response ) ) );
|
||||
if ( ! empty( $response['results'][0] ) ) {
|
||||
$this->logger->info( 'Sample benchmark result: ' . wp_json_encode( $response['results'][0] ) );
|
||||
// Log first result for debugging.
|
||||
if ( empty( $all_insights ) && ! empty( $response['results'][0] ) ) {
|
||||
$this->logger->info( 'Sample price insight: ' . wp_json_encode( $response['results'][0] ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $response['results'] ) ) {
|
||||
foreach ( $response['results'] as $result ) {
|
||||
// Extract data - response nests product fields under productView.
|
||||
$product_view = $result['productView'] ?? array();
|
||||
$offer_id = $product_view['offerId'] ?? ( $product_view['offer_id'] ?? null );
|
||||
$gtin = $product_view['gtin'] ?? null;
|
||||
$title = $product_view['title'] ?? '';
|
||||
$insight = $result['priceInsightsProductView'] ?? $result;
|
||||
|
||||
$benchmark_price = null;
|
||||
// Try different possible response structures for benchmark.
|
||||
if ( isset( $result['benchmarkPrice']['amountMicros'] ) ) {
|
||||
$benchmark_price = (float) $result['benchmarkPrice']['amountMicros'] / 1000000;
|
||||
} elseif ( isset( $result['benchmark_price']['amountMicros'] ) ) {
|
||||
$benchmark_price = (float) $result['benchmark_price']['amountMicros'] / 1000000;
|
||||
} elseif ( isset( $result['priceBenchmark']['priceBenchmarkValue']['amountMicros'] ) ) {
|
||||
$benchmark_price = (float) $result['priceBenchmark']['priceBenchmarkValue']['amountMicros'] / 1000000;
|
||||
$offer_id = $insight['offerId'] ?? ( $insight['offer_id'] ?? null );
|
||||
$product_id = $insight['id'] ?? null;
|
||||
|
||||
// Extract offer_id from product_id if needed (format: lang~country~offerId).
|
||||
if ( ! $offer_id && $product_id ) {
|
||||
$parts = explode( '~', $product_id );
|
||||
if ( count( $parts ) >= 3 ) {
|
||||
$offer_id = $parts[ count( $parts ) - 1 ];
|
||||
}
|
||||
}
|
||||
|
||||
$own_price = null;
|
||||
if ( isset( $product_view['price']['amountMicros'] ) ) {
|
||||
$own_price = (float) $product_view['price']['amountMicros'] / 1000000;
|
||||
} elseif ( isset( $result['price']['amountMicros'] ) ) {
|
||||
$own_price = (float) $result['price']['amountMicros'] / 1000000;
|
||||
$current_price = null;
|
||||
if ( isset( $insight['price']['amountMicros'] ) ) {
|
||||
$current_price = (float) $insight['price']['amountMicros'] / 1000000;
|
||||
}
|
||||
|
||||
if ( $offer_id && $benchmark_price ) {
|
||||
$all_benchmarks[ 'offer_' . $offer_id ] = array(
|
||||
'benchmark_price' => $benchmark_price,
|
||||
'own_price' => $own_price,
|
||||
'title' => $title,
|
||||
'gtin' => $gtin,
|
||||
$suggested_price = null;
|
||||
if ( isset( $insight['suggestedPrice']['amountMicros'] ) ) {
|
||||
$suggested_price = (float) $insight['suggestedPrice']['amountMicros'] / 1000000;
|
||||
}
|
||||
|
||||
if ( $offer_id ) {
|
||||
$data = array(
|
||||
'offer_id' => $offer_id,
|
||||
'product_id' => $product_id,
|
||||
'title' => $insight['title'] ?? '',
|
||||
'brand' => $insight['brand'] ?? '',
|
||||
'google_price' => $current_price,
|
||||
'suggested_price' => $suggested_price,
|
||||
'predicted_impressions_change' => isset( $insight['predictedImpressionsChangeFraction'] )
|
||||
? (float) $insight['predictedImpressionsChangeFraction'] * 100
|
||||
: null,
|
||||
'predicted_clicks_change' => isset( $insight['predictedClicksChangeFraction'] )
|
||||
? (float) $insight['predictedClicksChangeFraction'] * 100
|
||||
: null,
|
||||
'predicted_conversions_change' => isset( $insight['predictedConversionsChangeFraction'] )
|
||||
? (float) $insight['predictedConversionsChangeFraction'] * 100
|
||||
: null,
|
||||
);
|
||||
|
||||
// Also index by GTIN if available.
|
||||
if ( $gtin ) {
|
||||
$all_benchmarks[ 'gtin_' . $gtin ] = array(
|
||||
'benchmark_price' => $benchmark_price,
|
||||
'own_price' => $own_price,
|
||||
'title' => $title,
|
||||
'offer_id' => $offer_id,
|
||||
);
|
||||
}
|
||||
$all_insights[ 'offer_' . $offer_id ] = $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -541,12 +549,12 @@ class Informatiq_SP_Google_API {
|
||||
|
||||
} while ( $page_token );
|
||||
|
||||
$this->logger->info( sprintf( 'Fetched benchmark prices for %d products', count( $all_benchmarks ) ) );
|
||||
$this->logger->info( sprintf( 'Fetched price insights for %d products', count( $all_insights ) ) );
|
||||
|
||||
return $all_benchmarks;
|
||||
return $all_insights;
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
$this->logger->error( 'Error fetching benchmark prices: ' . $e->getMessage() );
|
||||
$this->logger->error( 'Error fetching price insights: ' . $e->getMessage() );
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user