fix some bugs from zarin
This commit is contained in:
+19
-3
@@ -54,8 +54,14 @@ export interface CustomerSyncStatus {
|
|||||||
panel_total: number
|
panel_total: number
|
||||||
panel_registered: number
|
panel_registered: number
|
||||||
panel_only: number
|
panel_only: number
|
||||||
zarin_total: number
|
zarin_total: number | null
|
||||||
zarin_only_count: number
|
zarin_only_count: number | null
|
||||||
|
/** اگر دریافت لیست شکست بخورد */
|
||||||
|
zarin_fetch_error?: string | null
|
||||||
|
zarin_http_status?: number | null
|
||||||
|
/** نام گروه فیلتر (مثلاً مشتری نقدی) */
|
||||||
|
zarin_group_filter?: string | null
|
||||||
|
include_zarin?: boolean
|
||||||
panel_only_list: {
|
panel_only_list: {
|
||||||
data: PanelOnlyCustomer[]
|
data: PanelOnlyCustomer[]
|
||||||
current_page: number
|
current_page: number
|
||||||
@@ -75,7 +81,17 @@ export const getZarinCustomerSyncStatus = async (params?: {
|
|||||||
page?: number
|
page?: number
|
||||||
zarin_page?: number
|
zarin_page?: number
|
||||||
include_zarin?: number | boolean
|
include_zarin?: number | boolean
|
||||||
}) => await useAxios.get<CustomerSyncStatus>('/admin/zarin/customer-sync/status', { params })
|
}) => {
|
||||||
|
// روی بعضی سرورها query به PHP نمیرسد — لیست زرین از مسیر جدا
|
||||||
|
if (params?.include_zarin) {
|
||||||
|
const { include_zarin: _ignored, ...rest } = params
|
||||||
|
return await useAxios.get<CustomerSyncStatus>(
|
||||||
|
'/admin/zarin/customer-sync/status-with-zarin',
|
||||||
|
Object.keys(rest).length ? { params: rest } : undefined,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return await useAxios.get<CustomerSyncStatus>('/admin/zarin/customer-sync/status', { params })
|
||||||
|
}
|
||||||
|
|
||||||
export interface SyncPanelResult {
|
export interface SyncPanelResult {
|
||||||
ok: boolean
|
ok: boolean
|
||||||
|
|||||||
@@ -18,11 +18,11 @@
|
|||||||
<Icon icon="vuesax-linear:arrow-left" :size="16" />
|
<Icon icon="vuesax-linear:arrow-left" :size="16" />
|
||||||
<Icon icon="vuesax-linear:arrow-right" :size="16" />
|
<Icon icon="vuesax-linear:arrow-right" :size="16" />
|
||||||
</div>
|
</div>
|
||||||
<div class="cs-stat cs-stat--zarin">
|
<div class="cs-stat cs-stat--zarin cs-stat--clickable" role="button" tabindex="0" @click="onTabChange('zarin')" @keydown.enter="onTabChange('zarin')">
|
||||||
<div class="cs-stat__num">
|
<div class="cs-stat__num">
|
||||||
<template v-if="status?.zarin_total != null">{{ status.zarin_total }}</template>
|
<template v-if="status?.zarin_total != null">{{ status.zarin_total }}</template>
|
||||||
<a-spin v-else-if="loadingZarin" size="small" />
|
<a-spin v-else-if="loadingZarin" size="small" />
|
||||||
<span v-else class="cs-stat__hint">تب دوم</span>
|
<span v-else class="cs-stat__hint">کلیک کنید</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="cs-stat__label">مشتریان زرین</div>
|
<div class="cs-stat__label">مشتریان زرین</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,11 +38,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="cs-stat__label">پنل — بدون زرین</div>
|
<div class="cs-stat__label">پنل — بدون زرین</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cs-stat cs-stat--sky">
|
<div class="cs-stat cs-stat--sky cs-stat--clickable" role="button" tabindex="0" @click="onTabChange('zarin')" @keydown.enter="onTabChange('zarin')">
|
||||||
<div class="cs-stat__num">
|
<div class="cs-stat__num">
|
||||||
<template v-if="status?.zarin_only_count != null">{{ status.zarin_only_count }}</template>
|
<template v-if="status?.zarin_only_count != null">{{ status.zarin_only_count }}</template>
|
||||||
<a-spin v-else-if="loadingZarin" size="small" />
|
<a-spin v-else-if="loadingZarin" size="small" />
|
||||||
<span v-else class="cs-stat__hint">تب دوم</span>
|
<span v-else class="cs-stat__hint">کلیک کنید</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="cs-stat__label">زرین — بدون پنل</div>
|
<div class="cs-stat__label">زرین — بدون پنل</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
<Icon icon="vuesax-linear:arrow-right" :size="14" />
|
<Icon icon="vuesax-linear:arrow-right" :size="14" />
|
||||||
زرین ← پنل
|
زرین ← پنل
|
||||||
<span v-if="status?.zarin_only_count" class="cs-tab__badge cs-tab__badge--sky">{{ status.zarin_only_count }}</span>
|
<span v-if="status?.zarin_only_count" class="cs-tab__badge cs-tab__badge--sky">{{ status.zarin_only_count }}</span>
|
||||||
|
<span v-else-if="!zarinFetched && !loadingZarin" class="cs-tab__hint">بارگذاری</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -193,6 +194,10 @@
|
|||||||
<span class="cs-card__head-title">مشتریان زرین بدون حساب پنل</span>
|
<span class="cs-card__head-title">مشتریان زرین بدون حساب پنل</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="cs-card__head-actions">
|
<div class="cs-card__head-actions">
|
||||||
|
<a-button size="small" :loading="loadingZarin" @click="reloadZarin">
|
||||||
|
<template #icon><Icon icon="vuesax-linear:refresh" :size="13" /></template>
|
||||||
|
بروزرسانی از زرین
|
||||||
|
</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -214,6 +219,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="status?.zarin_group_filter && status.include_zarin" class="cs-group-filter">
|
||||||
|
گروه: <b>{{ status.zarin_group_filter }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="zarinLoadFailed" class="cs-fetch-error">
|
||||||
|
<Icon icon="vuesax-linear:danger" :size="16" />
|
||||||
|
<div>
|
||||||
|
<div class="cs-fetch-error__title">دریافت لیست از زرین ممکن نشد</div>
|
||||||
|
<div class="cs-fetch-error__msg">اتصال را بررسی کنید و «بروزرسانی از زرین» را دوباره بزنید.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="syncingZarin || zarinSyncResult" class="cs-result">
|
<div v-if="syncingZarin || zarinSyncResult" class="cs-result">
|
||||||
<template v-if="syncingZarin">
|
<template v-if="syncingZarin">
|
||||||
<a-spin size="small" />
|
<a-spin size="small" />
|
||||||
@@ -233,6 +250,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="loadingZarin" class="cs-loading"><a-spin /></div>
|
<div v-if="loadingZarin" class="cs-loading"><a-spin /></div>
|
||||||
|
<template v-else-if="zarinLoadFailed">
|
||||||
|
<div class="cs-empty cs-empty--error">
|
||||||
|
<div class="cs-empty__title">لیست مشتریان فعلاً در دسترس نیست</div>
|
||||||
|
<div class="cs-empty__sub">دکمه «بروزرسانی از زرین» را دوباره بزنید.</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template v-else-if="zarinOnlyList.length">
|
<template v-else-if="zarinOnlyList.length">
|
||||||
<!-- موبایل -->
|
<!-- موبایل -->
|
||||||
<div class="cs-mobile-list">
|
<div class="cs-mobile-list">
|
||||||
@@ -300,7 +323,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<div v-else class="cs-empty">
|
<div v-else class="cs-empty">
|
||||||
<div class="cs-empty__icon"><Icon icon="vuesax-linear:tick-circle" :size="28" /></div>
|
<div class="cs-empty__icon"><Icon icon="vuesax-linear:tick-circle" :size="28" /></div>
|
||||||
<div class="cs-empty__title">همه مشتریان زرین در پنل ثبت شدهاند</div>
|
<div class="cs-empty__title">
|
||||||
|
<template v-if="(status?.zarin_total ?? 0) > 0">همه مشتریان زرین در پنل ثبت شدهاند</template>
|
||||||
|
<template v-else>هنوز لیست زرین بارگذاری نشده یا خالی است</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -329,6 +355,7 @@ const importStore = useCustomerImportStore()
|
|||||||
// ─── state ───
|
// ─── state ───
|
||||||
const loadingPanel = ref(true) // شروع با true تا اولین load کامل بشه
|
const loadingPanel = ref(true) // شروع با true تا اولین load کامل بشه
|
||||||
const loadingZarin = ref(false)
|
const loadingZarin = ref(false)
|
||||||
|
const zarinLoadFailed = ref(false)
|
||||||
const syncingPanel = ref(false)
|
const syncingPanel = ref(false)
|
||||||
const syncingZarin = ref(false)
|
const syncingZarin = ref(false)
|
||||||
const activeTab = ref<'panel' | 'zarin'>('panel')
|
const activeTab = ref<'panel' | 'zarin'>('panel')
|
||||||
@@ -404,21 +431,51 @@ async function load(panelPage = 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── load zarin customers (کند — فقط وقتی تب زرین باز میشه) ───
|
// ─── load zarin customers (کند — فقط وقتی تب زرین باز میشه) ───
|
||||||
async function loadZarin() {
|
async function loadZarin(force = false) {
|
||||||
if (zarinFetched.value) return
|
if (zarinFetched.value && !force) return
|
||||||
loadingZarin.value = true
|
loadingZarin.value = true
|
||||||
|
zarinLoadFailed.value = false
|
||||||
try {
|
try {
|
||||||
const res = await getZarinCustomerSyncStatus({ include_zarin: 1 } as any)
|
const res = await getZarinCustomerSyncStatus({ include_zarin: 1 })
|
||||||
if (status.value) {
|
if (status.value) {
|
||||||
status.value.zarin_total = res.zarin_total ?? 0
|
status.value.zarin_total = res.zarin_total ?? 0
|
||||||
status.value.zarin_only_count = res.zarin_only_count ?? 0
|
status.value.zarin_only_count = res.zarin_only_count ?? 0
|
||||||
|
status.value.zarin_group_filter = res.zarin_group_filter ?? null
|
||||||
|
status.value.include_zarin = res.include_zarin ?? false
|
||||||
|
status.value.zarin_fetch_error = null
|
||||||
} else {
|
} else {
|
||||||
status.value = res
|
status.value = { ...res, zarin_fetch_error: null }
|
||||||
}
|
}
|
||||||
zarinOnlyList.value = res.zarin_only_list?.data ?? []
|
zarinOnlyList.value = res.zarin_only_list?.data ?? []
|
||||||
zarinFetched.value = true
|
zarinFetched.value = true
|
||||||
} catch (e) { console.error(e) }
|
if (res.zarin_fetch_error || res.include_zarin === false) {
|
||||||
finally { loadingZarin.value = false }
|
zarinLoadFailed.value = true
|
||||||
|
if (status.value) {
|
||||||
|
status.value.zarin_total = 0
|
||||||
|
status.value.zarin_only_count = 0
|
||||||
|
}
|
||||||
|
message.error('دریافت لیست مشتریان از حسابداری ناموفق بود')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
zarinLoadFailed.value = true
|
||||||
|
zarinFetched.value = true
|
||||||
|
if (status.value) {
|
||||||
|
status.value.zarin_total = 0
|
||||||
|
status.value.zarin_only_count = 0
|
||||||
|
}
|
||||||
|
message.error('دریافت لیست مشتریان از حسابداری ناموفق بود')
|
||||||
|
} finally {
|
||||||
|
loadingZarin.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadZarin() {
|
||||||
|
zarinFetched.value = false
|
||||||
|
zarinLoadFailed.value = false
|
||||||
|
selectedZarin.clear()
|
||||||
|
zarinPage.value = 1
|
||||||
|
loadZarin(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTabChange(tab: 'panel' | 'zarin') {
|
function onTabChange(tab: 'panel' | 'zarin') {
|
||||||
@@ -609,6 +666,11 @@ onMounted(() => {
|
|||||||
&--zarin { background: rgba(14,165,233,0.25); }
|
&--zarin { background: rgba(14,165,233,0.25); }
|
||||||
&--amber { background: rgba(251,191,36,0.2); }
|
&--amber { background: rgba(251,191,36,0.2); }
|
||||||
&--sky { background: rgba(14,165,233,0.2); }
|
&--sky { background: rgba(14,165,233,0.2); }
|
||||||
|
&--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
&:hover { background: rgba(14,165,233,0.4); }
|
||||||
|
}
|
||||||
|
|
||||||
&__num {
|
&__num {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@@ -702,6 +764,15 @@ onMounted(() => {
|
|||||||
&--amber { background: rgba(245,158,11,0.15); color: #d97706; }
|
&--amber { background: rgba(245,158,11,0.15); color: #d97706; }
|
||||||
&--sky { background: rgba(14,165,233,0.15); color: #0ea5e9; }
|
&--sky { background: rgba(14,165,233,0.15); color: #0ea5e9; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__hint {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 99px;
|
||||||
|
background: rgba(14,165,233,0.12);
|
||||||
|
color: #0ea5e9;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── کارت ─── */
|
/* ─── کارت ─── */
|
||||||
@@ -786,6 +857,33 @@ onMounted(() => {
|
|||||||
|
|
||||||
.cs-loading { display: flex; justify-content: center; padding: 48px; }
|
.cs-loading { display: flex; justify-content: center; padding: 48px; }
|
||||||
|
|
||||||
|
.cs-group-filter {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--admin-text-secondary, #64748b);
|
||||||
|
border-bottom: 1px solid var(--admin-border);
|
||||||
|
background: var(--admin-bg-muted, transparent);
|
||||||
|
|
||||||
|
b { color: var(--admin-text); }
|
||||||
|
&__warn { color: #dc2626; font-weight: 600; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.cs-fetch-error {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid rgba(239, 68, 68, 0.25);
|
||||||
|
background: rgba(239, 68, 68, 0.08);
|
||||||
|
color: #dc2626;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
&__title { font-weight: 700; font-size: 13px; margin-bottom: 2px; }
|
||||||
|
&__msg { word-break: break-word; color: var(--admin-text); }
|
||||||
|
&__meta { margin-top: 4px; opacity: 0.8; font-family: ui-monospace, monospace; }
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── موبایل ─── */
|
/* ─── موبایل ─── */
|
||||||
.cs-mobile-list {
|
.cs-mobile-list {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -852,6 +950,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
&__icon { width: 52px; height: 52px; border-radius: 50%; background: rgba(52,211,153,0.12); color: #059669; display: flex; align-items: center; justify-content: center; }
|
&__icon { width: 52px; height: 52px; border-radius: 50%; background: rgba(52,211,153,0.12); color: #059669; display: flex; align-items: center; justify-content: center; }
|
||||||
&__title { font-size: 14px; font-weight: 700; color: var(--admin-text); }
|
&__title { font-size: 14px; font-weight: 700; color: var(--admin-text); }
|
||||||
|
&__sub { font-size: 12px; color: var(--admin-text-muted, #64748b); text-align: center; max-width: 360px; }
|
||||||
|
&--error &__title { color: #dc2626; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.cs-pagination {
|
.cs-pagination {
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ export function supportMessengerIdPlaceholder(messenger?: string): string {
|
|||||||
export const HIDDEN_LEGACY_SETTING_KEYS = new Set([
|
export const HIDDEN_LEGACY_SETTING_KEYS = new Set([
|
||||||
'main_product_price',
|
'main_product_price',
|
||||||
'montzar_price',
|
'montzar_price',
|
||||||
|
'fee_single',
|
||||||
'etehadie_api_url',
|
'etehadie_api_url',
|
||||||
'goldir_price',
|
'goldir_price',
|
||||||
'price_primary_source',
|
'price_primary_source',
|
||||||
@@ -566,11 +567,11 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
|||||||
show_product_details:
|
show_product_details:
|
||||||
'روشن: بعد از انتخاب محصول در صفحه خرید/فروش، متن توضیح محصول نمایش داده میشود. خاموش: توضیح نشان داده نمیشود.',
|
'روشن: بعد از انتخاب محصول در صفحه خرید/فروش، متن توضیح محصول نمایش داده میشود. خاموش: توضیح نشان داده نمیشود.',
|
||||||
fee_single:
|
fee_single:
|
||||||
'اختلاف پایه (ریال) بین قیمت خرید و فروش روی مظنه. معمولاً نصف مجموع اختلاف قیمت خرید و فروش است.',
|
'منسوخ — دیگر استفاده نمیشود. اختلاف فقط از buy_fee / sell_fee (خالی = صفر).',
|
||||||
buy_fee:
|
buy_fee:
|
||||||
'مبلغ اضافه (ریال) روی قیمت پایه برای سمت خرید. اگر خالی بگذارید، از «اختلاف پایه» استفاده میشود.',
|
'مبلغ اضافه (ریال) روی قیمت پایه برای سمت خرید. خالی یا صفر = بدون اختلاف.',
|
||||||
sell_fee:
|
sell_fee:
|
||||||
'مبلغ کسر (ریال) از قیمت پایه برای سمت فروش. اگر خالی بگذارید، از «اختلاف پایه» استفاده میشود.',
|
'مبلغ کسر (ریال) از قیمت پایه برای سمت فروش. خالی یا صفر = بدون اختلاف.',
|
||||||
fee_service_charge_percentage:
|
fee_service_charge_percentage:
|
||||||
'درصد کارمزد هر معامله (اعشاری؛ مثلاً ۰٫۰۰۵). روی همهٔ سفارشها اعمال میشود و با اختلاف قیمت محصول یا buy_fee/sell_fee قاطی نمیشود. محصول مقداری: اگر مبلغ ریالی قفل باشد کارمزد از فلز، اگر وزن قفل باشد از ریال. محصول تعدادی: کارمزد فقط از ریال.',
|
'درصد کارمزد هر معامله (اعشاری؛ مثلاً ۰٫۰۰۵). روی همهٔ سفارشها اعمال میشود و با اختلاف قیمت محصول یا buy_fee/sell_fee قاطی نمیشود. محصول مقداری: اگر مبلغ ریالی قفل باشد کارمزد از فلز، اگر وزن قفل باشد از ریال. محصول تعدادی: کارمزد فقط از ریال.',
|
||||||
fee_deduct_from_gold:
|
fee_deduct_from_gold:
|
||||||
@@ -776,7 +777,7 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
|||||||
zarin_gorohe_kar:
|
zarin_gorohe_kar:
|
||||||
'کد gorooheKar هنگام ساخت/جستجوی کار در زرین. اگر چند پنل یک زرین دارند، برای هر پنل گروه کار متفاوت بگذارید.',
|
'کد gorooheKar هنگام ساخت/جستجوی کار در زرین. اگر چند پنل یک زرین دارند، برای هر پنل گروه کار متفاوت بگذارید.',
|
||||||
zarin_name_g:
|
zarin_name_g:
|
||||||
'نام گروه حساب مشتریان در زرین (nameG). اگر چند پنل یک توکن دارند، برای هر پنل نام گروه متفاوت بگذارید. خالی = نام برند.',
|
'نام گروه حساب مشتریان در زرین (nameG). برای اپ خانگی معمولاً «مشتری نقدی (خریدار)» (گروه ۶). گروه همکار استفاده نشود چون مظنه فرق دارد. فقط از تنظیمات/دیپلوی تغییر میکند.',
|
||||||
zarin_uid_prefix:
|
zarin_uid_prefix:
|
||||||
'پیشوند عددی uid برای جلوگیری از تداخل بین چند پنل روی یک زرین. فقط عدد (مثلاً ۱۱).',
|
'پیشوند عددی uid برای جلوگیری از تداخل بین چند پنل روی یک زرین. فقط عدد (مثلاً ۱۱).',
|
||||||
montify_name:
|
montify_name:
|
||||||
@@ -918,7 +919,6 @@ export const SETTING_LOCKED_KEYS = new Set([
|
|||||||
'chart_id', 'chart_use_local_data',
|
'chart_id', 'chart_use_local_data',
|
||||||
'external_order_api_enabled',
|
'external_order_api_enabled',
|
||||||
'fee_deduct_from_gold',
|
'fee_deduct_from_gold',
|
||||||
'fee_single',
|
|
||||||
'referral_bonus_amount',
|
'referral_bonus_amount',
|
||||||
'group_sms',
|
'group_sms',
|
||||||
'customer_direct_sms',
|
'customer_direct_sms',
|
||||||
|
|||||||
Reference in New Issue
Block a user