Files
boursecu_admin/src/pages/menuPages/accounting/components/PargarWalletBulkSync.vue
T
2026-07-22 00:10:29 +03:30

241 lines
6.8 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="pw-bulk">
<div class="pw-bulk__head">
<div class="pw-bulk__icon" aria-hidden="true">
<Icon icon="vuesax-linear:refresh-circle" :size="20" color="var(--admin-primary, #10913A)" />
</div>
<div class="pw-bulk__copy min-w-0 flex-1">
<p class="pw-bulk__title">همگام‌سازی ماندهٔ همهٔ مشتریان</p>
<p class="pw-bulk__hint">
موجودی کیف همهٔ مشتریان دارای کد پرگار را با ماندهٔ پرگار یکی می‌کند — تاریخچهٔ اسناد روی کیف
replay نمی‌شود
</p>
</div>
</div>
<a-button
type="primary"
block
class="pw-bulk__btn"
:loading="running"
:disabled="running"
@click="confirmAndStart"
>
همگام‌سازی مانده از پرگار برای همه
</a-button>
<div v-if="running || resultVisible" class="pw-bulk__progress">
<template v-if="running">
<div class="pw-bulk__progress-row">
<a-spin size="small" />
<span>
در حال همگام‌سازی…
<template v-if="total > 0">{{ processed }} / {{ total }}</template>
</span>
</div>
<div class="pw-bulk__track">
<div class="pw-bulk__track-fill" :style="{ width: percent + '%' }" />
</div>
</template>
<template v-else-if="resultVisible">
<div class="pw-bulk__progress-row">
<span class="pw-bulk__ok">✓ {{ success }} موفق</span>
<span v-if="changed" class="pw-bulk__changed">{{ changed }} تغییر موجودی</span>
<span v-if="failed" class="pw-bulk__fail">✗ {{ failed }} ناموفق</span>
<a-button v-if="errors.length" type="link" size="small" @click="showErrors = !showErrors">
{{ showErrors ? 'پنهان' : 'جزئیات' }}
</a-button>
<a-button type="link" size="small" @click="dismissResult">بستن</a-button>
</div>
<div v-if="showErrors && errors.length" class="pw-bulk__errors">
<div v-for="e in errors" :key="e.id">
<b>{{ e.name }}</b>
<template v-if="e.mobile"> ({{ e.mobile }})</template>
— {{ e.error }}
</div>
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, createVNode, ref } from 'vue'
import { Modal, message } from 'ant-design-vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import {
syncAllWalletsFromPargarChunk,
type PargarBulkWalletSyncChunk,
} from '@/api/pargar'
const CHUNK_LIMIT = 20
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
const running = ref(false)
const resultVisible = ref(false)
const showErrors = ref(false)
const total = ref(0)
const processed = ref(0)
const success = ref(0)
const changed = ref(0)
const failed = ref(0)
const errors = ref<PargarBulkWalletSyncChunk['errors']>([])
const percent = computed(() =>
total.value > 0 ? Math.min(100, Math.round((processed.value / total.value) * 100)) : 0,
)
function dismissResult() {
resultVisible.value = false
showErrors.value = false
}
function confirmAndStart() {
if (running.value) return
Modal.confirm({
title: 'همگام‌سازی ماندهٔ همهٔ مشتریان؟',
icon: createVNode(ExclamationCircleOutlined),
content:
'ماندهٔ کیف همهٔ مشتریان دارای کد پرگار با پرگار هم‌تراز می‌شود. این کار ممکن است چند دقیقه طول بکشد.',
okText: 'شروع همگام‌سازی',
cancelText: 'انصراف',
centered: true,
onOk: () => start(),
})
}
async function start() {
running.value = true
resultVisible.value = false
showErrors.value = false
total.value = 0
processed.value = 0
success.value = 0
changed.value = 0
failed.value = 0
errors.value = []
let offset = 0
let done = false
try {
while (!done) {
const res = await syncAllWalletsFromPargarChunk({
offset,
limit: CHUNK_LIMIT,
force: false,
})
total.value = res.total
success.value += res.success
changed.value += res.changed
failed.value += res.failed
if (res.errors?.length) {
errors.value = [...errors.value, ...res.errors]
}
processed.value = Math.min(res.next_offset, res.total)
offset = res.next_offset
done = res.done || res.processed === 0 || res.total === 0
if (!done) await sleep(150)
}
resultVisible.value = true
if (failed.value === 0) {
message.success(
total.value === 0
? 'مشتری دارای کد پرگار یافت نشد'
: `همگام‌سازی ${success.value} مشتری انجام شد`,
)
} else if (success.value > 0) {
message.warning(`${success.value} موفق — ${failed.value} ناموفق`)
} else {
message.error('همگام‌سازی ناموفق بود')
}
} catch (e) {
console.error(e)
message.error('خطا در ارتباط با سرور')
resultVisible.value = processed.value > 0 || success.value > 0 || failed.value > 0
} finally {
running.value = false
}
}
</script>
<style scoped>
.pw-bulk {
@apply rounded-xl border border-base p-3;
background: var(--admin-surface-muted, var(--admin-bg-muted, #f8fafc));
}
.pw-bulk__head {
@apply mb-3 flex items-start gap-2.5;
}
.pw-bulk__icon {
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gray-50;
}
.pw-bulk__title {
@apply m-0 text-sm font-700;
color: var(--admin-text, inherit);
}
.pw-bulk__hint {
@apply m-0 mt-0.5 text-xs leading-5;
color: var(--admin-text-secondary, #6b7280);
}
.pw-bulk__btn {
@apply font-600;
}
.pw-bulk__progress {
@apply mt-3 rounded-lg border border-base p-2.5 text-xs;
background: var(--admin-bg-surface, #fff);
color: var(--admin-text, inherit);
}
.pw-bulk__progress-row {
@apply flex flex-wrap items-center gap-2;
}
.pw-bulk__track {
@apply mt-2 h-1.5 overflow-hidden rounded-full;
background: var(--admin-border, #e5e7eb);
}
.pw-bulk__track-fill {
@apply h-full rounded-full transition-all duration-300;
background: var(--admin-primary, #10913A);
}
.pw-bulk__ok {
@apply rounded-full px-2.5 py-0.5 font-600;
background: rgba(52, 211, 153, 0.15);
color: #059669;
}
.pw-bulk__changed {
@apply rounded-full px-2.5 py-0.5 font-600;
background: rgba(14, 165, 233, 0.12);
color: #0284c7;
}
.pw-bulk__fail {
@apply rounded-full px-2.5 py-0.5 font-600;
background: rgba(239, 68, 68, 0.12);
color: #dc2626;
}
.pw-bulk__errors {
@apply mt-2 max-h-28 overflow-y-auto rounded-lg p-2 text-xs leading-6;
background: rgba(239, 68, 68, 0.08);
border: 1px solid rgba(239, 68, 68, 0.2);
color: #dc2626;
}
</style>