Automattic\WooCommerce\Internal\Admin\Schedulers

OrdersScheduler::process_pending_batchpublic staticWC 1.0

Process pending orders in batch.

This method queries for orders updated since the last cursor position (compound cursor: date + ID) and imports them into the analytics tables.

Method of the class: OrdersScheduler{}

No Hooks.

Returns

null. Nothing (null).

Usage

$result = OrdersScheduler::process_pending_batch( $cursor_date, $cursor_id );
$cursor_date(string|null)
Cursor date in 'Y-m-d H:i:s' format. Orders after this date will be processed.
Default: null
$cursor_id(int|null)
Cursor order ID. Combined with $cursor_date to form compound cursor.
Default: null

OrdersScheduler::process_pending_batch() code WC 10.5.0

public static function process_pending_batch( $cursor_date = null, $cursor_id = null ) {
	$logger  = wc_get_logger();
	$context = array( 'source' => 'wc-analytics-order-import' );

	if ( self::is_importing() ) {
		// No need to process if an import is already in progress.
		$logger->info( 'Import is already in progress, skipping batch import.', $context );
		return;
	}

	// Load cursor position from options if not provided.
	// If the cursor date is not provided, use the last 24 hours as the default since `action_scheduler_ensure_recurring_actions` runs daily so 24 hours is enough.
	$default_cursor_date = gmdate( 'Y-m-d H:i:s', strtotime( '-24 hours' ) );
	$cursor_date         = $cursor_date ?? get_option( self::LAST_PROCESSED_ORDER_DATE_OPTION, $default_cursor_date );
	$cursor_id           = $cursor_id ?? (int) get_option( self::LAST_PROCESSED_ORDER_ID_OPTION, 0 );

	// Validate cursor date.
	if ( ! $cursor_date || ! strtotime( $cursor_date ) ) {
		$logger->error( 'Invalid cursor date: ' . $cursor_date, $context );
		$cursor_date = $default_cursor_date;
	}

	$batch_size = self::get_batch_size( self::PROCESS_PENDING_ORDERS_BATCH_ACTION );

	$logger->info(
		sprintf( 'Starting batch import. Cursor: %s (ID: %d), batch size: %d', $cursor_date, $cursor_id, $batch_size ),
		$context
	);

	$start_time = microtime( true );

	// Get orders updated since the cursor position.
	$orders = self::get_orders_since( $cursor_date, $cursor_id, $batch_size );

	if ( empty( $orders ) ) {
		$logger->info( 'No orders to process', $context );
		// Update the cursor position to the start time of the batch so that the next batch will start from that point.
		update_option( self::LAST_PROCESSED_ORDER_DATE_OPTION, gmdate( 'Y-m-d H:i:s', (int) $start_time ), false );
		update_option( self::LAST_PROCESSED_ORDER_ID_OPTION, 0, false );
		return;
	}

	$processed_count = 0;
	foreach ( $orders as $order ) {
		try {
			self::import( $order->id );
			++$processed_count;

			// Advance cursor after each successful import. Since orders are sorted by
			// date ASC, id ASC, we can simply overwrite with the current order's values.
			// If an error occurs, we break and save the last successful position.
			$cursor_date = $order->date_updated_gmt;
			$cursor_id   = $order->id;
		} catch ( \Exception $e ) {
			$logger->error(
				sprintf( 'Failed to import order %d: %s', $order->id, $e->getMessage() ),
				$context
			);
			break;
		}
	}

	// Save the updated cursor position.
	update_option( self::LAST_PROCESSED_ORDER_DATE_OPTION, $cursor_date, false );
	update_option( self::LAST_PROCESSED_ORDER_ID_OPTION, $cursor_id, false );

	$elapsed_time = microtime( true ) - $start_time;
	$logger->info(
		sprintf(
			'Batch import completed. Processed: %d orders in %.2f seconds. Cursor: %s (ID: %d)',
			$processed_count,
			$elapsed_time,
			$cursor_date,
			$cursor_id
		),
		$context
	);

	// If we got a full batch, there might be more orders to process.
	// Schedule immediate next batch.
	if ( $processed_count === $batch_size ) {
		$logger->info( 'Full batch processed, scheduling next batch', $context );
		self::schedule_action(
			'process_pending_batch',
			array( $cursor_date, $cursor_id )
		);
	}
}