feat: add shayegan inbound charges tab and floating alert host
This commit is contained in:
Vendored
+1
@@ -92,6 +92,7 @@ declare module '@vue/runtime-core' {
|
||||
PargarRetryModal: typeof import('./src/components/PargarRetryModal.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
ShayeganChargeAlertHost: typeof import('./src/components/ShayeganChargeAlertHost.vue')['default']
|
||||
ShayeganCustomerRegisterModal: typeof import('./src/components/ShayeganCustomerRegisterModal.vue')['default']
|
||||
SmsCreditWidget: typeof import('./src/components/SmsCreditWidget.vue')['default']
|
||||
SmsNegativeBalanceHost: typeof import('./src/components/SmsNegativeBalanceHost.vue')['default']
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<CustomerImportProgress />
|
||||
<TahesabDocSyncProgress />
|
||||
<GroupSmsSendProgress />
|
||||
<ShayeganChargeAlertHost />
|
||||
</AConfigProvider>
|
||||
</template>
|
||||
|
||||
@@ -15,6 +16,7 @@ import { initDarkmode } from '@core/stores/darkmode'
|
||||
import CustomerImportProgress from '@/components/CustomerImportProgress.vue'
|
||||
import TahesabDocSyncProgress from '@/components/TahesabDocSyncProgress.vue'
|
||||
import GroupSmsSendProgress from '@/components/GroupSmsSendProgress.vue'
|
||||
import ShayeganChargeAlertHost from '@/components/ShayeganChargeAlertHost.vue'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useTahesabDocSyncStore } from '@/store/tahesabDocSyncStore'
|
||||
import { bootstrapAdminChat, useAdminChatService } from '@/utils/adminChat'
|
||||
|
||||
@@ -4,6 +4,14 @@ import type { TableInput, TableOutput } from '@core/type/baseTable'
|
||||
export interface ShayeganRecord {
|
||||
id?: number | null
|
||||
customer_id?: number | null
|
||||
customer?: {
|
||||
id?: number
|
||||
name?: string
|
||||
fname?: string
|
||||
lname?: string
|
||||
mobile?: string
|
||||
moshtari_code?: string
|
||||
} | null
|
||||
order_id?: number | null
|
||||
withdraw_id?: number | null
|
||||
amount?: number | null
|
||||
@@ -42,3 +50,59 @@ export const registerCustomerInShayegan = async (customerId: number) =>
|
||||
await useAxios.post<{ ok: boolean; message: string; moshtari_code?: string }>(
|
||||
`/admin/customer/${customerId}/shayegan/register`
|
||||
)
|
||||
|
||||
export interface ShayeganInboundChargeRecord {
|
||||
id: number
|
||||
customer_id?: number | null
|
||||
customer_tafsil?: string | null
|
||||
shayegan_number?: string | null
|
||||
amount: number
|
||||
description?: string | null
|
||||
source?: string
|
||||
status: 'pending' | 'approved' | 'rejected'
|
||||
approved_at?: string | null
|
||||
rejected_at?: string | null
|
||||
reject_reason?: string | null
|
||||
customer?: {
|
||||
id: number
|
||||
name?: string
|
||||
fname?: string
|
||||
lname?: string
|
||||
mobile?: string
|
||||
n_code?: string
|
||||
moshtari_code?: string
|
||||
} | null
|
||||
approver?: {
|
||||
id: number
|
||||
name?: string
|
||||
} | null
|
||||
created_at?: string | null
|
||||
}
|
||||
|
||||
export const getPendingShayeganCharges = async () =>
|
||||
await useAxios.get<{ ok: boolean; charges: ShayeganInboundChargeRecord[] }>(
|
||||
'/admin/shayegan/charges/pending'
|
||||
)
|
||||
|
||||
export const getShayeganChargesTable = async (params: TableInput & { status?: string }) =>
|
||||
await useAxios.post<TableOutput<ShayeganInboundChargeRecord>>(
|
||||
'/admin/shayegan/charges/table',
|
||||
params
|
||||
)
|
||||
|
||||
export const approveShayeganCharge = async (id: number | string) =>
|
||||
await useAxios.post<{ ok: boolean; message: string; charge?: ShayeganInboundChargeRecord }>(
|
||||
`/admin/shayegan/charges/${id}/approve`
|
||||
)
|
||||
|
||||
export const rejectShayeganCharge = async (id: number | string, reason?: string) =>
|
||||
await useAxios.post<{ ok: boolean; message: string; charge?: ShayeganInboundChargeRecord }>(
|
||||
`/admin/shayegan/charges/${id}/reject`,
|
||||
{ reason }
|
||||
)
|
||||
|
||||
export const syncShayeganCharges = async () =>
|
||||
await useAxios.post<{ ok: boolean; message: string; new_count?: number }>(
|
||||
'/admin/shayegan/charges/sync'
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<div v-if="activeCharges.length > 0" class="shayegan-alert-container">
|
||||
<transition-group name="slide-down" tag="div" class="flex flex-col gap-2">
|
||||
<div
|
||||
v-for="charge in activeCharges"
|
||||
:key="charge.id"
|
||||
class="shayegan-charge-card shadow-lg"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="bell-icon-pulse flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-amber-100 text-amber-600">
|
||||
<Icon icon="vuesax-bold:notification-bing" size="20" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-gray-800 text-sm">
|
||||
واریزی جدید از سیستم حسابداری شایگان
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-0.5">
|
||||
سند شایگان: {{ charge.shayegan_number || '-' }} · تفصیلی: {{ charge.customer_tafsil || charge.customer?.moshtari_code || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="text-gray-400 hover:text-gray-600 p-1 text-sm rounded cursor-pointer border-0 bg-transparent"
|
||||
title="بستن موقت"
|
||||
@click="dismissCharge(charge.id)"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="my-2.5 rounded-md bg-amber-50/80 p-2.5 text-sm text-amber-900 border border-amber-200">
|
||||
حساب شخص <b>{{ charge.customer?.name || 'نامشخص' }}</b> از طریق پنل حسابداری مقدار <b>{{ formatPrice(charge.amount) }} ریال</b> شارژ شد
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-2 pt-1 border-t border-gray-100">
|
||||
<div class="text-[11px] text-gray-400">
|
||||
{{ formatTime(charge.created_at) }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<a-button
|
||||
size="small"
|
||||
type="primary"
|
||||
class="!bg-emerald-600 !border-emerald-600 hover:!bg-emerald-700 text-white font-medium"
|
||||
:loading="approvingId === charge.id"
|
||||
@click="handleApprove(charge)"
|
||||
>
|
||||
تأیید و شارژ کیف پول
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
danger
|
||||
:disabled="approvingId === charge.id"
|
||||
@click="handleReject(charge)"
|
||||
>
|
||||
رد
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
class="text-xs text-gray-500"
|
||||
@click="goToAccountingTab"
|
||||
>
|
||||
لیست کامل
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import {
|
||||
getPendingShayeganCharges,
|
||||
approveShayeganCharge,
|
||||
rejectShayeganCharge,
|
||||
type ShayeganInboundChargeRecord
|
||||
} from '@/api/shayegan'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const activeCharges = ref<ShayeganInboundChargeRecord[]>([])
|
||||
const dismissedIds = ref<number[]>([])
|
||||
const approvingId = ref<number | null>(null)
|
||||
let pollTimer: any = null
|
||||
|
||||
function formatPrice(val: number) {
|
||||
return usePrice(val, true, '')
|
||||
}
|
||||
|
||||
function formatTime(dt?: string | null) {
|
||||
return dt ? toJalali(dt) : ''
|
||||
}
|
||||
|
||||
function dismissCharge(id: number) {
|
||||
if (!dismissedIds.value.includes(id)) {
|
||||
dismissedIds.value.push(id)
|
||||
}
|
||||
activeCharges.value = activeCharges.value.filter(c => c.id !== id)
|
||||
}
|
||||
|
||||
async function fetchPending() {
|
||||
if (!userStore.userInfo?.id) return
|
||||
try {
|
||||
const res = await getPendingShayeganCharges()
|
||||
if (res && res.ok && Array.isArray(res.charges)) {
|
||||
// فقط مواردی که هنوز dismiss نشدهاند یا تازه آمدهاند
|
||||
activeCharges.value = res.charges.filter(c => !dismissedIds.value.includes(c.id))
|
||||
}
|
||||
} catch {
|
||||
// خطای شبکه را بیصدا نادیده میگیریم تا مزاحم کاربر نشود
|
||||
}
|
||||
}
|
||||
|
||||
async function handleApprove(charge: ShayeganInboundChargeRecord) {
|
||||
approvingId.value = charge.id
|
||||
try {
|
||||
const res = await approveShayeganCharge(charge.id)
|
||||
if (res && res.ok) {
|
||||
message.success(res.message || `کیف پول شخص ${charge.customer?.name} با موفقیت شارژ شد.`)
|
||||
activeCharges.value = activeCharges.value.filter(c => c.id !== charge.id)
|
||||
} else {
|
||||
message.error(res?.message || 'خطا در تایید شارژ')
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message || 'خطا در تایید شارژ و افزایش کیف پول')
|
||||
} finally {
|
||||
approvingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function handleReject(charge: ShayeganInboundChargeRecord) {
|
||||
Modal.confirm({
|
||||
title: 'رد واریزی شایگان',
|
||||
content: `آیا از رد واریزی ${formatPrice(charge.amount)} ریالی برای ${charge.customer?.name || 'مشتری'} اطمینان دارید؟`,
|
||||
okText: 'بله، رد شود',
|
||||
cancelText: 'انصراف',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
try {
|
||||
const res = await rejectShayeganCharge(charge.id)
|
||||
if (res && res.ok) {
|
||||
message.info(res.message || 'واریزی رد شد.')
|
||||
activeCharges.value = activeCharges.value.filter(c => c.id !== charge.id)
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message || 'خطا در رد شارژ')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function goToAccountingTab() {
|
||||
router.push({
|
||||
path: '/menu/accounting',
|
||||
query: { tab: 'shayegan-charges' }
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void fetchPending()
|
||||
// چک کردن خودکار هر ۳۰ ثانیه
|
||||
pollTimer = setInterval(fetchPending, 30000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (pollTimer) clearInterval(pollTimer)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => userStore.userInfo?.id,
|
||||
(id) => {
|
||||
if (id) void fetchPending()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.shayegan-alert-container {
|
||||
position: fixed;
|
||||
top: 1.5rem;
|
||||
left: 1.5rem;
|
||||
z-index: 9999;
|
||||
max-width: 440px;
|
||||
width: calc(100vw - 3rem);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.shayegan-charge-card {
|
||||
pointer-events: auto;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-right: 4px solid #f59e0b;
|
||||
border-radius: 0.75rem;
|
||||
padding: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.bell-icon-pulse {
|
||||
animation: pulse-ring 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-ring {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4);
|
||||
}
|
||||
70% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 8px rgba(245, 158, 11, 0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.slide-down-enter-active,
|
||||
.slide-down-leave-active {
|
||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.slide-down-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
.slide-down-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<div class="min-w-0 w-full max-w-full overflow-x-hidden">
|
||||
<BasicShowContext title="واریزیهای شایگان" :help="help">
|
||||
<template #extra>
|
||||
<div class="flex items-center gap-2">
|
||||
<a-select
|
||||
v-model:value="statusFilter"
|
||||
size="small"
|
||||
style="width: 140px"
|
||||
placeholder="فیلتر وضعیت"
|
||||
@change="reloadTable"
|
||||
>
|
||||
<a-select-option value="">همه وضعیتها</a-select-option>
|
||||
<a-select-option value="pending">در انتظار تایید</a-select-option>
|
||||
<a-select-option value="approved">تایید شده</a-select-option>
|
||||
<a-select-option value="rejected">رد شده</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="syncLoading"
|
||||
@click="runSyncFromShayegan"
|
||||
>
|
||||
استعلام از شایگان
|
||||
</a-button>
|
||||
|
||||
<a-button size="small" @click="reloadTable">
|
||||
بروزرسانی
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-alert
|
||||
v-if="syncMessage"
|
||||
:type="syncOk ? 'success' : 'error'"
|
||||
:message="syncMessage"
|
||||
show-icon
|
||||
closable
|
||||
class="mb-3"
|
||||
@close="syncMessage = ''"
|
||||
/>
|
||||
|
||||
<div class="min-w-0 w-full max-w-full overflow-x-auto">
|
||||
<BaseTable v-bind="tblProps" @register="submit">
|
||||
<template #customer="{ record }">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium text-gray-800">{{ customerLabel(record) }}</span>
|
||||
<span class="text-xs text-gray-400" v-if="record.customer?.mobile">{{ record.customer.mobile }}</span>
|
||||
<span class="text-[11px] text-gray-400" v-if="record.customer_tafsil || record.customer?.moshtari_code">
|
||||
تفصیلی: {{ record.customer_tafsil || record.customer?.moshtari_code }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="statusColor(record.status)">{{ statusLabel(record.status) }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template #ops="{ record }">
|
||||
<div class="flex items-center justify-center gap-1.5" v-if="record.status === 'pending'">
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title><span>تأیید و شارژ کیف پول در بورس مس</span></template>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
class="!bg-emerald-600 !border-emerald-600 hover:!bg-emerald-700 !px-2"
|
||||
:loading="actionLoadingId === record.id"
|
||||
@click="handleApprove(record)"
|
||||
>
|
||||
تأیید واریزی
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title><span>رد درخواست</span></template>
|
||||
<a-button
|
||||
danger
|
||||
size="small"
|
||||
class="!px-2"
|
||||
:disabled="actionLoadingId === record.id"
|
||||
@click="handleReject(record)"
|
||||
>
|
||||
رد
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div v-else class="text-xs text-gray-400 text-center">
|
||||
<span v-if="record.status === 'approved'">
|
||||
واریز شد
|
||||
<div v-if="record.approver?.name" class="text-[11px] text-gray-500">
|
||||
توسط {{ record.approver.name }}
|
||||
</div>
|
||||
</span>
|
||||
<span v-else-if="record.status === 'rejected'">
|
||||
رد شده
|
||||
<div v-if="record.reject_reason" class="text-[11px] text-red-500">
|
||||
{{ record.reject_reason }}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</BaseTable>
|
||||
</div>
|
||||
</BasicShowContext>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
|
||||
import type { TableInput } from '@core/type/baseTable'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import {
|
||||
getShayeganChargesTable,
|
||||
approveShayeganCharge,
|
||||
rejectShayeganCharge,
|
||||
syncShayeganCharges,
|
||||
type ShayeganInboundChargeRecord
|
||||
} from '@/api/shayegan'
|
||||
|
||||
const help =
|
||||
'واریزیهای ثبت شده در پنل حسابداری شایگان. با زدن دکمه «تأیید واریزی»، کیف پول ریالی مشتری در بورس مس شارژ خواهد شد.'
|
||||
|
||||
const [submit, reload] = useBaseTable()
|
||||
const statusFilter = ref('')
|
||||
const syncLoading = ref(false)
|
||||
const syncOk = ref(false)
|
||||
const syncMessage = ref('')
|
||||
const actionLoadingId = ref<number | null>(null)
|
||||
|
||||
const columns = [
|
||||
{ title: 'شناسه', dataIndex: 'id', key: 'id', width: 70, type: 'Number', filterable: false, sortable: false },
|
||||
{
|
||||
title: 'مشتری',
|
||||
dataIndex: 'customer',
|
||||
key: 'customer',
|
||||
slot: 'customer',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
title: 'مبلغ واریزی (ریال)',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
type: 'Number',
|
||||
filterable: false,
|
||||
customRender: (r: any) => usePrice(r.record?.amount || 0, true, ' ریال')
|
||||
},
|
||||
{ title: 'شماره سند شایگان', dataIndex: 'shayegan_number', key: 'shayegan_number', type: 'Text' },
|
||||
{ title: 'توضیحات', dataIndex: 'description', key: 'description', type: 'Text' },
|
||||
{
|
||||
title: 'منبع',
|
||||
dataIndex: 'source',
|
||||
key: 'source',
|
||||
type: 'Text',
|
||||
width: 90,
|
||||
customRender: (r: any) => (r.record?.source === 'sync' ? 'استعلام' : 'وبهوک')
|
||||
},
|
||||
{
|
||||
title: 'وضعیت',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
slot: 'status',
|
||||
type: 'Text',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: 'تاریخ دریافت',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
type: 'DateTime',
|
||||
filterable: false,
|
||||
customRender: (r: any) => (r.record?.created_at ? toJalali(r.record.created_at) : '-')
|
||||
},
|
||||
{
|
||||
title: 'عملیات',
|
||||
dataIndex: 'ops',
|
||||
key: 'ops',
|
||||
slot: 'ops',
|
||||
width: 150,
|
||||
filterable: false,
|
||||
sortable: false
|
||||
}
|
||||
]
|
||||
|
||||
function asTable(res: any, params: TableInput) {
|
||||
if (res?.query) {
|
||||
return {
|
||||
rows: Array.isArray(res.rows) ? res.rows : [],
|
||||
query: {
|
||||
...params,
|
||||
...res.query,
|
||||
filters: res.query.filters ?? []
|
||||
}
|
||||
}
|
||||
}
|
||||
const rows = Array.isArray(res)
|
||||
? res
|
||||
: Array.isArray(res?.data)
|
||||
? res.data
|
||||
: Array.isArray(res?.rows)
|
||||
? res.rows
|
||||
: []
|
||||
return {
|
||||
rows,
|
||||
query: {
|
||||
page: Number(res?.current_page ?? params.page ?? 1),
|
||||
limit: Number(res?.per_page ?? params.limit ?? 20),
|
||||
total: Number(res?.total ?? rows.length),
|
||||
filters: params.filters ?? [],
|
||||
search: params.search ?? '',
|
||||
select: params.select ?? []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tblProps = {
|
||||
columns,
|
||||
defaultPageLimit: 20,
|
||||
api: async (params: TableInput) => {
|
||||
try {
|
||||
const mergedParams = {
|
||||
...params,
|
||||
status: statusFilter.value || undefined
|
||||
}
|
||||
return asTable(await getShayeganChargesTable(mergedParams), params)
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || 'خطا در دریافت لیست واریزیهای شایگان')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function customerLabel(record?: ShayeganInboundChargeRecord) {
|
||||
if (record?.customer?.name?.trim()) return record.customer.name.trim()
|
||||
if (record?.customer?.fname || record?.customer?.lname) {
|
||||
const fullName = `${record?.customer?.fname || ''} ${record?.customer?.lname || ''}`.trim()
|
||||
if (fullName) return fullName
|
||||
}
|
||||
return '-'
|
||||
}
|
||||
|
||||
function statusLabel(status?: string) {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
return 'تأیید شده'
|
||||
case 'rejected':
|
||||
return 'رد شده'
|
||||
case 'pending':
|
||||
default:
|
||||
return 'در انتظار تأیید'
|
||||
}
|
||||
}
|
||||
|
||||
function statusColor(status?: string) {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
return 'green'
|
||||
case 'rejected':
|
||||
return 'red'
|
||||
case 'pending':
|
||||
default:
|
||||
return 'orange'
|
||||
}
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
reload()
|
||||
}
|
||||
|
||||
async function runSyncFromShayegan() {
|
||||
syncLoading.value = true
|
||||
syncMessage.value = ''
|
||||
try {
|
||||
const res = await syncShayeganCharges()
|
||||
syncOk.value = !!res?.ok
|
||||
syncMessage.value = res?.message || 'استعلام با موفقیت انجام شد.'
|
||||
if (res?.ok) {
|
||||
message.success(syncMessage.value)
|
||||
reloadTable()
|
||||
} else {
|
||||
message.error(syncMessage.value)
|
||||
}
|
||||
} catch (e: any) {
|
||||
syncOk.value = false
|
||||
syncMessage.value = e?.response?.data?.message || e?.message || 'خطا در استعلام از شایگان'
|
||||
message.error(syncMessage.value)
|
||||
} finally {
|
||||
syncLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleApprove(record: ShayeganInboundChargeRecord) {
|
||||
Modal.confirm({
|
||||
title: 'تأیید واریزی شایگان',
|
||||
content: `آیا تأیید میکنید مبلغ ${usePrice(record.amount, true, ' ریال')} به کیف پول ${customerLabel(record)} در بورس مس واریز شود؟`,
|
||||
okText: 'تأیید و شارژ',
|
||||
cancelText: 'انصراف',
|
||||
async onOk() {
|
||||
actionLoadingId.value = record.id
|
||||
try {
|
||||
const res = await approveShayeganCharge(record.id)
|
||||
if (res && res.ok) {
|
||||
message.success(res.message || 'کیف پول با موفقیت شارژ شد.')
|
||||
reloadTable()
|
||||
} else {
|
||||
message.error(res?.message || 'خطا در شارژ کیف پول')
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message || 'خطا در شارژ کیف پول مشتری')
|
||||
} finally {
|
||||
actionLoadingId.value = null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleReject(record: ShayeganInboundChargeRecord) {
|
||||
let reasonInput = ''
|
||||
Modal.confirm({
|
||||
title: 'رد واریزی شایگان',
|
||||
content: `آیا از رد واریزی ${usePrice(record.amount, true, ' ریال')} برای ${customerLabel(record)} اطمینان دارید؟`,
|
||||
okText: 'رد درخواست',
|
||||
cancelText: 'انصراف',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
actionLoadingId.value = record.id
|
||||
try {
|
||||
const res = await rejectShayeganCharge(record.id, reasonInput)
|
||||
if (res && res.ok) {
|
||||
message.info(res.message || 'درخواست رد شد.')
|
||||
reloadTable()
|
||||
} else {
|
||||
message.error(res?.message || 'خطا در رد درخواست')
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message || 'خطا در رد درخواست')
|
||||
} finally {
|
||||
actionLoadingId.value = null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -73,6 +73,13 @@ const healthMessage = ref('')
|
||||
|
||||
const columns = [
|
||||
{ title: 'شناسه', dataIndex: 'id', key: 'id', width: 80, type: 'Number', filterable: false, sortable: false },
|
||||
{
|
||||
title: 'مشتری',
|
||||
dataIndex: 'customer.name',
|
||||
key: 'customer.name',
|
||||
type: 'Text',
|
||||
customRender: (r: any) => customerLabel(r.record)
|
||||
},
|
||||
{
|
||||
title: 'نوع',
|
||||
dataIndex: 'type',
|
||||
@@ -160,6 +167,15 @@ const tblProps = {
|
||||
}
|
||||
}
|
||||
|
||||
function customerLabel(record?: any) {
|
||||
if (record?.customer?.name?.trim()) return record.customer.name.trim()
|
||||
if (record?.customer?.fname || record?.customer?.lname) {
|
||||
const fullName = `${record?.customer?.fname || ''} ${record?.customer?.lname || ''}`.trim()
|
||||
if (fullName) return fullName
|
||||
}
|
||||
return '-'
|
||||
}
|
||||
|
||||
function typeLabel(type?: string | null) {
|
||||
if (!type) return '-'
|
||||
return TYPE_LABELS[type] ?? type
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<ATabPane v-if="pargarEnabled" key="pargar-customer-sync" tab="همگام سازی مشتریان در پرگار" />
|
||||
<ATabPane v-if="pargarEnabled" key="pargar-wallet-sync" tab="مانده کیف پرگار" />
|
||||
<ATabPane v-if="shayeganEnabled" key="shayegan" tab="اسناد شایگان" />
|
||||
<ATabPane v-if="shayeganEnabled" key="shayegan-charges" tab="واریزیهای شایگان" />
|
||||
</ATabs>
|
||||
<tbl v-if="activeTab === 'reports'" ref="table" class="min-h-0 flex-1" />
|
||||
<tahesabTbl v-else-if="tahesabEnabled && activeTab === 'tahesab'" ref="tahesabTable" class="min-h-0 flex-1" />
|
||||
@@ -30,6 +31,7 @@
|
||||
<pargarCustomerSync v-else-if="pargarEnabled && activeTab === 'pargar-customer-sync'" class="shrink-0" />
|
||||
<pargarWalletSync v-else-if="pargarEnabled && activeTab === 'pargar-wallet-sync'" class="shrink-0" />
|
||||
<shayeganTbl v-else-if="shayeganEnabled && activeTab === 'shayegan'" ref="shayeganTable" class="min-h-0 flex-1" />
|
||||
<shayeganChargesTab v-else-if="shayeganEnabled && activeTab === 'shayegan-charges'" class="min-h-0 flex-1" />
|
||||
</div>
|
||||
</BasicMainCard>
|
||||
<BasicModal
|
||||
@@ -156,7 +158,8 @@
|
||||
// import BasicInfoModal from '@/components/BasicInfoModal'
|
||||
import { inputLenRules, inputRules, inputRulesNumber } from '@/utils/inputRules'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import tbl from './components/table.vue'
|
||||
import tahesabTbl from './components/tahesabTable.vue'
|
||||
import customerSync from './components/TahesabCustomerSync.vue'
|
||||
@@ -165,6 +168,7 @@ import zarinTbl from './components/zarinTable.vue'
|
||||
import zarinCustomerSync from './components/ZarinCustomerSync.vue'
|
||||
import zarinWalletSync from './components/ZarinWalletBulkSync.vue'
|
||||
import shayeganTbl from './components/shayeganTable.vue'
|
||||
import shayeganChargesTab from './components/ShayeganChargesTab.vue'
|
||||
import pargarTbl from './components/pargarTable.vue'
|
||||
import pargarCustomerSync from './components/PargarCustomerSync.vue'
|
||||
import pargarWalletSync from './components/PargarWalletBulkSync.vue'
|
||||
@@ -189,14 +193,24 @@ import { metalLabel } from '@/utils/adminMetalLabels'
|
||||
import { initAccountingSettingsFromApi, tahesabEnabledSetting, zarinEnabledSetting, pargarEnabledSetting, shayeganEnabledSetting } from '@/utils/config'
|
||||
|
||||
//loading
|
||||
const route = useRoute()
|
||||
const loading = ref<boolean>(false)
|
||||
const submitError = ref('')
|
||||
const activeTab = ref<'reports' | 'tahesab' | 'customer-sync' | 'tahesab-wallet-sync' | 'zarin' | 'zarin-customer-sync' | 'zarin-wallet-sync' | 'pargar' | 'pargar-customer-sync' | 'pargar-wallet-sync' | 'shayegan'>('reports')
|
||||
const activeTab = ref<'reports' | 'tahesab' | 'customer-sync' | 'tahesab-wallet-sync' | 'zarin' | 'zarin-customer-sync' | 'zarin-wallet-sync' | 'pargar' | 'pargar-customer-sync' | 'pargar-wallet-sync' | 'shayegan' | 'shayegan-charges'>((route.query.tab as any) || 'reports')
|
||||
const tahesabEnabled = tahesabEnabledSetting
|
||||
const zarinEnabled = zarinEnabledSetting
|
||||
const pargarEnabled = pargarEnabledSetting
|
||||
const shayeganEnabled = shayeganEnabledSetting
|
||||
|
||||
watch(
|
||||
() => route.query.tab,
|
||||
(tab) => {
|
||||
if (tab && typeof tab === 'string') {
|
||||
activeTab.value = tab as any
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
//define store
|
||||
const store = useStaticApiListsStore()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user