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:
@@ -22,6 +22,11 @@ class WBC_API_Client {
|
||||
*/
|
||||
const BASE_URL = 'https://api.businesscentral.dynamics.com/v2.0/%s/api/v2.0';
|
||||
|
||||
/**
|
||||
* OData V4 URL template: tenant_id, environment, company_name
|
||||
*/
|
||||
const ODATA_URL = 'https://api.businesscentral.dynamics.com/v2.0/%s/%s/ODataV4/Company(\'%s\')';
|
||||
|
||||
/**
|
||||
* Maximum retry attempts
|
||||
*/
|
||||
@@ -365,6 +370,121 @@ class WBC_API_Client {
|
||||
return str_replace( "'", "''", $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the OData V4 base URL for custom endpoints
|
||||
*
|
||||
* @return string|WP_Error Base URL or error if company name not configured.
|
||||
*/
|
||||
private static function get_odata_base_url() {
|
||||
$tenant_id = WBC_OAuth::get_tenant_id();
|
||||
$environment = WBC_OAuth::get_environment();
|
||||
$company_name = get_option( 'wbc_company_name', '' );
|
||||
|
||||
if ( empty( $company_name ) ) {
|
||||
return new WP_Error( 'wbc_missing_company_name', __( 'Company Name is not configured (required for OData endpoints).', 'woo-business-central' ) );
|
||||
}
|
||||
|
||||
return sprintf( self::ODATA_URL, $tenant_id, $environment, rawurlencode( $company_name ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* GET request to a custom OData V4 endpoint
|
||||
*
|
||||
* @param string $endpoint OData endpoint name (e.g. 'ItemByLocation').
|
||||
* @param array $params OData query parameters ($filter, $select, etc.).
|
||||
* @return array|WP_Error Response data or error.
|
||||
*/
|
||||
public static function odata_get( $endpoint, $params = array() ) {
|
||||
$base_url = self::get_odata_base_url();
|
||||
|
||||
if ( is_wp_error( $base_url ) ) {
|
||||
return $base_url;
|
||||
}
|
||||
|
||||
$url = $base_url . '/' . $endpoint;
|
||||
|
||||
if ( ! empty( $params ) ) {
|
||||
$url .= '?' . http_build_query( $params );
|
||||
}
|
||||
|
||||
// Get access token
|
||||
$token = WBC_OAuth::get_access_token();
|
||||
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
WBC_Logger::debug( 'API', 'OData GET request', array( 'url' => $url ) );
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt_array( $ch, array(
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => self::TIMEOUT,
|
||||
CURLOPT_HTTPGET => true,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Accept: application/json',
|
||||
),
|
||||
) );
|
||||
|
||||
$response = curl_exec( $ch );
|
||||
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
$curl_error = curl_error( $ch );
|
||||
|
||||
curl_close( $ch );
|
||||
|
||||
if ( $response === false ) {
|
||||
WBC_Logger::error( 'API', 'OData cURL error', array( 'error' => $curl_error, 'url' => $url ) );
|
||||
return new WP_Error( 'wbc_curl_error', $curl_error );
|
||||
}
|
||||
|
||||
$data = json_decode( $response, true );
|
||||
|
||||
// Handle 401 - retry with fresh token
|
||||
if ( $http_code === 401 ) {
|
||||
WBC_OAuth::clear_token_cache();
|
||||
$token = WBC_OAuth::get_access_token( true );
|
||||
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array( $ch, array(
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => self::TIMEOUT,
|
||||
CURLOPT_HTTPGET => true,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Accept: application/json',
|
||||
),
|
||||
) );
|
||||
|
||||
$response = curl_exec( $ch );
|
||||
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
curl_close( $ch );
|
||||
|
||||
$data = json_decode( $response, true );
|
||||
}
|
||||
|
||||
if ( $http_code >= 400 ) {
|
||||
$error_message = isset( $data['error']['message'] ) ? $data['error']['message'] : __( 'OData request failed', 'woo-business-central' );
|
||||
WBC_Logger::error( 'API', 'OData request failed', array(
|
||||
'http_code' => $http_code,
|
||||
'error' => $data['error'] ?? null,
|
||||
'url' => $url,
|
||||
) );
|
||||
return new WP_Error( 'wbc_odata_error', $error_message, array( 'status' => $http_code ) );
|
||||
}
|
||||
|
||||
WBC_Logger::debug( 'API', 'OData request successful', array( 'http_code' => $http_code, 'url' => $url ) );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific item by number
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user