perf: filter by BC category before fetching instead of per-product lookups
When wbc_category_filter is set, sync now pages through BC's /items endpoint filtered server-side by itemCategoryCode (or-chained eq - BC's classic OData rejects the `in` operator) and matches each returned item to a WooCommerce product locally via wc_get_product_id_by_sku(), instead of querying BC once per WooCommerce product only to discard most of them. Confirmed against live data that BC's `in` filter isn't supported here but chained `eq ... or ...` is. Also reorders find_bc_item_by_sku() to try GTIN/number before Item Reference (ReferenciasArticulo). Empirically, on this catalog Reference_No is always identical to Item_No, so the reference lookup never matched anything the GTIN/number checks didn't already find - it was costing an extra BC round trip on every single product for no benefit. Kept as a last-resort fallback both directions (SKU->item and item->SKU) in case that doesn't hold for some future item. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -143,7 +143,13 @@ class WBC_Product_Sync {
|
||||
/**
|
||||
* Find a BC item by WooCommerce SKU
|
||||
*
|
||||
* Tries, in order: Item Reference (ReferenciasArticulo), GTIN, then item number.
|
||||
* Tries, in order: GTIN, item number, then Item Reference (ReferenciasArticulo).
|
||||
* Item Reference is last on purpose: on this catalog's real data, Reference_No
|
||||
* is always identical to Item_No for every sampled row, so it never matches
|
||||
* anything the GTIN/number checks didn't already find - trying it first (as
|
||||
* originally implemented) cost an extra BC round trip on every single product
|
||||
* for zero benefit. Kept as a fallback in case that assumption doesn't hold
|
||||
* for some future item.
|
||||
*
|
||||
* @param string $sku WooCommerce product SKU.
|
||||
* @return array|false|WP_Error BC item data, false if not found, or WP_Error.
|
||||
@@ -151,30 +157,6 @@ class WBC_Product_Sync {
|
||||
private function find_bc_item_by_sku( $sku ) {
|
||||
$escaped_sku = WBC_API_Client::escape_odata_string( $sku );
|
||||
|
||||
// Try Item Reference first - lets merchants use a customer/vendor/bar-code
|
||||
// reference number as the WooCommerce SKU instead of the raw item number/GTIN.
|
||||
$item_number = $this->find_item_number_by_reference( $sku );
|
||||
|
||||
if ( is_wp_error( $item_number ) ) {
|
||||
return $item_number;
|
||||
}
|
||||
|
||||
if ( $item_number ) {
|
||||
$result = WBC_API_Client::get( '/items', array(
|
||||
'$filter' => "number eq '" . WBC_API_Client::escape_odata_string( $item_number ) . "'",
|
||||
'$select' => self::SELECT_FIELDS,
|
||||
'$top' => 1,
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( ! empty( $result['value'] ) ) {
|
||||
return $result['value'][0];
|
||||
}
|
||||
}
|
||||
|
||||
// Try by GTIN (since SKU = EAN = GTIN in this setup)
|
||||
$result = WBC_API_Client::get( '/items', array(
|
||||
'$filter' => "gtin eq '" . $escaped_sku . "'",
|
||||
@@ -205,6 +187,29 @@ class WBC_Product_Sync {
|
||||
return $result['value'][0];
|
||||
}
|
||||
|
||||
// Last resort: Item Reference
|
||||
$item_number = $this->find_item_number_by_reference( $sku );
|
||||
|
||||
if ( is_wp_error( $item_number ) ) {
|
||||
return $item_number;
|
||||
}
|
||||
|
||||
if ( $item_number ) {
|
||||
$result = WBC_API_Client::get( '/items', array(
|
||||
'$filter' => "number eq '" . WBC_API_Client::escape_odata_string( $item_number ) . "'",
|
||||
'$select' => self::SELECT_FIELDS,
|
||||
'$top' => 1,
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( ! empty( $result['value'] ) ) {
|
||||
return $result['value'][0];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -265,6 +270,118 @@ class WBC_Product_Sync {
|
||||
return in_array( $item_category, $allowed, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single BC item - find the matching WooCommerce product and sync
|
||||
*
|
||||
* Used by the category-filtered (BC-first) sync path: instead of iterating
|
||||
* every WooCommerce product and asking BC "do you have this SKU?", we
|
||||
* already fetched this item from BC (filtered server-side by category), so
|
||||
* we just need to find the corresponding WooCommerce product locally - no
|
||||
* further BC round trip needed in the common case.
|
||||
*
|
||||
* @param array $item BC item data (id, number, gtin, displayName, unitPrice, inventory, itemCategoryCode).
|
||||
*/
|
||||
public function process_bc_item( $item ) {
|
||||
$this->results['total']++;
|
||||
|
||||
$product = $this->find_wc_product_for_item( $item );
|
||||
|
||||
if ( ! $product ) {
|
||||
$this->results['skipped']++;
|
||||
WBC_Logger::debug( 'ProductSync', 'No matching WooCommerce product for BC item', array(
|
||||
'item_number' => $item['number'] ?? '',
|
||||
'gtin' => $item['gtin'] ?? '',
|
||||
) );
|
||||
return;
|
||||
}
|
||||
|
||||
// If location code is configured, get location-specific stock
|
||||
$location_code = get_option( 'wbc_location_code', '' );
|
||||
if ( ! empty( $location_code ) && get_option( 'wbc_enable_stock_sync', 'yes' ) === 'yes' ) {
|
||||
$location_stock = $this->get_location_stock( $item, $location_code );
|
||||
if ( $location_stock !== false ) {
|
||||
$item['inventory'] = $location_stock;
|
||||
}
|
||||
}
|
||||
|
||||
$updated = $this->update_product( $product, $item );
|
||||
|
||||
if ( $updated ) {
|
||||
$this->results['success']++;
|
||||
} else {
|
||||
$this->results['failed']++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the WooCommerce product matching a BC item, via a local SKU lookup
|
||||
* (no BC round trip) - tries GTIN, then item number, then Item Reference
|
||||
* as a last resort.
|
||||
*
|
||||
* @param array $item BC item data.
|
||||
* @return WC_Product|false
|
||||
*/
|
||||
private function find_wc_product_for_item( $item ) {
|
||||
$gtin = $item['gtin'] ?? '';
|
||||
$number = $item['number'] ?? '';
|
||||
|
||||
if ( ! empty( $gtin ) ) {
|
||||
$product_id = wc_get_product_id_by_sku( $gtin );
|
||||
if ( $product_id ) {
|
||||
return wc_get_product( $product_id );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $number ) ) {
|
||||
$product_id = wc_get_product_id_by_sku( $number );
|
||||
if ( $product_id ) {
|
||||
return wc_get_product( $product_id );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $number ) ) {
|
||||
$reference_no = $this->find_reference_no_by_item_number( $number );
|
||||
|
||||
if ( $reference_no ) {
|
||||
$product_id = wc_get_product_id_by_sku( $reference_no );
|
||||
if ( $product_id ) {
|
||||
return wc_get_product( $product_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a reference/barcode number for a BC item number via the custom
|
||||
* ReferenciasArticulo OData endpoint (reverse direction of find_item_number_by_reference).
|
||||
*
|
||||
* @param string $item_number BC item number.
|
||||
* @return string|false Reference number, or false if none found/on error.
|
||||
*/
|
||||
private function find_reference_no_by_item_number( $item_number ) {
|
||||
$escaped = WBC_API_Client::escape_odata_string( $item_number );
|
||||
|
||||
$result = WBC_API_Client::odata_get( 'ReferenciasArticulo', array(
|
||||
'$filter' => "Item_No eq '" . $escaped . "'",
|
||||
'$select' => 'Item_No,Reference_No,Reference_Type',
|
||||
'$top' => 1,
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entries = isset( $result['value'] ) ? $result['value'] : array();
|
||||
|
||||
if ( empty( $entries ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $entries[0]['Reference_No'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stock quantity for a specific BC location using ItemByLocation endpoint
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user