0, 'failed' => 0, 'skipped' => 0, 'total' => 0, 'errors' => array(), ); /** * Run the product sync * * @return array Sync results. */ 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' ), ); } // 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' ), ); } WBC_Logger::info( 'ProductSync', 'Starting product sync' ); $start_time = microtime( true ); $skip = 0; $has_more = true; // Fetch all items from BC with pagination while ( $has_more ) { $items = $this->fetch_items( $skip ); if ( is_wp_error( $items ) ) { $this->results['errors'][] = $items->get_error_message(); WBC_Logger::error( 'ProductSync', 'Failed to fetch items from BC', array( 'error' => $items->get_error_message(), 'skip' => $skip, ) ); break; } // Check if we have items $item_list = isset( $items['value'] ) ? $items['value'] : array(); if ( empty( $item_list ) ) { $has_more = false; break; } // Process each item foreach ( $item_list as $item ) { $this->process_item( $item ); } // Check for more pages $skip += self::ITEMS_PER_PAGE; $has_more = count( $item_list ) >= self::ITEMS_PER_PAGE; // Add a small delay to avoid rate limiting if ( $has_more ) { usleep( 100000 ); // 100ms } } $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( __( 'Sync completed in %s seconds. Success: %d, Failed: %d, Skipped: %d', 'woo-business-central' ), $duration, $this->results['success'], $this->results['failed'], $this->results['skipped'] ), 'results' => $this->results, ); } /** * Fetch items from Business Central * * @param int $skip Number of items to skip. * @return array|WP_Error Items data or error. */ private function fetch_items( $skip = 0 ) { return WBC_API_Client::get_items( self::ITEMS_PER_PAGE, $skip, self::SELECT_FIELDS ); } /** * Process a single BC item * * @param array $item BC item data. */ private function process_item( $item ) { $this->results['total']++; // Get item number and GTIN $item_number = isset( $item['number'] ) ? $item['number'] : ''; $gtin = isset( $item['gtin'] ) ? $item['gtin'] : ''; if ( empty( $item_number ) && empty( $gtin ) ) { $this->results['skipped']++; WBC_Logger::debug( 'ProductSync', 'Skipping item without number or GTIN', array( 'item' => $item ) ); return; } // Try to find WooCommerce product $product_id = $this->find_wc_product( $item_number, $gtin ); if ( ! $product_id ) { $this->results['skipped']++; WBC_Logger::debug( 'ProductSync', 'No matching WC product found', array( 'item_number' => $item_number, 'gtin' => $gtin, ) ); return; } // Get the product $product = wc_get_product( $product_id ); if ( ! $product ) { $this->results['skipped']++; WBC_Logger::warning( 'ProductSync', 'Product ID found but product not loaded', array( 'product_id' => $product_id, ) ); return; } // Update the product $updated = $this->update_product( $product, $item ); if ( $updated ) { $this->results['success']++; } else { $this->results['failed']++; } } /** * Find WooCommerce product by SKU (item number or GTIN) * * @param string $item_number BC item number. * @param string $gtin BC GTIN/EAN. * @return int|false Product ID or false if not found. */ private function find_wc_product( $item_number, $gtin ) { // First try to match by item number (SKU) if ( ! empty( $item_number ) ) { $product_id = wc_get_product_id_by_sku( $item_number ); if ( $product_id ) { return $product_id; } } // Fall back to GTIN match if ( ! empty( $gtin ) ) { $product_id = wc_get_product_id_by_sku( $gtin ); if ( $product_id ) { return $product_id; } } return false; } /** * Update WooCommerce product with BC data * * @param WC_Product $product WooCommerce product. * @param array $item BC item data. * @return bool Whether the product was updated. */ private function update_product( $product, $item ) { $product_id = $product->get_id(); $updated = false; try { // Update stock if enabled if ( get_option( 'wbc_enable_stock_sync', 'yes' ) === 'yes' ) { $stock = isset( $item['inventory'] ) ? (float) $item['inventory'] : 0; $current_stock = (float) $product->get_stock_quantity(); if ( $stock !== $current_stock ) { // Enable stock management if not already enabled if ( ! $product->get_manage_stock() ) { $product->set_manage_stock( true ); } wc_update_product_stock( $product, $stock ); $updated = true; WBC_Logger::debug( 'ProductSync', 'Updated stock', array( 'product_id' => $product_id, 'old_stock' => $current_stock, 'new_stock' => $stock, ) ); } } // Update price if enabled if ( get_option( 'wbc_enable_price_sync', 'yes' ) === 'yes' ) { $price = isset( $item['unitPrice'] ) ? (float) $item['unitPrice'] : 0; $current_price = (float) $product->get_regular_price(); if ( $price > 0 && $price !== $current_price ) { $product->set_regular_price( $price ); // Also update sale price if it's higher than the new regular price $sale_price = (float) $product->get_sale_price(); if ( $sale_price > 0 && $sale_price >= $price ) { $product->set_sale_price( '' ); } $product->save(); $updated = true; WBC_Logger::debug( 'ProductSync', 'Updated price', array( 'product_id' => $product_id, 'old_price' => $current_price, 'new_price' => $price, ) ); } } // Store BC item info in meta via WooCommerce CRUD $product->update_meta_data( '_wbc_bc_item_id', $item['id'] ?? '' ); $product->update_meta_data( '_wbc_bc_item_number', $item['number'] ?? '' ); $product->update_meta_data( '_wbc_last_sync', current_time( 'mysql' ) ); $product->save(); return true; } catch ( Exception $e ) { WBC_Logger::error( 'ProductSync', 'Failed to update product', array( 'product_id' => $product_id, 'error' => $e->getMessage(), ) ); $this->results['errors'][] = sprintf( 'Product %d: %s', $product_id, $e->getMessage() ); return false; } } /** * Get sync results * * @return array */ public function get_results() { return $this->results; } /** * Sync a single product by WooCommerce product ID * * @param int $product_id WooCommerce product ID. * @return array Sync result. */ public function sync_single_product( $product_id ) { $product = wc_get_product( $product_id ); if ( ! $product ) { return array( 'success' => false, 'message' => __( 'Product not found.', 'woo-business-central' ), ); } $sku = $product->get_sku(); if ( empty( $sku ) ) { return array( 'success' => false, 'message' => __( 'Product has no SKU.', 'woo-business-central' ), ); } // Fetch item from BC $result = WBC_API_Client::get_item_by_number( $sku ); if ( is_wp_error( $result ) ) { return array( 'success' => false, 'message' => $result->get_error_message(), ); } $items = isset( $result['value'] ) ? $result['value'] : array(); if ( empty( $items ) ) { return array( 'success' => false, 'message' => __( 'Item not found in Business Central.', 'woo-business-central' ), ); } $item = $items[0]; $updated = $this->update_product( $product, $item ); return array( 'success' => true, 'message' => $updated ? __( 'Product updated successfully.', 'woo-business-central' ) : __( 'Product is already up to date.', 'woo-business-central' ), ); } }