Sync Tahesab wallet balance on import and let admins resolve conflicts.

Add customer search on the Tahesab-only list, bulk/per-customer mande sync UI, and a decision modal when app balance differs from Tahesab.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 14:16:36 +03:30
co-authored by Cursor
parent 084b449177
commit 04cc03fe25
7 changed files with 761 additions and 8 deletions
@@ -0,0 +1,97 @@
<template>
<BasicModal
v-model:visible="visibleProxy"
:width="isMobile ? '92vw' : '28rem'"
title="تصمیم برای مانده حساب"
:custom-footer="true"
cancel-text="بعداً"
@close="close"
>
<div class="flex flex-col gap-3 text-sm" style="color: var(--admin-text)">
<p class="m-0" style="color: var(--admin-text-secondary)">
موجودی اپ با مانده ته‌حساب فرق دارد. کدام را اعمال کنیم؟
</p>
<div class="rounded-lg border border-base p-3 space-y-2" style="background: var(--admin-bg-muted)">
<div class="flex justify-between gap-2">
<span style="color: var(--admin-text-secondary)">موجودی اپ (طلا)</span>
<strong dir="ltr">{{ formatGold(appGold) }}</strong>
</div>
<div class="flex justify-between gap-2">
<span style="color: var(--admin-text-secondary)">مانده ته‌حساب (طلا)</span>
<strong dir="ltr">{{ formatGold(tahesabGold) }}</strong>
</div>
<div class="h-px" style="background: var(--admin-border)" />
<div class="flex justify-between gap-2">
<span style="color: var(--admin-text-secondary)">موجودی اپ (ریال)</span>
<strong dir="ltr">{{ formatCash(appCash) }}</strong>
</div>
<div class="flex justify-between gap-2">
<span style="color: var(--admin-text-secondary)">مانده ته‌حساب (ریال)</span>
<strong dir="ltr">{{ formatCash(tahesabCash) }}</strong>
</div>
</div>
<div class="flex flex-col gap-2">
<a-button type="primary" block :loading="loading === 'overwrite'" :disabled="!!loading" @click="choose('overwrite')">
اعمال مانده ته‌حساب روی کیف
</a-button>
<a-button block :loading="loading === 'keep'" :disabled="!!loading" @click="choose('keep')">
نگه داشتن موجودی فعلی اپ
</a-button>
<a-button type="link" block :disabled="!!loading" @click="close">بعداً</a-button>
</div>
</div>
<template #footer><span /></template>
</BasicModal>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import BasicModal from '@/components/BasicModal.vue'
import { isMobile } from '@core/utils/is'
const props = defineProps<{
visible: boolean
appGold?: number | null
appCash?: number | null
tahesabGold?: number | null
tahesabCash?: number | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'decide', mode: 'overwrite' | 'keep'): void
}>()
const loading = ref<'overwrite' | 'keep' | null>(null)
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value),
})
watch(
() => props.visible,
(v) => {
if (!v) loading.value = null
}
)
function formatGold(v?: number | null) {
if (v == null || Number.isNaN(Number(v))) return '—'
return Number(v).toLocaleString('fa-IR', { maximumFractionDigits: 3 })
}
function formatCash(v?: number | null) {
if (v == null || Number.isNaN(Number(v))) return '—'
return Math.round(Number(v)).toLocaleString('fa-IR')
}
function close() {
visibleProxy.value = false
}
function choose(mode: 'overwrite' | 'keep') {
loading.value = mode
emit('decide', mode)
}
</script>