ArcWOC/admin/partials/hesabix-v2-sync.php
morrning 8aedcf2500 Release 4.9.0: invoice profit in WP + marketplace license banner
Show Hesabix invoice profit on ArcWOC orders list and order meta box,
refresh after sync, and surface WooCommerce marketplace license upsell
(hidden when active). Also includes 4.8.2 bank/cash take≤100 fixes.
2026-08-04 12:56:06 +00:00

700 lines
35 KiB
PHP
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Sync view — همگام‌سازی دسته‌ای با مرحله‌بندی AJAX
*
* @since 2.0.0
* @package Hesabix_V2
*/
if (!defined('WPINC')) {
die;
}
$bulk_defs = Hesabix_V2_Sync_Service::get_bulk_sync_defaults();
$bulk_opts = Hesabix_V2_Sync_Service::get_bulk_sync_options();
$bulk_wc_max = (int) Hesabix_V2_Sync_Service::BULK_WC_CHUNK_MAX_ITEMS;
$bulk_api_max_person = (int) Hesabix_V2_Sync_Service::BULK_API_ITEMS_MAX_PERSON;
$bulk_api_max_invoice = (int) Hesabix_V2_Sync_Service::BULK_API_ITEMS_MAX_INVOICE;
$bulk_api_max_product = (int) Hesabix_V2_Sync_Service::BULK_API_ITEMS_MAX_PRODUCT;
?>
<div class="wrap hesabix-v2-wrap hesabix-v2-sync-page">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<?php if (!empty($_GET['hesabix_bulk_saved'])): ?>
<div class="notice notice-success is-dismissible"><p><?php esc_html_e('تنظیمات اندازهٔ دسته ذخیره شد.', 'hesabix-v2'); ?></p></div>
<?php endif; ?>
<form method="post" action="" class="hesabix-v2-card hesabix-v2-bulk-settings-form">
<?php wp_nonce_field('hesabix_v2_bulk_sync_save'); ?>
<input type="hidden" name="hesabix_v2_save_bulk_sync" value="1">
<h2><?php esc_html_e('همگام‌سازی دسته‌ای (کاهش تایم‌اوت)', 'hesabix-v2'); ?></h2>
<p class="description"><?php esc_html_e('در فروشگاه‌های بزرگ، عملیات در چند درخواست کوتاه‌تر تقسیم می‌شود. اگر یک مرحله خطا بدهد، بقیه مراحل ادامه می‌یابند. در انتها خلاصه و نمونهٔ خطاها نمایش داده می‌شود.', 'hesabix-v2'); ?></p>
<table class="form-table hesabix-v2-bulk-form-table">
<tr>
<th scope="row"><label for="wc_product_parents_per_ajax"><?php esc_html_e('تعداد محصول والد به‌ازای هر درخواست (وکامرس → حسابیکس)', 'hesabix-v2'); ?></label></th>
<td>
<input type="number" min="5" max="<?php echo esc_attr((string) $bulk_wc_max); ?>" step="1" name="wc_product_parents_per_ajax" id="wc_product_parents_per_ajax" value="<?php echo esc_attr((string) $bulk_opts['wc_product_parents_per_ajax']); ?>" class="small-text">
<p class="description"><?php esc_html_e('هر مرحله این تعداد محصول والد انتخاب می‌شود؛ واریانت‌ها داخل هر مرحله در چند تماس bulk کالا تقسیم می‌شوند؛ مقدار کمتر هر مرحلهٔ AJAX سبک‌تر است.', 'hesabix-v2'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="wc_categories_per_ajax"><?php esc_html_e('تعداد دستهٔ ووکامرس به‌ازای هر درخواست (همگام درخت دسته‌ها)', 'hesabix-v2'); ?></label></th>
<td>
<input type="number" min="10" max="<?php echo esc_attr((string) $bulk_wc_max); ?>" step="1" name="wc_categories_per_ajax" id="wc_categories_per_ajax" value="<?php echo esc_attr((string) ($bulk_opts['wc_categories_per_ajax'] ?? 60)); ?>" class="small-text">
<p class="description"><?php esc_html_e('همهٔ ترم‌های product_cat (حتی بدون محصول) به ترتیب والد→فرزند نگاشت می‌شوند؛ مقدار بزرگ‌تر مراحل را کمتر می‌کند ولی هر مرحله سنگین‌تر است.', 'hesabix-v2'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="wc_customers_per_ajax"><?php esc_html_e('تعداد مشتری به‌ازای هر درخواست AJAX (ادمین ووکامرس)', 'hesabix-v2'); ?></label></th>
<td>
<input type="number" min="5" max="<?php echo esc_attr((string) $bulk_wc_max); ?>" step="1" name="wc_customers_per_ajax" id="wc_customers_per_ajax" value="<?php echo esc_attr((string) $bulk_opts['wc_customers_per_ajax']); ?>" class="small-text">
<p class="description"><?php esc_html_e('چند کاربر همزمان از مرورگر برای یک درخواست admin-ajax ارسال شود؛ داخل هر درخواست، شخص‌ها در دستهٔ کوچک‌تر برای API حسابیکس شکسته می‌شوند.', 'hesabix-v2'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="wc_orders_ajax_batch"><?php esc_html_e('حداکثر سفارش در هر دستهٔ همگام‌سازی AJAX', 'hesabix-v2'); ?></label></th>
<td>
<input type="number" min="5" max="<?php echo esc_attr((string) $bulk_wc_max); ?>" step="1" name="wc_orders_ajax_batch" id="wc_orders_ajax_batch" value="<?php echo esc_attr((string) ($bulk_opts['wc_orders_ajax_batch'] ?? $bulk_defs['wc_orders_ajax_batch'])); ?>" class="small-text">
</td>
</tr>
<tr>
<th scope="row"><label for="api_bulk_persons_per_request"><?php esc_html_e('حداکثر مشتری در هر تماس bulk API (حسابیکس)', 'hesabix-v2'); ?></label></th>
<td>
<input type="number" min="5" max="<?php echo esc_attr((string) $bulk_api_max_person); ?>" step="1" name="api_bulk_persons_per_request" id="api_bulk_persons_per_request" value="<?php echo esc_attr((string) ($bulk_opts['api_bulk_persons_per_request'] ?? $bulk_defs['api_bulk_persons_per_request'])); ?>" class="small-text">
<p class="description"><?php echo esc_html(sprintf(
/* translators: %d: max items per bulk request (aligned with Hesabix API). */
__('یک درخواست HTTP به اندپوینت bulk اشخاص؛ حداکثر %d آیتم در هر بدنه مطابق حسابیکس؛ مقادیر بسیار بالا خطر تایم‌اوت دارند.', 'hesabix-v2'),
$bulk_api_max_person
)); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="api_bulk_invoices_per_request"><?php esc_html_e('حداکثر فاکتور در هر تماس bulk API (حسابیکس)', 'hesabix-v2'); ?></label></th>
<td>
<input type="number" min="1" max="<?php echo esc_attr((string) $bulk_api_max_invoice); ?>" step="1" name="api_bulk_invoices_per_request" id="api_bulk_invoices_per_request" value="<?php echo esc_attr((string) ($bulk_opts['api_bulk_invoices_per_request'] ?? $bulk_defs['api_bulk_invoices_per_request'])); ?>" class="small-text">
<p class="description"><?php echo esc_html(sprintf(
/* translators: %d: max invoices per bulk request. */
__('هر بدنهٔ bulk حداکثر %d فاکتور؛ payload سنگین است — مقدار کمتر برای پایداری و تایم‌اوت کمتر توصیه می‌شود.', 'hesabix-v2'),
$bulk_api_max_invoice
)); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="api_bulk_products_per_request"><?php esc_html_e('حداکثر کالا در هر تماس bulk API (حسابیکس)', 'hesabix-v2'); ?></label></th>
<td>
<input type="number" min="3" max="<?php echo esc_attr((string) $bulk_api_max_product); ?>" step="1" name="api_bulk_products_per_request" id="api_bulk_products_per_request" value="<?php echo esc_attr((string) ($bulk_opts['api_bulk_products_per_request'] ?? $bulk_defs['api_bulk_products_per_request'])); ?>" class="small-text">
<p class="description"><?php echo esc_html(sprintf(
/* translators: %d: max product rows per bulk request. */
__('هر تماس شامل سطرهای همگام (محصول ساده یا واریانت) است؛ حسابیکس تا %d سطر قبول می‌کند؛ برای تایم‌اوت کمتر مقدار کوچک‌تر بهتر است.', 'hesabix-v2'),
$bulk_api_max_product
)); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="hesabix_person_take"><?php esc_html_e('اندازهٔ صفحهٔ API اشخاص (حسابیکس → ووکامرس)', 'hesabix-v2'); ?></label></th>
<td>
<input type="number" min="10" max="200" step="1" name="hesabix_person_take" id="hesabix_person_take" value="<?php echo esc_attr((string) $bulk_opts['hesabix_person_take']); ?>" class="small-text">
</td>
</tr>
<tr>
<th scope="row"><label for="hesabix_import_pages_per_ajax"><?php esc_html_e('تعداد صفحهٔ اشخاص در هر درخواست واردات', 'hesabix-v2'); ?></label></th>
<td>
<input type="number" min="1" max="50" step="1" name="hesabix_import_pages_per_ajax" id="hesabix_import_pages_per_ajax" value="<?php echo esc_attr((string) $bulk_opts['hesabix_import_pages_per_ajax']); ?>" class="small-text">
</td>
</tr>
<tr>
<th scope="row"><label for="errors_preview_cap"><?php esc_html_e('حداکثر نمونهٔ خطا برای هر مرحله در پاسخ JSON', 'hesabix-v2'); ?></label></th>
<td>
<input type="number" min="10" max="300" step="1" name="errors_preview_cap" id="errors_preview_cap" value="<?php echo esc_attr((string) $bulk_opts['errors_preview_cap']); ?>" class="small-text">
</td>
</tr>
</table>
<p>
<?php submit_button(__('ذخیرهٔ اندازهٔ دسته‌ها', 'hesabix-v2'), 'secondary', 'submit', false); ?>
</p>
</form>
<div class="hesabix-v2-card">
<h2><?php esc_html_e('همگام‌سازی محصولات', 'hesabix-v2'); ?></h2>
<p><?php esc_html_e('تمام محصولات منتشرشدهٔ ووکامرس به حسابیکس؛ به‌صورت چند مرحله.', 'hesabix-v2'); ?></p>
<button id="sync-products" type="button" class="button button-primary"><?php esc_html_e('همگام‌سازی همهٔ محصولات', 'hesabix-v2'); ?></button>
<button id="abort-sync-products" type="button" class="button" style="display:none;" aria-live="polite"><?php esc_html_e('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2'); ?></button>
<div id="products-progress" class="hesabix-v2-sync-progress" aria-live="polite"></div>
<div id="products-result"></div>
</div>
<div class="hesabix-v2-card">
<h2><?php esc_html_e('همگام‌سازی دسته‌های محصول ووکامرس', 'hesabix-v2'); ?></h2>
<p><?php esc_html_e('همهٔ ترم‌های دستهٔ محصول (product_cat)، از جمله دسته‌های بدون کالا، به حسابیکس نگاشت یا به‌روز می‌شوند؛ به‌صورت چند مرحله.', 'hesabix-v2'); ?></p>
<p class="description"><?php esc_html_e('اگر در تنظیمات «تطبیق دسته با نام موجود در حسابیکس» را فعال کرده باشید، قبل از ایجاد دستهٔ جدید، درخت حسابیکس برای همان نام و والد بررسی می‌شود.', 'hesabix-v2'); ?></p>
<button id="sync-wc-categories" type="button" class="button button-primary"><?php esc_html_e('همگام‌سازی همهٔ دسته‌ها', 'hesabix-v2'); ?></button>
<button id="abort-sync-wc-categories" type="button" class="button" style="display:none;" aria-live="polite"><?php esc_html_e('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2'); ?></button>
<div id="wc-categories-progress" class="hesabix-v2-sync-progress" aria-live="polite"></div>
<div id="wc-categories-result"></div>
</div>
<div class="hesabix-v2-card">
<h2><?php esc_html_e('همگام‌سازی مشتریان', 'hesabix-v2'); ?></h2>
<p><?php esc_html_e('کاربران با نقش مشتری یا مشترک به حسابیکس؛ مرحله‌ای.', 'hesabix-v2'); ?></p>
<p class="description">
<?php
echo wp_kses_post(
sprintf(
/* translators: 1: opening <a>, 2: closing </a> — link wraps «صفحهٔ مشتریان و حسابیکس». */
__('برای انتخاب مشتریان مشخص، مشاهدهٔ وضعیت هر کاربر و همگام‌سازی تکی یا گروهی، به %1$sصفحهٔ مشتریان و حسابیکس%2$s بروید.', 'hesabix-v2'),
'<a href="' . esc_url(admin_url('admin.php?page=hesabix-v2-customers')) . '">',
'</a>'
)
);
?>
</p>
<button id="sync-customers" type="button" class="button button-primary"><?php esc_html_e('همگام‌سازی همهٔ مشتریان', 'hesabix-v2'); ?></button>
<button id="abort-sync-customers" type="button" class="button" style="display:none;"><?php esc_html_e('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2'); ?></button>
<div id="customers-progress" class="hesabix-v2-sync-progress" aria-live="polite"></div>
<div id="customers-result"></div>
</div>
<div class="hesabix-v2-card">
<h2><?php esc_html_e('واردات مشتریان از حسابیکس', 'hesabix-v2'); ?></h2>
<p><?php esc_html_e('اشخاص حسابیکس با ایمیل یا موبایل معتبر با کاربر ووکامرس تطبیق داده می‌شوند.', 'hesabix-v2'); ?></p>
<p>
<label>
<input type="checkbox" id="hesabix-import-create-missing" value="1">
<?php esc_html_e('ایجاد حساب مشتری برای اشخاصی که ایمیل معتبر دارند و کاربر نیستند', 'hesabix-v2'); ?>
</label>
</p>
<p class="description"><?php esc_html_e('رمز تصادفی ساخته می‌شود؛ ممکن است ووکامرس ایمیل ارسال کند.', 'hesabix-v2'); ?></p>
<p>
<button type="button" id="import-customers-from-hesabix" class="button button-primary"><?php esc_html_e('واردات از حسابیکس', 'hesabix-v2'); ?></button>
<button id="abort-import-customers" type="button" class="button" style="display:none;"><?php esc_html_e('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2'); ?></button>
</p>
<div id="import-customers-progress" class="hesabix-v2-sync-progress" aria-live="polite"></div>
<div id="import-customers-result"></div>
</div>
<div class="hesabix-v2-card">
<h2><?php esc_html_e('وضعیت همگام‌سازی', 'hesabix-v2'); ?></h2>
<?php
$db = new Hesabix_V2_DB_Service();
$pending = $db->get_pending_items(null, 100);
$errors = $db->get_error_items(null, 100);
?>
<p>
<?php esc_html_e('موارد در انتظار:', 'hesabix-v2'); ?> <strong><?php echo count($pending); ?></strong><br>
<?php esc_html_e('موارد با خطا:', 'hesabix-v2'); ?> <strong><?php echo count($errors); ?></strong>
</p>
<?php if (!empty($errors)): ?>
<h3><?php esc_html_e('خطاهای اخیر', 'hesabix-v2'); ?></h3>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th><?php esc_html_e('نوع', 'hesabix-v2'); ?></th>
<th><?php esc_html_e('شناسه', 'hesabix-v2'); ?></th>
<th><?php esc_html_e('پیام خطا', 'hesabix-v2'); ?></th>
<th><?php esc_html_e('تلاش مجدد', 'hesabix-v2'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($errors as $error): ?>
<tr>
<td><?php echo esc_html($error['entity_type']); ?></td>
<td><?php echo esc_html($error['wc_id']); ?></td>
<td><?php echo esc_html($error['error_message']); ?></td>
<td><?php echo esc_html($error['retry_count']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<style>
.hesabix-v2-bulk-settings-form { margin-bottom: 20px; }
.hesabix-v2-bulk-form-table th { width: 280px; vertical-align: top; }
.hesabix-v2-sync-progress {
margin: 12px 0 6px;
padding: 10px 12px;
background: #f6f7f7;
border: 1px solid #c3c4c7;
border-radius: 4px;
font-size: 13px;
line-height: 1.55;
max-width: 720px;
}
.hesabix-v2-sync-progress .hesabix-v2-sync-bar {
height: 8px;
background: #dcdcde;
border-radius: 4px;
overflow: hidden;
margin-top: 8px;
}
.hesabix-v2-sync-progress .hesabix-v2-sync-bar > span {
display: block;
height: 100%;
background: #2271b1;
width: 0%;
transition: width 0.25s ease;
}
</style>
<script>
jQuery(function($) {
var ajaxUrl = (typeof hesabix_v2_ajax !== 'undefined' && hesabix_v2_ajax.ajax_url) ? hesabix_v2_ajax.ajax_url : (typeof ajaxurl !== 'undefined' ? ajaxurl : '');
var nonce = (typeof hesabix_v2_ajax !== 'undefined' && hesabix_v2_ajax.nonce) ? hesabix_v2_ajax.nonce : '';
var bulk = (typeof hesabix_v2_ajax !== 'undefined' && hesabix_v2_ajax.bulk_sync) ? hesabix_v2_ajax.bulk_sync : {};
function esc(s) {
return $('<div/>').text(String(s == null ? '' : s)).html();
}
function mergeImportStats(tot, ch) {
if (!ch) return;
tot.matched_updated += (ch.matched_updated || 0);
tot.created += (ch.created || 0);
tot.skipped += (ch.skipped || 0);
tot.failed += (ch.failed || 0);
tot.total_processed += (ch.total_processed || 0);
}
function attachErrors(agg, chunk) {
if (!chunk || !chunk.errors_preview || !chunk.errors_preview.length) return;
for (var i = 0; i < chunk.errors_preview.length && agg.errors.length < 200; i++) {
agg.errors.push(chunk.errors_preview[i]);
}
}
function ajaxChunk(action, data, retries) {
retries = retries === undefined ? 3 : retries;
return $.Deferred(function(def) {
function attempt(left) {
$.ajax({
url: ajaxUrl,
type: 'POST',
timeout: 900000,
data: $.extend({ action: action, nonce: nonce }, data)
}).done(function(res) {
def.resolve(res || {});
}).fail(function(xhr, status) {
if (left > 0 && (status === 'timeout' || xhr.status === 0 || xhr.status >= 500)) {
window.setTimeout(function() {
attempt(left - 1);
}, 800 + (400 * (4 - left)));
return;
}
def.resolve({
success: false,
message: status || xhr.statusText || '<?php echo esc_js(__('خطای شبکه یا سرور', 'hesabix-v2')); ?>'
});
});
}
attempt(retries);
}).promise();
}
function fmtProductErr(e) {
if (!e) {
return '';
}
var label = e.variation_id ? ('#' + e.product_id + ' (واریانت ' + e.variation_id + ')') : ('#' + (e.product_id || ''));
return esc(label) + ': ' + esc(e.message || '');
}
function fmtCategoryErr(e) {
if (!e) return '';
return esc('<?php echo esc_js(__('دستهٔ ووکامرس', 'hesabix-v2')); ?> #' + (e.wc_category_id || '')) + ': ' + esc(e.message || '');
}
function renderFinalNoticeCls(aggFailed) {
return aggFailed > 0 ? 'notice-warning' : 'notice-success';
}
// --- محصولات ---
var abortProducts = false;
$('#abort-sync-products').on('click', function() {
abortProducts = true;
$(this).prop('disabled', true).text('<?php echo esc_js(__('در انتظار پایان مرحلهٔ جاری…', 'hesabix-v2')); ?>');
});
$('#sync-products').on('click', async function() {
if (!ajaxUrl || !nonce) return;
if (!confirm('<?php echo esc_js(__('این عمل در چند مرحله انجام می‌شود و ممکن است طولانی باشد. ادامه می‌دهید؟', 'hesabix-v2')); ?>')) return;
var $btn = $('#sync-products');
var $abort = $('#abort-sync-products');
var $prog = $('#products-progress');
var $result = $('#products-result');
abortProducts = false;
$btn.prop('disabled', true);
$abort.prop('disabled', false).show().text('<?php echo esc_js(__('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2')); ?>');
var agg = { success: 0, failed: 0, total: 0, errors: [], batches: 0, haltedMsg: '', networkStalls: 0 };
var offset = 0;
var batch = parseInt(String(bulk.wc_product_parents_per_ajax || <?php echo (int) $bulk_defs['wc_product_parents_per_ajax']; ?>), 10) || 35;
var estTotal = null;
for (;;) {
if (abortProducts) {
agg.haltedMsg = '<?php echo esc_js(__('توسط کاربر متوقف شد (پس از آخرین مرحلهٔ کامل).', 'hesabix-v2')); ?>';
break;
}
$prog.html(
'<strong><?php echo esc_js(__('در حال اجرا…', 'hesabix-v2')); ?></strong>'
+ '<div class="hesabix-v2-sync-bar"><span></span></div>'
+ '<p class="hesabix-sync-progress-text"></p>'
);
var pct = estTotal ? Math.min(99, Math.round((offset / estTotal) * 100)) : 0;
$prog.find('.hesabix-v2-sync-bar > span').css('width', pct + '%');
$prog.find('.hesabix-sync-progress-text').text(
'<?php echo esc_js(__('مرحلهٔ', 'hesabix-v2')); ?> ' + (agg.batches + 1)
+ ' — <?php echo esc_js(__('offset', 'hesabix-v2')); ?> ' + offset
+ (estTotal !== null ? ' / ~' + estTotal : '')
);
var res = await ajaxChunk('hesabix_v2_sync_products', { offset: offset, batch_size: batch });
if (!res || res.success !== true || !res.chunk_results) {
agg.haltedMsg = (res && res.message) ? res.message : '<?php echo esc_js(__('پاسخ نامعتبر یا خطای سرور', 'hesabix-v2')); ?>';
break;
}
var ch = res.chunk_results;
agg.success += (ch.success || 0);
agg.failed += (ch.failed || 0);
agg.total += (ch.total || 0);
attachErrors(agg, ch);
agg.batches++;
if (agg.batches > 5000) {
agg.haltedMsg += (agg.haltedMsg ? ' ' : '') +
'<?php echo esc_js(__('حداکثر تعداد مراحل ایمن رسید؛ در صورت نیاز دوباره اجرا کنید.', 'hesabix-v2')); ?>';
break;
}
if (res.estimated_catalog_total_parents !== undefined && res.estimated_catalog_total_parents !== null) {
estTotal = parseInt(res.estimated_catalog_total_parents, 10) || estTotal;
}
offset = parseInt(res.next_offset, 10) || 0;
if (res.done) {
break;
}
var advance = parseInt(res.processed_parent_posts_in_chunk, 10);
if (!advance || advance < 1) {
break;
}
}
var cls = renderFinalNoticeCls(agg.failed);
var note = '';
note += '<strong><?php echo esc_js(__('گزارش نهایی همگام‌سازی محصولات', 'hesabix-v2')); ?></strong><br>';
note += '<?php echo esc_js(__('موفق:', 'hesabix-v2')); ?> ' + agg.success + '<br>';
note += '<?php echo esc_js(__('ناموفق:', 'hesabix-v2')); ?> ' + agg.failed + '<br>';
note += '<?php echo esc_js(__('جمع عملیات (شامل واریانت‌ها):', 'hesabix-v2')); ?> ' + agg.total + '<br>';
note += '<?php echo esc_js(__('تعداد دستهٔ اجراشده:', 'hesabix-v2')); ?> ' + agg.batches + '<br>';
if (agg.haltedMsg) {
note += '<strong><?php echo esc_js(__('وضعیت:', 'hesabix-v2')); ?></strong> ' + esc(agg.haltedMsg) + '<br>';
}
if (agg.errors.length) {
note += '<br><strong><?php echo esc_js(__('نمونهٔ خطاها (حداکثر ۲۰ خط نخست ذخیره‌شده در گزارش):', 'hesabix-v2')); ?></strong><ul>';
var show = agg.errors.slice(0, 20);
for (var ei = 0; ei < show.length; ei++) {
note += '<li>' + fmtProductErr(show[ei]) + '</li>';
}
note += '</ul>';
if (agg.errors.length > 20) {
note += '<p class="description">' + esc('<?php echo esc_js(__('… و خطاهای دیگر؛ جمع خطاهای دارای جزئیات در هر مرحله در پاسخ سرور بوده است.', 'hesabix-v2')); ?>') + '</p>';
}
}
$prog.empty();
$result.html('<div class="notice ' + cls + '"><p>' + note + '</p></div>');
$btn.prop('disabled', false);
$abort.hide().prop('disabled', false).text('<?php echo esc_js(__('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2')); ?>');
});
// --- دسته‌های محصول ووکامرس ---
var abortWcCategories = false;
$('#abort-sync-wc-categories').on('click', function() {
abortWcCategories = true;
$(this).prop('disabled', true).text('<?php echo esc_js(__('در انتظار پایان مرحلهٔ جاری…', 'hesabix-v2')); ?>');
});
$('#sync-wc-categories').on('click', async function() {
if (!ajaxUrl || !nonce) return;
if (!confirm('<?php echo esc_js(__('همهٔ دسته‌های محصول ووکامرس (حتی بدون کالا) با حسابیکس همگام شوند. ادامه می‌دهید؟', 'hesabix-v2')); ?>')) return;
var $btn = $('#sync-wc-categories');
var $abort = $('#abort-sync-wc-categories');
var $prog = $('#wc-categories-progress');
var $result = $('#wc-categories-result');
abortWcCategories = false;
$btn.prop('disabled', true);
$abort.prop('disabled', false).show().text('<?php echo esc_js(__('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2')); ?>');
var agg = { success: 0, failed: 0, total: 0, errors: [], batches: 0, haltedMsg: '' };
var offset = 0;
var batch = parseInt(String(bulk.wc_categories_per_ajax || <?php echo (int) ($bulk_defs['wc_categories_per_ajax'] ?? 60); ?>), 10) || 60;
var estTotal = null;
for (;;) {
if (abortWcCategories) {
agg.haltedMsg = '<?php echo esc_js(__('توسط کاربر متوقف شد (پس از آخرین مرحلهٔ کامل).', 'hesabix-v2')); ?>';
break;
}
$prog.html(
'<strong><?php echo esc_js(__('در حال اجرا…', 'hesabix-v2')); ?></strong>'
+ '<div class="hesabix-v2-sync-bar"><span></span></div>'
+ '<p class="hesabix-sync-progress-text"></p>'
);
var pct = estTotal ? Math.min(99, Math.round((offset / estTotal) * 100)) : 0;
$prog.find('.hesabix-v2-sync-bar > span').css('width', pct + '%');
$prog.find('.hesabix-sync-progress-text').text(
'<?php echo esc_js(__('مرحلهٔ', 'hesabix-v2')); ?> ' + (agg.batches + 1)
+ ' — <?php echo esc_js(__('offset', 'hesabix-v2')); ?> ' + offset
+ (estTotal !== null ? ' / ~' + estTotal : '')
);
var res = await ajaxChunk('hesabix_v2_sync_wc_categories', { offset: offset, batch_size: batch });
if (!res || res.success !== true || !res.chunk_results) {
agg.haltedMsg = (res && res.message) ? res.message : '<?php echo esc_js(__('پاسخ نامعتبر یا خطای سرور', 'hesabix-v2')); ?>';
break;
}
var ch = res.chunk_results;
agg.success += (ch.success || 0);
agg.failed += (ch.failed || 0);
agg.total += (ch.total || 0);
attachErrors(agg, ch);
agg.batches++;
if (agg.batches > 5000) {
agg.haltedMsg += (agg.haltedMsg ? ' ' : '') +
'<?php echo esc_js(__('حداکثر تعداد مراحل ایمن رسید؛ در صورت نیاز دوباره اجرا کنید.', 'hesabix-v2')); ?>';
break;
}
if (res.estimated_catalog_total_wc_categories !== undefined && res.estimated_catalog_total_wc_categories !== null) {
estTotal = parseInt(res.estimated_catalog_total_wc_categories, 10) || estTotal;
}
offset = parseInt(res.next_offset, 10) || 0;
if (res.done) break;
var advance = parseInt(res.processed_wc_categories_in_chunk, 10);
if (!advance || advance < 1) break;
}
var cls = renderFinalNoticeCls(agg.failed);
var note = '';
note += '<strong><?php echo esc_js(__('گزارش نهایی همگام‌سازی دسته‌های ووکامرس', 'hesabix-v2')); ?></strong><br>';
note += '<?php echo esc_js(__('موفق:', 'hesabix-v2')); ?> ' + agg.success + '<br>';
note += '<?php echo esc_js(__('ناموفق:', 'hesabix-v2')); ?> ' + agg.failed + '<br>';
note += '<?php echo esc_js(__('جمع عملیات در این اجرا:', 'hesabix-v2')); ?> ' + agg.total + '<br>';
note += '<?php echo esc_js(__('تعداد مرحله:', 'hesabix-v2')); ?> ' + agg.batches + '<br>';
if (agg.haltedMsg) {
note += '<strong><?php echo esc_js(__('وضعیت:', 'hesabix-v2')); ?></strong> ' + esc(agg.haltedMsg) + '<br>';
}
if (agg.errors.length) {
note += '<br><strong><?php echo esc_js(__('نمونهٔ خطاها:', 'hesabix-v2')); ?></strong><ul>';
var sh = agg.errors.slice(0, 20);
for (var ci = 0; ci < sh.length; ci++) {
note += '<li>' + fmtCategoryErr(sh[ci]) + '</li>';
}
note += '</ul>';
}
$prog.empty();
$result.html('<div class="notice ' + cls + '"><p>' + note + '</p></div>');
$btn.prop('disabled', false);
$abort.hide().prop('disabled', false).text('<?php echo esc_js(__('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2')); ?>');
});
// --- مشتریان (به حسابیکس) ---
var abortCustomers = false;
$('#abort-sync-customers').on('click', function() {
abortCustomers = true;
$(this).prop('disabled', true).text('<?php echo esc_js(__('در انتظار پایان مرحلهٔ جاری…', 'hesabix-v2')); ?>');
});
$('#sync-customers').on('click', async function() {
if (!ajaxUrl || !nonce) return;
if (!confirm('<?php echo esc_js(__('این عمل در چند مرحله انجام می‌شود. ادامه می‌دهید؟', 'hesabix-v2')); ?>')) return;
var $btn = $('#sync-customers');
var $abort = $('#abort-sync-customers');
var $prog = $('#customers-progress');
var $result = $('#customers-result');
abortCustomers = false;
$btn.prop('disabled', true);
$abort.prop('disabled', false).show().text('<?php echo esc_js(__('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2')); ?>');
var agg = { success: 0, failed: 0, total: 0, errors: [], batches: 0, haltedMsg: '' };
var offset = 0;
var batch = parseInt(String(bulk.wc_customers_per_ajax || <?php echo (int) $bulk_defs['wc_customers_per_ajax']; ?>), 10) || 45;
var estTotal = null;
for (;;) {
if (abortCustomers) {
agg.haltedMsg = '<?php echo esc_js(__('توسط کاربر متوقف شد.', 'hesabix-v2')); ?>';
break;
}
$prog.html(
'<strong><?php echo esc_js(__('در حال اجرا…', 'hesabix-v2')); ?></strong>'
+ '<div class="hesabix-v2-sync-bar"><span></span></div>'
+ '<p class="hesabix-sync-progress-text"></p>'
);
var pct = estTotal ? Math.min(99, Math.round((offset / estTotal) * 100)) : 0;
$prog.find('.hesabix-v2-sync-bar > span').css('width', pct + '%');
$prog.find('.hesabix-sync-progress-text').text(
'<?php echo esc_js(__('مرحلهٔ', 'hesabix-v2')); ?> ' + (agg.batches + 1) + ' — offset ' + offset + (estTotal !== null ? ' / ~' + estTotal : '')
);
var res = await ajaxChunk('hesabix_v2_sync_customers', { offset: offset, batch_size: batch });
if (!res || res.success !== true || !res.chunk_results) {
agg.haltedMsg = (res && res.message) ? res.message : '<?php echo esc_js(__('پاسخ نامعتبر یا خطای سرور', 'hesabix-v2')); ?>';
break;
}
var ch = res.chunk_results;
agg.success += (ch.success || 0);
agg.failed += (ch.failed || 0);
agg.total += (ch.total || 0);
attachErrors(agg, ch);
agg.batches++;
if (agg.batches > 5000) {
agg.haltedMsg += (agg.haltedMsg ? ' ' : '') +
'<?php echo esc_js(__('حداکثر تعداد مراحل ایمن رسید؛ در صورت نیاز دوباره اجرا کنید.', 'hesabix-v2')); ?>';
break;
}
if (res.estimated_catalog_total_customers !== undefined) {
estTotal = parseInt(res.estimated_catalog_total_customers, 10) || estTotal;
}
offset = parseInt(res.next_offset, 10) || 0;
if (res.done) break;
var adv = parseInt(res.processed_in_chunk, 10);
if (!adv || adv < 1) break;
}
var cls = renderFinalNoticeCls(agg.failed);
var note = '';
note += '<strong><?php echo esc_js(__('گزارش نهایی همگام‌سازی مشتریان', 'hesabix-v2')); ?></strong><br>';
note += '<?php echo esc_js(__('موفق:', 'hesabix-v2')); ?> ' + agg.success + '<br>';
note += '<?php echo esc_js(__('ناموفق:', 'hesabix-v2')); ?> ' + agg.failed + '<br>';
note += '<?php echo esc_js(__('کل در این عملیات:', 'hesabix-v2')); ?> ' + agg.total + '<br>';
note += '<?php echo esc_js(__('تعداد دسته:', 'hesabix-v2')); ?> ' + agg.batches + '<br>';
if (agg.haltedMsg) note += '<strong><?php echo esc_js(__('وضعیت:', 'hesabix-v2')); ?></strong> ' + esc(agg.haltedMsg) + '<br>';
if (agg.errors.length) {
note += '<br><strong><?php echo esc_js(__('نمونهٔ خطاها:', 'hesabix-v2')); ?></strong><ul>';
var sh = agg.errors.slice(0, 20);
for (var cj = 0; cj < sh.length; cj++) {
var ee = sh[cj];
note += '<li>' + esc('#' + (ee.customer_id || '') + ': ' + (ee.message || '')) + '</li>';
}
note += '</ul>';
}
$prog.empty();
$result.html('<div class="notice ' + cls + '"><p>' + note + '</p></div>');
$btn.prop('disabled', false);
$abort.hide().prop('disabled', false).text('<?php echo esc_js(__('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2')); ?>');
});
// --- واردات از حسابیکس ---
var abortImport = false;
$('#abort-import-customers').on('click', function() {
abortImport = true;
$(this).prop('disabled', true).text('<?php echo esc_js(__('در انتظار پایان مرحلهٔ جاری…', 'hesabix-v2')); ?>');
});
$('#import-customers-from-hesabix').on('click', async function() {
if (!ajaxUrl || !nonce) return;
if (!confirm('<?php echo esc_js(__('واردات در چند مرحله انجام می‌شود. ادامه می‌دهید؟', 'hesabix-v2')); ?>')) return;
var $btn = $('#import-customers-from-hesabix');
var $abort = $('#abort-import-customers');
var $prog = $('#import-customers-progress');
var $result = $('#import-customers-result');
var createMissing = $('#hesabix-import-create-missing').is(':checked') ? '1' : '';
var tot = { matched_updated: 0, created: 0, skipped: 0, failed: 0, total_processed: 0 };
var batches = 0;
var skip = 0;
var halt = '';
var lastImportRes = { success: true };
abortImport = false;
$btn.prop('disabled', true);
$abort.prop('disabled', false).show().text('<?php echo esc_js(__('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2')); ?>');
for (;;) {
if (abortImport) {
halt = '<?php echo esc_js(__('توسط کاربر متوقف شد.', 'hesabix-v2')); ?>';
break;
}
batches++;
$prog.html(
'<strong><?php echo esc_js(__('در حال واردات…', 'hesabix-v2')); ?></strong>'
+ '<p class="hesabix-import-skip-msg"></p>'
);
$prog.find('.hesabix-import-skip-msg').text('<?php echo esc_js(__('موقعیت:', 'hesabix-v2')); ?> skip = ' + skip);
var res = await ajaxChunk('hesabix_v2_import_customers_from_hesabix', {
skip: skip,
create_missing: createMissing
});
lastImportRes = res || lastImportRes;
if (!res.success) {
halt = (res.message || '') + '';
if (res.chunk_stats) mergeImportStats(tot, res.chunk_stats);
break;
}
if (res.chunk_stats) mergeImportStats(tot, res.chunk_stats);
skip = parseInt(res.next_skip, 10) || skip;
if (res.done) {
break;
}
if (batches > 8000) {
halt = '<?php echo esc_js(__('توقف ایمن؛ برای ادامه دوباره واردات را اجرا کنید (از همان ابتدا لیست اشخاص).', 'hesabix-v2')); ?>';
break;
}
if (!(parseInt(res.pages_fetched, 10) > 0)) {
halt = '<?php echo esc_js(__('هیچ صفحه‌ای از API دریافت نشد؛ واردات متوقف شد.', 'hesabix-v2')); ?>';
break;
}
}
var touched = !!(tot.total_processed || tot.created || tot.matched_updated);
var cls = 'notice-success';
if (lastImportRes && lastImportRes.success !== true && !touched && !halt) {
cls = 'notice-error';
} else if (
halt ||
tot.failed > 0 ||
(lastImportRes && lastImportRes.success !== true && touched)
) {
cls = 'notice-warning';
}
var html = '';
html += '<strong><?php echo esc_js(__('گزارش واردات', 'hesabix-v2')); ?></strong><br>';
html += '<?php echo esc_js(__('کل پردازش‌شده:', 'hesabix-v2')); ?> ' + tot.total_processed + '<br>';
html += '<?php echo esc_js(__('به‌روزشده:', 'hesabix-v2')); ?> ' + tot.matched_updated + '<br>';
html += '<?php echo esc_js(__('ایجاد شده:', 'hesabix-v2')); ?> ' + tot.created + '<br>';
html += '<?php echo esc_js(__('رد شده:', 'hesabix-v2')); ?> ' + tot.skipped + '<br>';
html += '<?php echo esc_js(__('ناموفق:', 'hesabix-v2')); ?> ' + tot.failed + '<br>';
html += '<?php echo esc_js(__('تعداد دستهٔ AJAX:', 'hesabix-v2')); ?> ' + batches + '<br>';
if (halt) {
html += '<strong><?php echo esc_js(__('وضعیت:', 'hesabix-v2')); ?></strong> ' + esc(halt) + '<br>';
}
$prog.empty();
$result.html('<div class="notice ' + cls + '"><p>' + html + '</p></div>');
$btn.prop('disabled', false);
$abort.hide().prop('disabled', false).text('<?php echo esc_js(__('توقف پس از پایان مرحلهٔ جاری', 'hesabix-v2')); ?>');
});
});
</script>