Files
boursecu_admin/src/pages/menuPages/accounting/components/shayeganTable.vue
T
safavimohadeseh78andCursor 29904adf02 fix(shayegan): save customer accounting codes and align the document table
Shayegan was rejected as inactive when editing moshtari_code. The document list now uses the shared table format.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 13:24:02 +03:30

222 lines
6.2 KiB
Vue

<template>
<div class="min-w-0 w-full max-w-full overflow-x-hidden">
<BasicShowContext title="اسناد شایگان" :help="help">
<template #extra>
<a-button size="small" :loading="healthLoading" @click="runHealth">تست اتصال</a-button>
</template>
<a-alert
v-if="healthMessage"
:type="healthOk ? 'success' : 'error'"
:message="healthMessage"
show-icon
closable
class="mb-2"
@close="healthMessage = ''"
/>
<div class="min-w-0 w-full max-w-full overflow-x-auto">
<BaseTable v-bind="tblProps" @register="submit">
<template #status="{ record }">
<a-tag :color="statusColor(record.sync_status)">{{ statusLabel(record.sync_status) }}</a-tag>
</template>
<template #ops="{ record }">
<div class="flex justify-center">
<a-tooltip
v-if="record.sync_status !== 'success'"
placement="bottom"
class="cursor-pointer"
@click="retry(record)"
>
<template #title><span>ارسال مجدد</span></template>
<Icon icon="vuesax-linear:send-2" color="#CA8A04" />
</a-tooltip>
</div>
</template>
</BaseTable>
</div>
</BasicShowContext>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Modal, message } from 'ant-design-vue'
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
import type { TableInput } from '@core/type/baseTable'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import { toJalali } from '@core/utils/useDataTime'
import { usePrice } from '@core/utils/usePrice'
import { shayeganHealth, shayeganRetry, shayeganTable } from '@/api/shayegan'
const help =
'وضعیت ارسال به حسابگر. تست اتصال فقط خواندنی است و سند آزمایشی در CYPRSD04 نمی‌سازد.'
const TYPE_LABELS: Record<string, string> = {
customer: 'ثبت مشتری',
trade: 'معامله',
wallet_cash: 'کیف ریالی',
wallet_gold: 'کیف محصول'
}
const STATUS_LABELS: Record<string, string> = {
success: 'موفق',
failed: 'ناموفق',
timeout: 'تایم‌اوت',
pending: 'در انتظار'
}
const [submit, reload] = useBaseTable()
const healthLoading = ref(false)
const healthOk = ref(false)
const healthMessage = ref('')
const columns = [
{ title: 'شناسه', dataIndex: 'id', key: 'id', width: 80, type: 'Number', filterable: false, sortable: false },
{
title: 'نوع',
dataIndex: 'type',
key: 'type',
type: 'Text',
customRender: (r: any) => typeLabel(r.record?.type)
},
{
title: 'مبلغ/مقدار',
dataIndex: 'amount',
key: 'amount',
type: 'Number',
filterable: false,
customRender: (r: any) => (r.record?.amount == null || r.record?.amount === '' ? '-' : usePrice(r.record.amount, true, ''))
},
{
title: 'وضعیت',
dataIndex: 'sync_status',
key: 'sync_status',
slot: 'status',
type: 'Text'
},
{ title: 'شماره شایگان', dataIndex: 'shayegan_number', key: 'shayegan_number', type: 'Text' },
{ title: 'توضیح', dataIndex: 'desc', key: 'desc', type: 'Text' },
{
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: 110,
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 {
return asTable(await shayeganTable(params), params)
} catch (e: any) {
message.error(e?.message || 'خطا در دریافت اسناد شایگان')
throw e
}
}
}
function typeLabel(type?: string | null) {
if (!type) return '-'
return TYPE_LABELS[type] ?? type
}
function statusLabel(status?: string | null) {
if (!status) return '-'
return STATUS_LABELS[status] ?? status
}
function statusColor(status?: string) {
if (status === 'success') return 'green'
if (status === 'failed') return 'red'
if (status === 'timeout') return 'orange'
return 'blue'
}
async function runHealth() {
healthLoading.value = true
healthMessage.value = ''
try {
const res: any = await shayeganHealth()
healthOk.value = !!res?.ok
healthMessage.value = res?.message || (res?.ok ? 'اتصال برقرار است' : 'اتصال ناموفق')
} catch (e: any) {
healthOk.value = false
healthMessage.value = e?.response?.data?.message || e?.message || 'خطای تست اتصال'
} finally {
healthLoading.value = false
}
}
function retry(record: any) {
const isTimeout = record.sync_status === 'timeout'
Modal.confirm({
title: 'ارسال مجدد به شایگان',
content: isTimeout
? 'این سند timeout شده. فقط اگر در حسابگر ثبت نشده، تأیید کنید.'
: 'سند دوباره به شایگان ارسال شود؟',
okText: 'ارسال',
cancelText: 'انصراف',
onOk: async () => {
try {
await shayeganRetry(record.id, isTimeout)
message.success('انجام شد')
await reload()
} catch (e: any) {
message.error(e?.response?.data?.message || e?.message || 'خطا')
}
}
})
}
async function fetch() {
await reload()
}
defineExpose({ fetch })
</script>