/**
* Optimized BTS Stock & Price Sync
*
* CHANGES FROM ORIGINAL:
* 1. Memory management: Cache flush and garbage collection every 500 products
* 2. Chunking: Process products in batches instead of all at once
* 3. Optimized queries: Single JOIN query to fetch all meta data at once
* 4. Verbose logging: Memory usage and detailed progress reporting
* 5. Configurable chunk size via --chunk-size parameter
*
* @param bool $dry_run
* @param bool $safe_mode
* @param bool $zero_missing
* @param int $chunk_size Number of products to process per chunk (default: 500)
* @return array
*/
function master_sync_bts_optimized($dry_run = false, $safe_mode = false, $zero_missing = false, $chunk_size = 500) {
WP_CLI::log('');
WP_CLI::log('๐ฆ BTS WHOLESALER SYNC (OPTIMIZED)');
WP_CLI::log('=================================');
$start_time = microtime(true);
$start_memory = memory_get_usage(true);
try {
require_once __DIR__ . '/../api/class-parfumselect-bts-api.php';
$bts_api = new Parfumselect_BTS_API();
// STEP 1: Fetch API products
WP_CLI::log('๐ฅ Fetching products from BTS API...');
$api_products = $bts_api->getProducts();
if (empty($api_products)) {
throw new Exception("Geen producten ontvangen van BTS API");
}
WP_CLI::log('โ
Received ' . number_format(count($api_products)) . ' products from BTS API');
// STEP 2: Build EAN map
WP_CLI::log('๐๏ธ Building EAN map...');
$ean_map = [];
foreach ($api_products as $product) {
$ean = trim($product['ean'] ?? '');
if (!empty($ean)) {
$ean_map[$ean] = $product;
}
}
WP_CLI::log('โ
EAN map built with ' . number_format(count($ean_map)) . ' entries');
// Free up memory from API products array
unset($api_products);
gc_collect_cycles();
// STEP 3: Fetch products with EAN using optimized JOIN query
WP_CLI::log('๐ Fetching local products with EAN...');
global $wpdb;
$local_products = $wpdb->get_results(
"SELECT DISTINCT
p.ID as post_id,
ean.meta_value as ean,
bts_stock.meta_value as current_bts_stock,
bts_cost.meta_value as current_bts_cost
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} ean ON (
p.ID = ean.post_id
AND ean.meta_key = '_global_unique_id'
AND ean.meta_value != ''
)
LEFT JOIN {$wpdb->postmeta} bts_stock ON (
p.ID = bts_stock.post_id
AND bts_stock.meta_key = '_supplier_bts_stock'
)
LEFT JOIN {$wpdb->postmeta} bts_cost ON (
p.ID = bts_cost.post_id
AND bts_cost.meta_key = '_supplier_bts_cost'
)
WHERE p.post_type = 'product'
AND p.post_status = 'publish'",
OBJECT_K
);
WP_CLI::log('โ
Found ' . number_format(count($local_products)) . ' local products with EAN');
$results = [
'updated' => 0,
'skipped' => 0,
'unchanged' => 0,
];
// STEP 4: Process in chunks
WP_CLI::log('');
WP_CLI::log('๐ Processing products in chunks of ' . number_format($chunk_size));
WP_CLI::log(' Memory limit: ' . ini_get('memory_limit'));
WP_CLI::log(' Start memory: ' . size_format(memory_get_usage(true)));
WP_CLI::log('');
$product_ids = array_keys($local_products);
$total_products = count($product_ids);
$chunks = array_chunk($product_ids, $chunk_size);
$total_chunks = count($chunks);
foreach ($chunks as $chunk_index => $chunk_product_ids) {
$chunk_num = $chunk_index + 1;
$chunk_start = $chunk_index * $chunk_size + 1;
$chunk_end = min($chunk_num * $chunk_size, $total_products);
WP_CLI::log("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
WP_CLI::log("๐ฆ Processing Chunk {$chunk_num}/{$total_chunks} (products {$chunk_start}-{$chunk_end})");
WP_CLI::log(" Memory before: " . size_format(memory_get_usage(true)));
$chunk_updated = 0;
$chunk_unchanged = 0;
$chunk_skipped = 0;
foreach ($chunk_product_ids as $product_id) {
if (!isset($local_products[$product_id])) {
$chunk_skipped++;
continue;
}
$product_data = $local_products[$product_id];
$ean = trim($product_data->ean);
if (empty($ean) || !isset($ean_map[$ean])) {
$chunk_skipped++;
continue;
}
$api_product = $ean_map[$ean];
// Clean price string (e.g., "13.31โฌ" -> 13.31)
$price_string = $api_product['price'] ?? '0';
$cleaned_string = preg_replace('/[^0-9,.]/', '', $price_string);
$cleaned_string = str_replace(',', '.', $cleaned_string);
$new_cost = (float) $cleaned_string;
$new_stock = (int) ($api_product['stock'] ?? 0);
$current_bts_stock = (int) ($product_data->current_bts_stock ?? 0);
$current_bts_cost = (float) ($product_data->current_bts_cost ?? 0);
if ($current_bts_stock !== $new_stock || $current_bts_cost !== $new_cost) {
if (!$dry_run) {
update_post_meta($product_id, '_supplier_bts_stock', $new_stock);
update_post_meta($product_id, '_supplier_bts_cost', $new_cost);
update_post_meta($product_id, '_supplier_bts_id', $ean);
if (function_exists('calculate_best_supplier')) {
calculate_best_supplier($product_id, $safe_mode);
}
wc_delete_product_transients($product_id);
}
$chunk_updated++;
} else {
$chunk_unchanged++;
}
}
$results['updated'] += $chunk_updated;
$results['unchanged'] += $chunk_unchanged;
$results['skipped'] += $chunk_skipped;
// MEMORY MANAGEMENT: Clear cache and run garbage collection
wp_cache_flush();
if (function_exists('gc_collect_cycles')) {
gc_collect_cycles();
}
$peak_memory = memory_get_usage(true);
WP_CLI::log(" Updated: {$chunk_updated} | Unchanged: {$chunk_unchanged} | Skipped: {$chunk_skipped}");
WP_CLI::log(" Memory after: " . size_format($peak_memory));
WP_CLI::log(" Peak memory: " . size_format(memory_get_peak_usage(true)));
}
WP_CLI::log("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
WP_CLI::log('');
// STEP 5: Zero missing products (if enabled)
if ($zero_missing && !$dry_run) {
WP_CLI::log('๐ Controleren op ontbrekende producten in BTS API...');
$zeroed_count = 0;
// Get all products with BTS ID (not just those with EAN)
$bts_products = $wpdb->get_results(
"SELECT p.ID as post_id,
bts_id.meta_value as bts_id,
bts_stock.meta_value as current_stock
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} bts_id ON (p.ID = bts_id.post_id AND bts_id.meta_key = '_supplier_bts_id' AND bts_id.meta_value != '')
LEFT JOIN {$wpdb->postmeta} bts_stock ON (p.ID = bts_stock.post_id AND bts_stock.meta_key = '_supplier_bts_stock')
WHERE p.post_type = 'product'
AND p.post_status = 'publish'",
OBJECT_K
);
WP_CLI::log(' Found ' . count($bts_products) . ' products with BTS ID');
foreach ($bts_products as $product_data) {
$ean = trim($product_data->bts_id); // BTS ID is the EAN
// Check if this EAN is NOT in API
if (!isset($ean_map[$ean])) {
$product_id = $product_data->post_id;
$current_bts_stock = (int) ($product_data->current_stock ?? 0);
// Only zero out if current stock > 0 (avoid unnecessary updates)
if ($current_bts_stock > 0) {
// Update BTS supplier stock to 0
update_post_meta($product_id, '_supplier_bts_stock', 0);
// Use calculate_best_supplier to handle full price/stock recalculation
if (function_exists('calculate_best_supplier')) {
// Always use save() for zero-missing (safe mode)
calculate_best_supplier($product_id, true);
}
wc_delete_product_transients($product_id);
$zeroed_count++;
}
}
}
if ($zeroed_count > 0) {
WP_CLI::log("โ ๏ธ {$zeroed_count} producten niet gevonden in API โ Supplier stock op 0 gezet");
} else {
WP_CLI::log("โ
Alle producten gevonden in API");
}
// Add to results
$results['zeroed_missing'] = $zeroed_count;
// Memory cleanup
wp_cache_flush();
if (function_exists('gc_collect_cycles')) {
gc_collect_cycles();
}
}
$duration = round(microtime(true) - $start_time, 2);
$end_memory = memory_get_usage(true);
$memory_used = $end_memory - $start_memory;
WP_CLI::log('');
WP_CLI::log("โ
BTS sync voltooid in {$duration}s");
WP_CLI::log('๐ Memory used: ' . size_format($memory_used));
WP_CLI::log('๐ Peak memory: ' . size_format(memory_get_peak_usage(true)));
$log_message = " Bijgewerkt: {$results['updated']} | Ongewijzigd: {$results['unchanged']} | Overgeslagen: {$results['skipped']}";
if ($zero_missing && isset($results['zeroed_missing'])) {
$log_message .= " | Nieuw op 0: {$results['zeroed_missing']}";
}
WP_CLI::log($log_message);
return [
'success' => true,
'duration' => $duration,
'results' => $results,
'memory_used' => $memory_used,
'peak_memory' => memory_get_peak_usage(true),
];
} catch (Exception $e) {
WP_CLI::warning("โ ๏ธ BTS sync mislukt: " . $e->getMessage());
return [
'success' => false,
'error' => $e->getMessage(),
'results' => [
'updated' => 0,
'skipped' => 0,
'unchanged' => 0,
'zeroed_missing' => 0,
]
];
}
}
Super Sale - Parfumselect
Gratis verzending vanaf โฌ80,-
“Gezichtsmake-Up Verwijderaar Collistar CLEANSING COLLISTAR 150 ml” is toegevoegd aan je winkelwagen.
Bekijk winkelwagen
Registreren
Aanmelden voor vroege toegang Verkoop plus op maat van de nieuwkomers, trends en promoties. Om opt-out, klikt u op afmelden in onze e-mails.
21.19%
Nog € 63,05 tot GRATIS VERZENDING
Free Shipping Bar Attributes