fix: run product sync in background batches to avoid 502 on large catalogs
Manual and scheduled sync previously processed every WooCommerce product inline in a single PHP request/AJAX call, with several sequential BC API calls per product. At real catalog sizes this exceeds PHP's max_execution_time and the proxy's read timeout, surfacing as a 502 Bad Gateway on "Sync Now". Adds WBC_Batch_Sync, which queues the catalog in chunks of 25 via Action Scheduler (bundled with WooCommerce) and processes them asynchronously. The manual sync AJAX call now returns immediately after queuing, and the admin UI polls for progress instead of waiting on one long request. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,102 +46,37 @@ class WBC_Product_Sync {
|
||||
);
|
||||
|
||||
/**
|
||||
* Run the product sync (WooCommerce-first approach)
|
||||
* Process a batch of WooCommerce products by ID (WooCommerce-first approach)
|
||||
*
|
||||
* Instead of pulling all 60k+ items from BC, we iterate WooCommerce
|
||||
* products and query BC for each one by SKU/GTIN. This means ~100
|
||||
* targeted API calls instead of 600+ paginated calls.
|
||||
* products and query BC for each one by reference/GTIN/number. This is
|
||||
* called per-batch by WBC_Batch_Sync so a single request never has to
|
||||
* process the whole catalog (which would exceed PHP/proxy timeouts at
|
||||
* real-world catalog sizes).
|
||||
*
|
||||
* @return array Sync results.
|
||||
* @param int[] $product_ids WooCommerce product IDs to process.
|
||||
* @return array Sync results for this batch.
|
||||
*/
|
||||
public function run_sync() {
|
||||
// Check if sync is enabled
|
||||
if ( get_option( 'wbc_enable_stock_sync', 'yes' ) !== 'yes' && get_option( 'wbc_enable_price_sync', 'yes' ) !== 'yes' ) {
|
||||
WBC_Logger::info( 'ProductSync', 'Sync skipped - both stock and price sync are disabled' );
|
||||
return array(
|
||||
'success' => true,
|
||||
'message' => __( 'Sync skipped - both stock and price sync are disabled.', 'woo-business-central' ),
|
||||
);
|
||||
}
|
||||
public function run_sync_for_ids( array $product_ids ) {
|
||||
foreach ( $product_ids as $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
// Check if credentials are configured
|
||||
if ( ! WBC_OAuth::is_configured() ) {
|
||||
WBC_Logger::error( 'ProductSync', 'Sync failed - API credentials not configured' );
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'Sync failed - API credentials not configured.', 'woo-business-central' ),
|
||||
);
|
||||
}
|
||||
if ( ! $product || empty( $product->get_sku() ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WBC_Logger::info( 'ProductSync', 'Starting product sync (WooCommerce-first)' );
|
||||
|
||||
$start_time = microtime( true );
|
||||
|
||||
// Get all WooCommerce products that have a SKU
|
||||
$products = $this->get_wc_products_with_sku();
|
||||
|
||||
if ( empty( $products ) ) {
|
||||
WBC_Logger::info( 'ProductSync', 'No WooCommerce products with SKU found' );
|
||||
return array(
|
||||
'success' => true,
|
||||
'message' => __( 'No WooCommerce products with SKU found.', 'woo-business-central' ),
|
||||
);
|
||||
}
|
||||
|
||||
WBC_Logger::info( 'ProductSync', 'Found WooCommerce products to sync', array(
|
||||
'count' => count( $products ),
|
||||
) );
|
||||
|
||||
// Process each WooCommerce product
|
||||
foreach ( $products as $product ) {
|
||||
$this->process_wc_product( $product );
|
||||
|
||||
// Small delay between API calls to avoid rate limiting
|
||||
usleep( 50000 ); // 50ms
|
||||
}
|
||||
|
||||
$duration = round( microtime( true ) - $start_time, 2 );
|
||||
|
||||
WBC_Logger::info( 'ProductSync', 'Product sync completed', array(
|
||||
'duration_seconds' => $duration,
|
||||
'total' => $this->results['total'],
|
||||
'success' => $this->results['success'],
|
||||
'failed' => $this->results['failed'],
|
||||
'skipped' => $this->results['skipped'],
|
||||
) );
|
||||
|
||||
return array(
|
||||
'success' => empty( $this->results['errors'] ),
|
||||
'message' => sprintf(
|
||||
/* translators: 1: duration, 2: success count, 3: failed count, 4: skipped count */
|
||||
__( 'Sync completed in %1$s seconds. Success: %2$d, Failed: %3$d, Skipped: %4$d', 'woo-business-central' ),
|
||||
$duration,
|
||||
$this->results['success'],
|
||||
$this->results['failed'],
|
||||
$this->results['skipped']
|
||||
),
|
||||
'results' => $this->results,
|
||||
'success' => empty( $this->results['errors'] ),
|
||||
'results' => $this->results,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all WooCommerce products that have a SKU set
|
||||
*
|
||||
* @return WC_Product[] Array of WooCommerce products.
|
||||
*/
|
||||
private function get_wc_products_with_sku() {
|
||||
$products = wc_get_products( array(
|
||||
'limit' => -1,
|
||||
'status' => 'publish',
|
||||
'return' => 'objects',
|
||||
) );
|
||||
|
||||
// Filter to only products with SKUs
|
||||
return array_filter( $products, function ( $product ) {
|
||||
return ! empty( $product->get_sku() );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single WooCommerce product - find it in BC and sync
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user