feat: integrate custom OData endpoints for stock and price sync
Replace broken itemLedgerEntries approach with custom ItemByLocation OData V4 endpoint for location-specific stock. Add ListaPrecios endpoint for price list sync (B2C regular, B2C_OF sale price) with filters for active status and all-customers assignment. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -231,11 +231,10 @@ class WBC_Product_Sync {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stock quantity for a specific BC location
|
||||
* Get stock quantity for a specific BC location using ItemByLocation endpoint
|
||||
*
|
||||
* Uses the OData web services endpoint for Item Ledger Entries.
|
||||
* Requires the BC admin to publish "Item Ledger Entries" (Table 32)
|
||||
* as an OData v4 web service named "ItemLedgerEntries".
|
||||
* Uses the custom OData V4 endpoint "ItemByLocation" which returns
|
||||
* No, Description, Location_Code, Remaining_Quantity.
|
||||
*
|
||||
* Falls back to the item's total inventory if the OData call fails.
|
||||
*
|
||||
@@ -250,24 +249,16 @@ class WBC_Product_Sync {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build OData web services URL for Item Ledger Entries
|
||||
$environment = WBC_OAuth::get_environment();
|
||||
$company_id = WBC_OAuth::get_company_id();
|
||||
$escaped_number = WBC_API_Client::escape_odata_string( $item_number );
|
||||
$escaped_location = WBC_API_Client::escape_odata_string( $location_code );
|
||||
|
||||
// Query Item Ledger Entries filtered by item number and location
|
||||
// Sum the "quantity" field to get current stock at this location
|
||||
$endpoint = '/itemLedgerEntries';
|
||||
$params = array(
|
||||
'$filter' => "itemNumber eq '" . $escaped_number . "' and locationCode eq '" . $escaped_location . "'",
|
||||
'$select' => 'quantity',
|
||||
);
|
||||
|
||||
$result = WBC_API_Client::get( $endpoint, $params );
|
||||
$result = WBC_API_Client::odata_get( 'ItemByLocation', array(
|
||||
'$filter' => "No eq '" . $escaped_number . "' and Location_Code eq '" . $escaped_location . "'",
|
||||
'$select' => 'Remaining_Quantity',
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
WBC_Logger::warning( 'ProductSync', 'Failed to get location stock, using total inventory', array(
|
||||
WBC_Logger::warning( 'ProductSync', 'Failed to get location stock from ItemByLocation, using total inventory', array(
|
||||
'item_number' => $item_number,
|
||||
'location_code' => $location_code,
|
||||
'error' => $result->get_error_message(),
|
||||
@@ -278,27 +269,80 @@ class WBC_Product_Sync {
|
||||
$entries = isset( $result['value'] ) ? $result['value'] : array();
|
||||
|
||||
if ( empty( $entries ) ) {
|
||||
// No ledger entries for this location = 0 stock
|
||||
WBC_Logger::debug( 'ProductSync', 'No ledger entries for location', array(
|
||||
WBC_Logger::debug( 'ProductSync', 'No ItemByLocation entry for item/location', array(
|
||||
'item_number' => $item_number,
|
||||
'location_code' => $location_code,
|
||||
) );
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Sum all quantities (entries can be positive or negative)
|
||||
$total_qty = 0.0;
|
||||
foreach ( $entries as $entry ) {
|
||||
$total_qty += (float) ( $entry['quantity'] ?? 0 );
|
||||
}
|
||||
// Should return a single row for item + location
|
||||
$qty = (float) ( $entries[0]['Remaining_Quantity'] ?? 0 );
|
||||
|
||||
WBC_Logger::debug( 'ProductSync', 'Got location-specific stock', array(
|
||||
WBC_Logger::debug( 'ProductSync', 'Got location-specific stock from ItemByLocation', array(
|
||||
'item_number' => $item_number,
|
||||
'location_code' => $location_code,
|
||||
'stock' => $total_qty,
|
||||
'stock' => $qty,
|
||||
) );
|
||||
|
||||
return $total_qty;
|
||||
return $qty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get price from ListaPrecios custom endpoint
|
||||
*
|
||||
* Filters by: Assign-to Type = All Customers, Status = Active,
|
||||
* Price List Code = specified code, and Product No. = item number.
|
||||
*
|
||||
* @param string $item_number BC item number.
|
||||
* @param string $price_list_code Price list code (e.g. 'B2C').
|
||||
* @return float|false Unit price or false if not found.
|
||||
*/
|
||||
private function get_price_from_list( $item_number, $price_list_code ) {
|
||||
if ( empty( $item_number ) || empty( $price_list_code ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$escaped_number = WBC_API_Client::escape_odata_string( $item_number );
|
||||
$escaped_code = WBC_API_Client::escape_odata_string( $price_list_code );
|
||||
|
||||
$result = WBC_API_Client::odata_get( 'ListaPrecios', array(
|
||||
'$filter' => "Price_List_Code eq '" . $escaped_code . "'"
|
||||
. " and Status eq 'Active'"
|
||||
. " and Assign_to_Type eq 'All Customers'"
|
||||
. " and Product_No eq '" . $escaped_number . "'",
|
||||
'$select' => 'Unit_Price',
|
||||
'$top' => 1,
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
WBC_Logger::warning( 'ProductSync', 'Failed to get price from ListaPrecios', array(
|
||||
'item_number' => $item_number,
|
||||
'price_list_code' => $price_list_code,
|
||||
'error' => $result->get_error_message(),
|
||||
) );
|
||||
return false;
|
||||
}
|
||||
|
||||
$entries = isset( $result['value'] ) ? $result['value'] : array();
|
||||
|
||||
if ( empty( $entries ) ) {
|
||||
WBC_Logger::debug( 'ProductSync', 'No price list entry found', array(
|
||||
'item_number' => $item_number,
|
||||
'price_list_code' => $price_list_code,
|
||||
) );
|
||||
return false;
|
||||
}
|
||||
|
||||
$price = (float) ( $entries[0]['Unit_Price'] ?? 0 );
|
||||
|
||||
WBC_Logger::debug( 'ProductSync', 'Got price from ListaPrecios', array(
|
||||
'item_number' => $item_number,
|
||||
'price_list_code' => $price_list_code,
|
||||
'price' => $price,
|
||||
) );
|
||||
|
||||
return $price;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,29 +380,71 @@ class WBC_Product_Sync {
|
||||
}
|
||||
}
|
||||
|
||||
// Update price if enabled
|
||||
// Update prices if enabled
|
||||
if ( get_option( 'wbc_enable_price_sync', 'yes' ) === 'yes' ) {
|
||||
$price = isset( $item['unitPrice'] ) ? (float) $item['unitPrice'] : 0;
|
||||
$item_number = $item['number'] ?? '';
|
||||
$regular_list = get_option( 'wbc_regular_price_list', '' );
|
||||
$sale_list = get_option( 'wbc_sale_price_list', '' );
|
||||
|
||||
// Get regular price from price list, or fall back to item unitPrice
|
||||
if ( ! empty( $regular_list ) && ! empty( $item_number ) ) {
|
||||
$price = $this->get_price_from_list( $item_number, $regular_list );
|
||||
if ( $price === false ) {
|
||||
$price = isset( $item['unitPrice'] ) ? (float) $item['unitPrice'] : 0;
|
||||
}
|
||||
} else {
|
||||
$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 );
|
||||
|
||||
// Clear sale price if it's now >= the new regular price
|
||||
$sale_price = (float) $product->get_sale_price();
|
||||
if ( $sale_price > 0 && $sale_price >= $price ) {
|
||||
$product->set_sale_price( '' );
|
||||
}
|
||||
|
||||
$changed = true;
|
||||
|
||||
WBC_Logger::debug( 'ProductSync', 'Updated price', array(
|
||||
WBC_Logger::debug( 'ProductSync', 'Updated regular price', array(
|
||||
'product_id' => $product_id,
|
||||
'sku' => $product->get_sku(),
|
||||
'old_price' => $current_price,
|
||||
'new_price' => $price,
|
||||
'source' => ! empty( $regular_list ) ? 'ListaPrecios:' . $regular_list : 'unitPrice',
|
||||
) );
|
||||
}
|
||||
|
||||
// Get sale price from price list
|
||||
if ( ! empty( $sale_list ) && ! empty( $item_number ) ) {
|
||||
$sale_price = $this->get_price_from_list( $item_number, $sale_list );
|
||||
|
||||
if ( $sale_price !== false && $sale_price > 0 ) {
|
||||
$current_sale = (float) $product->get_sale_price();
|
||||
|
||||
// Only set sale price if it's less than the regular price
|
||||
if ( $sale_price < $price && $sale_price !== $current_sale ) {
|
||||
$product->set_sale_price( $sale_price );
|
||||
$changed = true;
|
||||
|
||||
WBC_Logger::debug( 'ProductSync', 'Updated sale price', array(
|
||||
'product_id' => $product_id,
|
||||
'sku' => $product->get_sku(),
|
||||
'old_sale' => $current_sale,
|
||||
'new_sale' => $sale_price,
|
||||
'source' => 'ListaPrecios:' . $sale_list,
|
||||
) );
|
||||
}
|
||||
} else {
|
||||
// No active sale price found - clear any existing sale price
|
||||
if ( ! empty( $product->get_sale_price() ) ) {
|
||||
$product->set_sale_price( '' );
|
||||
$changed = true;
|
||||
|
||||
WBC_Logger::debug( 'ProductSync', 'Cleared sale price (no active entry in list)', array(
|
||||
'product_id' => $product_id,
|
||||
'sku' => $product->get_sku(),
|
||||
'sale_list' => $sale_list,
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store BC item info in meta
|
||||
|
||||
Reference in New Issue
Block a user